diff --git a/.bazelignore b/.bazelignore index b71007690f1cf..ac7a2d15a7aa4 100644 --- a/.bazelignore +++ b/.bazelignore @@ -8,8 +8,7 @@ .idea .teamcity .yarn-local-mirror -bazel-cache -bazel-dist +/bazel build node_modules target diff --git a/.bazelrc b/.bazelrc index fd469d1203a82..741067e4ff18e 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,4 +1,4 @@ -# Inspired on from https://raw.githubusercontent.com/bazelbuild/rules_nodejs/master/.bazelrc +# Inspired by https://raw.githubusercontent.com/bazelbuild/rules_nodejs/master/.bazelrc # Import shared settings first so we can override below import %workspace%/.bazelrc.common diff --git a/.bazelrc.common b/.bazelrc.common index a53d1b8072483..491b2eb8ef821 100644 --- a/.bazelrc.common +++ b/.bazelrc.common @@ -5,18 +5,22 @@ # # The full list of Bazel options: https://docs.bazel.build/versions/master/command-line-reference.html -# Cache action outputs on disk so they persist across output_base and bazel shutdown (eg. changing branches) -build --disk_cache=bazel-cache/disk-cache +# Local Cache Settings +## Avoid cache results from being corrupt when changing source during build +common --experimental_guard_against_concurrent_changes -# Bazel repo cache settings -build --repository_cache=bazel-cache/repository-cache +## Cache action outputs on disk so they persist across output_base and bazel shutdown (eg. changing branches) +build --disk_cache=~/.bazel-cache/disk-cache + +## Bazel repo cache settings +build --repository_cache=~/.bazel-cache/repository-cache # Bazel will create symlinks from the workspace directory to output artifacts. -# Build results will be placed in a directory called "bazel-dist/bin" +# Build results will be placed in a directory called "bazel/bin" # This will still create a bazel-out symlink in # the project directory, which must be excluded from the # editor's search path. -build --symlink_prefix=bazel-dist/ +build --symlink_prefix=bazel/ # To disable the symlinks altogether (including bazel-out) we can use # build --symlink_prefix=/ # however this makes it harder to find outputs. @@ -37,9 +41,7 @@ common --color=yes build --show_task_finish build --noshow_progress build --noshow_loading_progress - -## enforced default values -build --show_result=1 +build --show_result=0 # Specifies desired output mode for running tests. # Valid values are @@ -78,7 +80,8 @@ test:debug --test_output=streamed --test_strategy=exclusive --test_timeout=9999 # The node process will break before user code starts and wait for the debugger to connect. run:debug --define=VERBOSE_LOGS=1 -- --node_options=--inspect-brk # The following option will change the build output of certain rules such as terser and may not be desirable in all cases -build:debug --compilation_mode=dbg +# It will also output both the repo cache and action cache to a folder inside the repo +build:debug --compilation_mode=dbg --show_result=1 --disk_cache=bazel/disk-cache --repository_cache=bazel/repository-cache # Turn off legacy external runfiles # This prevents accidentally depending on this feature, which Bazel will remove. diff --git a/.ci/teamcity/bootstrap.sh b/.ci/teamcity/bootstrap.sh deleted file mode 100755 index fc57811bb2077..0000000000000 --- a/.ci/teamcity/bootstrap.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -source "$(dirname "${0}")/util.sh" - -tc_start_block "Bootstrap" - -tc_start_block "yarn install and kbn bootstrap" -verify_no_git_changes yarn kbn bootstrap -tc_end_block "yarn install and kbn bootstrap" - -tc_start_block "build kbn-pm" -verify_no_git_changes yarn kbn run build -i @kbn/pm -tc_end_block "build kbn-pm" - -tc_start_block "build plugin list docs" -verify_no_git_changes node scripts/build_plugin_list_docs -tc_end_block "build plugin list docs" - -tc_end_block "Bootstrap" diff --git a/.ci/teamcity/checks/bundle_limits.sh b/.ci/teamcity/checks/bundle_limits.sh deleted file mode 100755 index 751ec5a03ee7b..0000000000000 --- a/.ci/teamcity/checks/bundle_limits.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -checks-reporter-with-killswitch "Check Bundle Limits" \ - node scripts/build_kibana_platform_plugins --validate-limits diff --git a/.ci/teamcity/checks/commit.sh b/.ci/teamcity/checks/commit.sh deleted file mode 100755 index 387ec0c126785..0000000000000 --- a/.ci/teamcity/checks/commit.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -# Runs pre-commit hook script for the files touched in the last commit. -# That way we can ensure a set of quick commit checks earlier as we removed -# the pre-commit hook installation by default. -# If files are more than 200 we will skip it and just use -# the further ci steps that already check linting and file casing for the entire repo. -checks-reporter-with-killswitch "Quick commit checks" \ - "$(dirname "${0}")/commit_check_runner.sh" diff --git a/.ci/teamcity/checks/commit_check_runner.sh b/.ci/teamcity/checks/commit_check_runner.sh deleted file mode 100755 index f2a4a20568215..0000000000000 --- a/.ci/teamcity/checks/commit_check_runner.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash - -echo "!!!!!!!! ATTENTION !!!!!!!! -That check is intended to provide earlier CI feedback after we remove the automatic install for the local pre-commit hook. -If you want, you can still manually install the pre-commit hook locally by running 'node scripts/register_git_hook locally' -!!!!!!!!!!!!!!!!!!!!!!!!!!! -" - -node scripts/precommit_hook.js --ref HEAD~1..HEAD --max-files 200 --verbose diff --git a/.ci/teamcity/checks/doc_api_changes.sh b/.ci/teamcity/checks/doc_api_changes.sh deleted file mode 100755 index 43b65d4e188ba..0000000000000 --- a/.ci/teamcity/checks/doc_api_changes.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -checks-reporter-with-killswitch "Check Doc API Changes" \ - node scripts/check_published_api_changes diff --git a/.ci/teamcity/checks/eslint.sh b/.ci/teamcity/checks/eslint.sh deleted file mode 100755 index d7282b310f81c..0000000000000 --- a/.ci/teamcity/checks/eslint.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -checks-reporter-with-killswitch "Lint: eslint" \ - node scripts/eslint --no-cache diff --git a/.ci/teamcity/checks/file_casing.sh b/.ci/teamcity/checks/file_casing.sh deleted file mode 100755 index 5c0815bdd9551..0000000000000 --- a/.ci/teamcity/checks/file_casing.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -checks-reporter-with-killswitch "Check File Casing" \ - node scripts/check_file_casing --quiet diff --git a/.ci/teamcity/checks/i18n.sh b/.ci/teamcity/checks/i18n.sh deleted file mode 100755 index 62ea3fbe9b04d..0000000000000 --- a/.ci/teamcity/checks/i18n.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -checks-reporter-with-killswitch "Check i18n" \ - node scripts/i18n_check --ignore-missing diff --git a/.ci/teamcity/checks/jest_configs.sh b/.ci/teamcity/checks/jest_configs.sh deleted file mode 100755 index 6703ffffb5651..0000000000000 --- a/.ci/teamcity/checks/jest_configs.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -checks-reporter-with-killswitch "Check Jest Configs" \ - node scripts/check_jest_configs diff --git a/.ci/teamcity/checks/licenses.sh b/.ci/teamcity/checks/licenses.sh deleted file mode 100755 index 136d281647cc5..0000000000000 --- a/.ci/teamcity/checks/licenses.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -checks-reporter-with-killswitch "Check Licenses" \ - node scripts/check_licenses --dev diff --git a/.ci/teamcity/checks/plugins_with_circular_deps.sh b/.ci/teamcity/checks/plugins_with_circular_deps.sh deleted file mode 100755 index 5acc4b2ae351b..0000000000000 --- a/.ci/teamcity/checks/plugins_with_circular_deps.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -checks-reporter-with-killswitch "Check Plugins With Circular Dependencies" \ - node scripts/find_plugins_with_circular_deps diff --git a/.ci/teamcity/checks/stylelint.sh b/.ci/teamcity/checks/stylelint.sh deleted file mode 100755 index f4e1da5027346..0000000000000 --- a/.ci/teamcity/checks/stylelint.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -checks-reporter-with-killswitch "Lint: stylelint" \ - node scripts/stylelint diff --git a/.ci/teamcity/checks/telemetry.sh b/.ci/teamcity/checks/telemetry.sh deleted file mode 100755 index 034dd6d647ad3..0000000000000 --- a/.ci/teamcity/checks/telemetry.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -checks-reporter-with-killswitch "Check Telemetry Schema" \ - node scripts/telemetry_check diff --git a/.ci/teamcity/checks/test_hardening.sh b/.ci/teamcity/checks/test_hardening.sh deleted file mode 100755 index 5799a0b44133b..0000000000000 --- a/.ci/teamcity/checks/test_hardening.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -checks-reporter-with-killswitch "Test Hardening" \ - node scripts/test_hardening diff --git a/.ci/teamcity/checks/ts_projects.sh b/.ci/teamcity/checks/ts_projects.sh deleted file mode 100755 index 9d1c898090def..0000000000000 --- a/.ci/teamcity/checks/ts_projects.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -checks-reporter-with-killswitch "Check TypeScript Projects" \ - node scripts/check_ts_projects diff --git a/.ci/teamcity/checks/type_check.sh b/.ci/teamcity/checks/type_check.sh deleted file mode 100755 index d465e8f4c52b4..0000000000000 --- a/.ci/teamcity/checks/type_check.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -checks-reporter-with-killswitch "Check Types" \ - node scripts/type_check diff --git a/.ci/teamcity/checks/verify_notice.sh b/.ci/teamcity/checks/verify_notice.sh deleted file mode 100755 index 636dc35555f67..0000000000000 --- a/.ci/teamcity/checks/verify_notice.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -checks-reporter-with-killswitch "Verify NOTICE" \ - node scripts/notice --validate diff --git a/.ci/teamcity/ci_stats.js b/.ci/teamcity/ci_stats.js deleted file mode 100644 index 2953661eca1fd..0000000000000 --- a/.ci/teamcity/ci_stats.js +++ /dev/null @@ -1,59 +0,0 @@ -const https = require('https'); -const token = process.env.CI_STATS_TOKEN; -const host = process.env.CI_STATS_HOST; - -const request = (url, options, data = null) => { - const httpOptions = { - ...options, - headers: { - ...(options.headers || {}), - Authorization: `token ${token}`, - }, - }; - - return new Promise((resolve, reject) => { - console.log(`Calling https://${host}${url}`); - - const req = https.request(`https://${host}${url}`, httpOptions, (res) => { - if (res.statusCode < 200 || res.statusCode >= 300) { - return reject(new Error(`Status Code: ${res.statusCode}`)); - } - - const data = []; - res.on('data', (d) => { - data.push(d); - }) - - res.on('end', () => { - try { - let resp = Buffer.concat(data).toString(); - - try { - if (resp.trim()) { - resp = JSON.parse(resp); - } - } catch (ex) { - console.error(ex); - } - - resolve(resp); - } catch (ex) { - reject(ex); - } - }); - }) - - req.on('error', reject); - - if (data) { - req.write(JSON.stringify(data)); - } - - req.end(); - }); -} - -module.exports = { - get: (url) => request(url, { method: 'GET' }), - post: (url, data) => request(url, { method: 'POST' }, data), -} diff --git a/.ci/teamcity/ci_stats_complete.js b/.ci/teamcity/ci_stats_complete.js deleted file mode 100644 index 0df9329167ff6..0000000000000 --- a/.ci/teamcity/ci_stats_complete.js +++ /dev/null @@ -1,18 +0,0 @@ -const ciStats = require('./ci_stats'); - -// This might be better as an API call in the future. -// Instead, it relies on a separate step setting the BUILD_STATUS env var. BUILD_STATUS is not something provided by TeamCity. -const BUILD_STATUS = process.env.BUILD_STATUS === 'SUCCESS' ? 'SUCCESS' : 'FAILURE'; - -(async () => { - try { - if (process.env.CI_STATS_BUILD_ID) { - await ciStats.post(`/v1/build/_complete?id=${process.env.CI_STATS_BUILD_ID}`, { - result: BUILD_STATUS, - }); - } - } catch (ex) { - console.error(ex); - process.exit(1); - } -})(); diff --git a/.ci/teamcity/default/accessibility.sh b/.ci/teamcity/default/accessibility.sh deleted file mode 100755 index 2868db9d067b8..0000000000000 --- a/.ci/teamcity/default/accessibility.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -export JOB=kibana-default-accessibility -export KIBANA_INSTALL_DIR="$PARENT_DIR/build/kibana-build-default" - -cd "$XPACK_DIR" - -checks-reporter-with-killswitch "X-Pack accessibility tests" \ - node scripts/functional_tests \ - --debug --bail \ - --kibana-install-dir "$KIBANA_INSTALL_DIR" \ - --config test/accessibility/config.ts diff --git a/.ci/teamcity/default/build.sh b/.ci/teamcity/default/build.sh deleted file mode 100755 index 140233f29e6af..0000000000000 --- a/.ci/teamcity/default/build.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -tc_start_block "Build Platform Plugins" -node scripts/build_kibana_platform_plugins \ - --scan-dir "$KIBANA_DIR/test/plugin_functional/plugins" \ - --scan-dir "$KIBANA_DIR/test/common/fixtures/plugins" \ - --scan-dir "$XPACK_DIR/test/plugin_functional/plugins" \ - --scan-dir "$XPACK_DIR/test/functional_with_es_ssl/fixtures/plugins" \ - --scan-dir "$XPACK_DIR/test/alerting_api_integration/plugins" \ - --scan-dir "$XPACK_DIR/test/plugin_api_integration/plugins" \ - --scan-dir "$XPACK_DIR/test/plugin_api_perf/plugins" \ - --scan-dir "$XPACK_DIR/test/licensing_plugin/plugins" \ - --scan-dir "$XPACK_DIR/test/usage_collection/plugins" \ - --verbose -tc_end_block "Build Platform Plugins" - -export KBN_NP_PLUGINS_BUILT=true - -tc_start_block "Build Default Distribution" - -cd "$KIBANA_DIR" -node scripts/build --debug --no-oss -linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" -installDir="$KIBANA_DIR/install/kibana" -mkdir -p "$installDir" -tar -xzf "$linuxBuild" -C "$installDir" --strip=1 - -tc_end_block "Build Default Distribution" diff --git a/.ci/teamcity/default/build_plugins.sh b/.ci/teamcity/default/build_plugins.sh deleted file mode 100755 index 4b87596392239..0000000000000 --- a/.ci/teamcity/default/build_plugins.sh +++ /dev/null @@ -1,21 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -tc_start_block "Build Platform Plugins" -node scripts/build_kibana_platform_plugins \ - --scan-dir "$KIBANA_DIR/test/plugin_functional/plugins" \ - --scan-dir "$KIBANA_DIR/test/common/fixtures/plugins" \ - --scan-dir "$XPACK_DIR/test/plugin_functional/plugins" \ - --scan-dir "$XPACK_DIR/test/functional_with_es_ssl/fixtures/plugins" \ - --scan-dir "$XPACK_DIR/test/alerting_api_integration/plugins" \ - --scan-dir "$XPACK_DIR/test/plugin_api_integration/plugins" \ - --scan-dir "$XPACK_DIR/test/plugin_api_perf/plugins" \ - --scan-dir "$XPACK_DIR/test/licensing_plugin/plugins" \ - --scan-dir "$XPACK_DIR/test/usage_collection/plugins" \ - --verbose -tc_end_block "Build Platform Plugins" - -tc_set_env KBN_NP_PLUGINS_BUILT true diff --git a/.ci/teamcity/default/ci_group.sh b/.ci/teamcity/default/ci_group.sh deleted file mode 100755 index 26c2c563210ed..0000000000000 --- a/.ci/teamcity/default/ci_group.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -export CI_GROUP="$1" -export JOB=kibana-default-ciGroup${CI_GROUP} -export KIBANA_INSTALL_DIR="$PARENT_DIR/build/kibana-build-default" - -cd "$XPACK_DIR" - -checks-reporter-with-killswitch "Default Distro Chrome Functional tests / Group ${CI_GROUP}" \ - node scripts/functional_tests \ - --debug --bail \ - --kibana-install-dir "$KIBANA_INSTALL_DIR" \ - --include-tag "ciGroup$CI_GROUP" diff --git a/.ci/teamcity/default/firefox.sh b/.ci/teamcity/default/firefox.sh deleted file mode 100755 index 5922a72bd5e85..0000000000000 --- a/.ci/teamcity/default/firefox.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -export JOB=kibana-default-firefoxSmoke -export KIBANA_INSTALL_DIR="$PARENT_DIR/build/kibana-build-default" - -cd "$XPACK_DIR" - -checks-reporter-with-killswitch "X-Pack firefox smoke test" \ - node scripts/functional_tests \ - --debug --bail \ - --kibana-install-dir "$KIBANA_INSTALL_DIR" \ - --include-tag "includeFirefox" \ - --config test/functional/config.firefox.js \ - --config test/functional_embedded/config.firefox.ts diff --git a/.ci/teamcity/default/jest.sh b/.ci/teamcity/default/jest.sh deleted file mode 100755 index b900d1b6d6b4e..0000000000000 --- a/.ci/teamcity/default/jest.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -export JOB=kibana-default-jest - -checks-reporter-with-killswitch "Jest Unit Tests" \ - node scripts/jest x-pack --ci --verbose --maxWorkers=5 diff --git a/.ci/teamcity/default/saved_object_field_metrics.sh b/.ci/teamcity/default/saved_object_field_metrics.sh deleted file mode 100755 index f5b57ce3b06eb..0000000000000 --- a/.ci/teamcity/default/saved_object_field_metrics.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -export JOB=kibana-default-savedObjectFieldMetrics -export KIBANA_INSTALL_DIR="$PARENT_DIR/build/kibana-build-default" - -cd "$XPACK_DIR" - -checks-reporter-with-killswitch "Capture Kibana Saved Objects field count metrics" \ - node scripts/functional_tests \ - --debug --bail \ - --kibana-install-dir "$KIBANA_INSTALL_DIR" \ - --config test/saved_objects_field_count/config.ts diff --git a/.ci/teamcity/default/security_solution.sh b/.ci/teamcity/default/security_solution.sh deleted file mode 100755 index 46048f6c82d52..0000000000000 --- a/.ci/teamcity/default/security_solution.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -export JOB=kibana-default-securitySolution -export KIBANA_INSTALL_DIR="$PARENT_DIR/build/kibana-build-default" - -cd "$XPACK_DIR" - -checks-reporter-with-killswitch "Security Solution Cypress Tests" \ - node scripts/functional_tests \ - --debug --bail \ - --kibana-install-dir "$KIBANA_INSTALL_DIR" \ - --config test/security_solution_cypress/cli_config.ts diff --git a/.ci/teamcity/es_snapshots/build.sh b/.ci/teamcity/es_snapshots/build.sh deleted file mode 100755 index f983713e80f4d..0000000000000 --- a/.ci/teamcity/es_snapshots/build.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -cd .. -destination="$(pwd)/es-build" -mkdir -p "$destination" - -cd elasticsearch - -# These turn off automation in the Elasticsearch repo -export BUILD_NUMBER="" -export JENKINS_URL="" -export BUILD_URL="" -export JOB_NAME="" -export NODE_NAME="" - -# Reads the ES_BUILD_JAVA env var out of .ci/java-versions.properties and exports it -export "$(grep '^ES_BUILD_JAVA' .ci/java-versions.properties | xargs)" - -export PATH="$HOME/.java/$ES_BUILD_JAVA/bin:$PATH" -export JAVA_HOME="$HOME/.java/$ES_BUILD_JAVA" - -tc_start_block "Build Elasticsearch" -./gradlew -Dbuild.docker=true assemble --parallel -tc_end_block "Build Elasticsearch" - -tc_start_block "Create distribution archives" -find distribution -type f \( -name 'elasticsearch-*-*-*-*.tar.gz' -o -name 'elasticsearch-*-*-*-*.zip' \) -not -path '*no-jdk*' -not -path '*build-context*' -exec cp {} "$destination" \; -tc_end_block "Create distribution archives" - -ls -alh "$destination" - -tc_start_block "Create docker image archives" -docker images "docker.elastic.co/elasticsearch/elasticsearch" -docker images "docker.elastic.co/elasticsearch/elasticsearch" --format "{{.Tag}}" | xargs -n1 echo 'docker save docker.elastic.co/elasticsearch/elasticsearch:${0} | gzip > ../es-build/elasticsearch-${0}-docker-image.tar.gz' -docker images "docker.elastic.co/elasticsearch/elasticsearch" --format "{{.Tag}}" | xargs -n1 bash -c 'docker save docker.elastic.co/elasticsearch/elasticsearch:${0} | gzip > ../es-build/elasticsearch-${0}-docker-image.tar.gz' -tc_end_block "Create docker image archives" - -cd "$destination" - -find ./* -exec bash -c "shasum -a 512 {} > {}.sha512" \; -ls -alh "$destination" diff --git a/.ci/teamcity/es_snapshots/create_manifest.js b/.ci/teamcity/es_snapshots/create_manifest.js deleted file mode 100644 index 63e54987f788f..0000000000000 --- a/.ci/teamcity/es_snapshots/create_manifest.js +++ /dev/null @@ -1,82 +0,0 @@ -const fs = require('fs'); -const { execSync } = require('child_process'); - -(async () => { - const destination = process.argv[2] || __dirname + '/test'; - - let ES_BRANCH = process.env.ELASTICSEARCH_BRANCH; - let GIT_COMMIT = process.env.ELASTICSEARCH_GIT_COMMIT; - let GIT_COMMIT_SHORT = execSync(`git rev-parse --short '${GIT_COMMIT}'`).toString().trim(); - - let VERSION = ''; - let SNAPSHOT_ID = ''; - let DESTINATION = ''; - - const now = new Date() - - // format: yyyyMMdd-HHmmss - const date = [ - now.getFullYear(), - (now.getMonth()+1).toString().padStart(2, '0'), - now.getDate().toString().padStart(2, '0'), - '-', - now.getHours().toString().padStart(2, '0'), - now.getMinutes().toString().padStart(2, '0'), - now.getSeconds().toString().padStart(2, '0'), - ].join('') - - try { - const files = fs.readdirSync(destination); - const manifestEntries = files - .filter(f => !f.match(/.sha512$/)) - .filter(f => !f.match(/.json$/)) - .map(filename => { - const 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-teamcity/${DESTINATION}/${filename}`, - version: parts[1], - platform: parts[3], - architecture: parts[4].split('.')[0], - license: parts[0] == 'oss' ? 'oss' : 'default', - } - }); - - const manifest = { - id: SNAPSHOT_ID, - bucket: `kibana-ci-es-snapshots-daily-teamcity/${DESTINATION}`.toString(), - branch: ES_BRANCH, - sha: GIT_COMMIT, - sha_short: GIT_COMMIT_SHORT, - version: VERSION, - generated: now.toISOString(), - archives: manifestEntries, - }; - - const manifestJSON = JSON.stringify(manifest, null, 2); - fs.writeFileSync(`${destination}/manifest.json`, manifestJSON); - - execSync(` - set -euo pipefail - cd "${destination}" - gsutil -m cp -r *.* gs://kibana-ci-es-snapshots-daily-teamcity/${DESTINATION} - cp manifest.json manifest-latest.json - gsutil cp manifest-latest.json gs://kibana-ci-es-snapshots-daily-teamcity/${VERSION} - `, { shell: '/bin/bash' }); - - console.log(`##teamcity[setParameter name='env.ES_SNAPSHOT_MANIFEST' value='https://storage.googleapis.com/kibana-ci-es-snapshots-daily-teamcity/${DESTINATION}/manifest.json']`); - console.log(`##teamcity[setParameter name='env.ES_SNAPSHOT_VERSION' value='${VERSION}']`); - console.log(`##teamcity[setParameter name='env.ES_SNAPSHOT_ID' value='${SNAPSHOT_ID}']`); - - console.log(`##teamcity[buildNumber '{build.number}-${VERSION}-${SNAPSHOT_ID}']`); - } catch (ex) { - console.error(ex); - process.exit(1); - } -})(); diff --git a/.ci/teamcity/es_snapshots/promote_manifest.js b/.ci/teamcity/es_snapshots/promote_manifest.js deleted file mode 100644 index bcc79e696d783..0000000000000 --- a/.ci/teamcity/es_snapshots/promote_manifest.js +++ /dev/null @@ -1,53 +0,0 @@ -const fs = require('fs'); -const { execSync } = require('child_process'); - -const BASE_BUCKET_DAILY = 'kibana-ci-es-snapshots-daily-teamcity'; -const BASE_BUCKET_PERMANENT = 'kibana-ci-es-snapshots-daily-teamcity/permanent'; - -(async () => { - try { - const MANIFEST_URL = process.argv[2]; - - if (!MANIFEST_URL) { - throw Error('Manifest URL missing'); - } - - if (!fs.existsSync('snapshot-promotion')) { - fs.mkdirSync('snapshot-promotion'); - } - process.chdir('snapshot-promotion'); - - execSync(`curl '${MANIFEST_URL}' > manifest.json`); - - const manifest = JSON.parse(fs.readFileSync('manifest.json')); - const { id, bucket, version } = manifest; - - console.log(`##teamcity[buildNumber '{build.number}-${version}-${id}']`); - - const manifestPermanent = { - ...manifest, - bucket: bucket.replace(BASE_BUCKET_DAILY, BASE_BUCKET_PERMANENT), - }; - - fs.writeFileSync('manifest-permanent.json', JSON.stringify(manifestPermanent, null, 2)); - - execSync( - ` - set -euo pipefail - - cp manifest.json manifest-latest-verified.json - gsutil cp manifest-latest-verified.json gs://${BASE_BUCKET_DAILY}/${version}/ - - rm manifest.json - cp manifest-permanent.json manifest.json - gsutil -m cp -r gs://${bucket}/* gs://${BASE_BUCKET_PERMANENT}/${version}/ - gsutil cp manifest.json gs://${BASE_BUCKET_PERMANENT}/${version}/ - - `, - { shell: '/bin/bash' } - ); - } catch (ex) { - console.error(ex); - process.exit(1); - } -})(); diff --git a/.ci/teamcity/oss/accessibility.sh b/.ci/teamcity/oss/accessibility.sh deleted file mode 100755 index 09693d7ebdc57..0000000000000 --- a/.ci/teamcity/oss/accessibility.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -export JOB=kibana-oss-accessibility -export KIBANA_INSTALL_DIR="$PARENT_DIR/build/kibana-build-oss" - -checks-reporter-with-killswitch "Kibana accessibility tests" \ - node scripts/functional_tests \ - --debug --bail \ - --kibana-install-dir "$KIBANA_INSTALL_DIR" \ - --config test/accessibility/config.ts diff --git a/.ci/teamcity/oss/api_integration.sh b/.ci/teamcity/oss/api_integration.sh deleted file mode 100755 index 37241bdbdc075..0000000000000 --- a/.ci/teamcity/oss/api_integration.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -export JOB=kibana-oss-api-integration - -checks-reporter-with-killswitch "API Integration Tests" \ - node scripts/functional_tests --config test/api_integration/config.js --bail --debug diff --git a/.ci/teamcity/oss/build.sh b/.ci/teamcity/oss/build.sh deleted file mode 100755 index 3ef14b1663355..0000000000000 --- a/.ci/teamcity/oss/build.sh +++ /dev/null @@ -1,24 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -tc_start_block "Build Platform Plugins" -node scripts/build_kibana_platform_plugins \ - --oss \ - --filter '!alertingExample' \ - --scan-dir "$KIBANA_DIR/test/plugin_functional/plugins" \ - --scan-dir "$KIBANA_DIR/test/interpreter_functional/plugins" \ - --scan-dir "$KIBANA_DIR/test/common/fixtures/plugins" \ - --verbose -tc_end_block "Build Platform Plugins" - -export KBN_NP_PLUGINS_BUILT=true - -tc_start_block "Build OSS Distribution" -node scripts/build --debug --oss - -# Renaming the build directory to a static one, so that we can put a static one in the TeamCity artifact rules -mv build/oss/kibana-*-SNAPSHOT-linux-x86_64 build/oss/kibana-build-oss -tc_end_block "Build OSS Distribution" diff --git a/.ci/teamcity/oss/build_plugins.sh b/.ci/teamcity/oss/build_plugins.sh deleted file mode 100755 index 28e3c9247f1d4..0000000000000 --- a/.ci/teamcity/oss/build_plugins.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -tc_start_block "Build Platform Plugins - OSS" - -node scripts/build_kibana_platform_plugins \ - --oss \ - --filter '!alertingExample' \ - --scan-dir "$KIBANA_DIR/test/plugin_functional/plugins" \ - --scan-dir "$KIBANA_DIR/test/interpreter_functional/plugins" \ - --scan-dir "$KIBANA_DIR/test/common/fixtures/plugins" \ - --verbose -tc_end_block "Build Platform Plugins - OSS" diff --git a/.ci/teamcity/oss/ci_group.sh b/.ci/teamcity/oss/ci_group.sh deleted file mode 100755 index 3b2fb7ea912b7..0000000000000 --- a/.ci/teamcity/oss/ci_group.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -export CI_GROUP="$1" -export JOB="kibana-ciGroup$CI_GROUP" -export KIBANA_INSTALL_DIR="$PARENT_DIR/build/kibana-build-oss" - -checks-reporter-with-killswitch "Functional tests / Group $CI_GROUP" \ - node scripts/functional_tests \ - --debug --bail \ - --kibana-install-dir "$KIBANA_INSTALL_DIR" \ - --include-tag "ciGroup$CI_GROUP" diff --git a/.ci/teamcity/oss/firefox.sh b/.ci/teamcity/oss/firefox.sh deleted file mode 100755 index 5e2a6c17c0052..0000000000000 --- a/.ci/teamcity/oss/firefox.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -export JOB=kibana-firefoxSmoke -export KIBANA_INSTALL_DIR="$PARENT_DIR/build/kibana-build-oss" - -checks-reporter-with-killswitch "Firefox smoke test" \ - node scripts/functional_tests \ - --bail --debug \ - --kibana-install-dir "$KIBANA_INSTALL_DIR" \ - --include-tag "includeFirefox" \ - --config test/functional/config.firefox.js diff --git a/.ci/teamcity/oss/jest.sh b/.ci/teamcity/oss/jest.sh deleted file mode 100755 index 0dee07d00d2be..0000000000000 --- a/.ci/teamcity/oss/jest.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -export JOB=kibana-oss-jest - -checks-reporter-with-killswitch "OSS Jest Unit Tests" \ - node scripts/jest --config jest.config.oss.js --ci --verbose --maxWorkers=5 diff --git a/.ci/teamcity/oss/jest_integration.sh b/.ci/teamcity/oss/jest_integration.sh deleted file mode 100755 index 4c51d2ff29888..0000000000000 --- a/.ci/teamcity/oss/jest_integration.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -export JOB=kibana-oss-jest-integration - -checks-reporter-with-killswitch "OSS Jest Integration Tests" \ - node scripts/jest_integration --ci --verbose diff --git a/.ci/teamcity/oss/plugin_functional.sh b/.ci/teamcity/oss/plugin_functional.sh deleted file mode 100755 index 3570bf01e49c4..0000000000000 --- a/.ci/teamcity/oss/plugin_functional.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -export JOB=kibana-oss-pluginFunctional -export KIBANA_INSTALL_DIR="$PARENT_DIR/build/kibana-build-oss" - -cd test/plugin_functional/plugins/kbn_sample_panel_action -if [[ ! -d "target" ]]; then - yarn build -fi -cd - - -checks-reporter-with-killswitch "Plugin Functional Tests" \ - node scripts/functional_tests \ - --config test/plugin_functional/config.ts \ - --bail \ - --debug - -checks-reporter-with-killswitch "Example Functional Tests" \ - node scripts/functional_tests \ - --config test/examples/config.js \ - --bail \ - --debug - -checks-reporter-with-killswitch "Interpreter Functional Tests" \ - node scripts/functional_tests \ - --config test/interpreter_functional/config.ts \ - --bail \ - --debug \ - --kibana-install-dir "$KIBANA_INSTALL_DIR" diff --git a/.ci/teamcity/oss/server_integration.sh b/.ci/teamcity/oss/server_integration.sh deleted file mode 100755 index ddeef77907c49..0000000000000 --- a/.ci/teamcity/oss/server_integration.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -export JOB=kibana-oss-server-integration -export KIBANA_INSTALL_DIR="$PARENT_DIR/build/kibana-build-oss" - -checks-reporter-with-killswitch "Server integration tests" \ - node scripts/functional_tests \ - --config test/server_integration/http/ssl/config.js \ - --config test/server_integration/http/ssl_redirect/config.js \ - --config test/server_integration/http/platform/config.ts \ - --config test/server_integration/http/ssl_with_p12/config.js \ - --config test/server_integration/http/ssl_with_p12_intermediate/config.js \ - --bail \ - --debug \ - --kibana-install-dir $KIBANA_INSTALL_DIR diff --git a/.ci/teamcity/setup_ci_stats.js b/.ci/teamcity/setup_ci_stats.js deleted file mode 100644 index 882ad119a3db3..0000000000000 --- a/.ci/teamcity/setup_ci_stats.js +++ /dev/null @@ -1,33 +0,0 @@ -const ciStats = require('./ci_stats'); - -(async () => { - try { - const build = await ciStats.post('/v1/build', { - jenkinsJobName: process.env.TEAMCITY_BUILDCONF_NAME, - jenkinsJobId: process.env.TEAMCITY_BUILD_ID, - jenkinsUrl: process.env.TEAMCITY_BUILD_URL, - prId: process.env.GITHUB_PR_NUMBER || null, - }); - - const config = { - apiUrl: `https://${process.env.CI_STATS_HOST}`, - apiToken: process.env.CI_STATS_TOKEN, - buildId: build.id, - }; - - const configJson = JSON.stringify(config); - process.env.KIBANA_CI_STATS_CONFIG = configJson; - console.log(`\n##teamcity[setParameter name='env.KIBANA_CI_STATS_CONFIG' display='hidden' password='true' value='${configJson}']\n`); - console.log(`\n##teamcity[setParameter name='env.CI_STATS_BUILD_ID' value='${build.id}']\n`); - - await ciStats.post(`/v1/git_info?buildId=${build.id}`, { - branch: process.env.GIT_BRANCH.replace(/^(refs\/heads\/|origin\/)/, ''), - commit: process.env.GIT_COMMIT, - targetBranch: process.env.GITHUB_PR_TARGET_BRANCH || null, - mergeBase: process.env.GITHUB_PR_MERGE_BASE || null, - }); - } catch (ex) { - console.error(ex); - process.exit(1); - } -})(); diff --git a/.ci/teamcity/setup_env.sh b/.ci/teamcity/setup_env.sh deleted file mode 100755 index 8f607323102bc..0000000000000 --- a/.ci/teamcity/setup_env.sh +++ /dev/null @@ -1,59 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -source "$(dirname "${0}")/util.sh" - -tc_set_env KIBANA_DIR "$(cd "$(dirname "$0")/../.." && pwd)" -tc_set_env XPACK_DIR "$KIBANA_DIR/x-pack" - -tc_set_env CACHE_DIR "$HOME/.kibana" -tc_set_env PARENT_DIR "$(cd "$KIBANA_DIR/.."; pwd)" -tc_set_env WORKSPACE "${WORKSPACE:-$PARENT_DIR}" - -tc_set_env KIBANA_PKG_BRANCH "$(jq -r .branch "$KIBANA_DIR/package.json")" -tc_set_env KIBANA_BASE_BRANCH "$KIBANA_PKG_BRANCH" - -tc_set_env GECKODRIVER_CDNURL "https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache" -tc_set_env CHROMEDRIVER_CDNURL "https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache" -tc_set_env RE2_DOWNLOAD_MIRROR "https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache" -tc_set_env CYPRESS_DOWNLOAD_MIRROR "https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache/cypress" - -tc_set_env NODE_OPTIONS "${NODE_OPTIONS:-} --max-old-space-size=4096" - -tc_set_env FORCE_COLOR 1 -tc_set_env TEST_BROWSER_HEADLESS 1 - -tc_set_env ELASTIC_APM_ENVIRONMENT ci -tc_set_env ELASTIC_APM_TRANSACTION_SAMPLE_RATE 0.1 - -if [[ "${KIBANA_CI_REPORTER_KEY_BASE64-}" ]]; then - tc_set_env KIBANA_CI_REPORTER_KEY "$(echo "$KIBANA_CI_REPORTER_KEY_BASE64" | base64 -d)" -fi - -if is_pr; then - tc_set_env ELASTIC_APM_ACTIVE false - tc_set_env CHECKS_REPORTER_ACTIVE "${CI_REPORTING_ENABLED-}" - - # These can be removed once we're not supporting Jenkins and TeamCity at the same time - # These are primarily used by github checks reporter and can be configured via /github_checks_api.json - tc_set_env ghprbGhRepository "elastic/kibana" # TODO? - tc_set_env ghprbActualCommit "$GITHUB_PR_TRIGGERED_SHA" - tc_set_env BUILD_URL "$TEAMCITY_BUILD_URL" - - set_git_merge_base -else - tc_set_env ELASTIC_APM_ACTIVE "${CI_REPORTING_ENABLED-}" - tc_set_env CHECKS_REPORTER_ACTIVE false -fi - -tc_set_env FLEET_PACKAGE_REGISTRY_PORT 6104 # Any unused port is fine, used by ingest manager tests -tc_set_env TEST_CORS_SERVER_PORT 6105 # Any unused port is fine, used by ingest manager tests - -if [[ "$(which google-chrome-stable)" || "$(which google-chrome)" ]]; then - echo "Chrome detected, setting DETECT_CHROMEDRIVER_VERSION=true" - tc_set_env DETECT_CHROMEDRIVER_VERSION true - tc_set_env CHROMEDRIVER_FORCE_DOWNLOAD true -else - echo "Chrome not detected, installing default chromedriver binary for the package version" -fi diff --git a/.ci/teamcity/setup_node.sh b/.ci/teamcity/setup_node.sh deleted file mode 100755 index b805a2aa6fe62..0000000000000 --- a/.ci/teamcity/setup_node.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -source "$(dirname "${0}")/util.sh" - -tc_start_block "Setup Node" - -tc_set_env NODE_VERSION "$(cat "$KIBANA_DIR/.node-version")" -tc_set_env NODE_DIR "$CACHE_DIR/node/$NODE_VERSION" -tc_set_env NODE_BIN_DIR "$NODE_DIR/bin" -tc_set_env YARN_OFFLINE_CACHE "$CACHE_DIR/yarn-offline-cache" - -if [[ ! -d "$NODE_DIR" ]]; then - nodeUrl="https://us-central1-elastic-kibana-184716.cloudfunctions.net/kibana-ci-proxy-cache/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.gz" - - echo "node.js v$NODE_VERSION not found at $NODE_DIR, downloading from $nodeUrl" - - mkdir -p "$NODE_DIR" - curl --silent -L "$nodeUrl" | tar -xz -C "$NODE_DIR" --strip-components=1 -else - echo "node.js v$NODE_VERSION already installed to $NODE_DIR, re-using" - ls -alh "$NODE_BIN_DIR" -fi - -tc_set_env PATH "$NODE_BIN_DIR:$PATH" - -tc_end_block "Setup Node" -tc_start_block "Setup Yarn" - -tc_set_env YARN_VERSION "$(node -e "console.log(String(require('./package.json').engines.yarn || '').replace(/^[^\d]+/,''))")" - -if [[ ! $(which yarn) || $(yarn --version) != "$YARN_VERSION" ]]; then - npm install -g "yarn@^${YARN_VERSION}" -fi - -yarn config set yarn-offline-mirror "$YARN_OFFLINE_CACHE" - -tc_set_env YARN_GLOBAL_BIN "$(yarn global bin)" -tc_set_env PATH "$PATH:$YARN_GLOBAL_BIN" - -tc_end_block "Setup Yarn" diff --git a/.ci/teamcity/tests/test_projects.sh b/.ci/teamcity/tests/test_projects.sh deleted file mode 100755 index 06dd3607a6799..0000000000000 --- a/.ci/teamcity/tests/test_projects.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -source "$(dirname "${0}")/../util.sh" - -checks-reporter-with-killswitch "Test Projects" \ - yarn kbn run test --exclude kibana --oss --skip-kibana-plugins --skip-missing diff --git a/.ci/teamcity/util.sh b/.ci/teamcity/util.sh deleted file mode 100755 index f43f84059e25f..0000000000000 --- a/.ci/teamcity/util.sh +++ /dev/null @@ -1,88 +0,0 @@ -#!/usr/bin/env bash - -tc_escape() { - escaped="$1" - - # See https://www.jetbrains.com/help/teamcity/service-messages.html#Escaped+values - - escaped="$(echo "$escaped" | sed -z 's/|/||/g')" - escaped="$(echo "$escaped" | sed -z "s/'/|'/g")" - escaped="$(echo "$escaped" | sed -z 's/\[/|\[/g')" - escaped="$(echo "$escaped" | sed -z 's/\]/|\]/g')" - escaped="$(echo "$escaped" | sed -z 's/\n/|n/g')" - escaped="$(echo "$escaped" | sed -z 's/\r/|r/g')" - - echo "$escaped" -} - -# Sets up an environment variable locally, and also makes it available for subsequent steps in the build -# NOTE: env vars set up this way will be visible in the UI when logged in unless you set them up as blank password parameters ahead of time. -tc_set_env() { - export "$1"="$2" - echo "##teamcity[setParameter name='env.$1' value='$(tc_escape "$2")']" -} - -verify_no_git_changes() { - RED='\033[0;31m' - C_RESET='\033[0m' # Reset color - - "$@" - - GIT_CHANGES="$(git ls-files --modified)" - if [ "$GIT_CHANGES" ]; then - echo -e "\n${RED}ERROR: '$*' caused changes to the following files:${C_RESET}\n" - echo -e "$GIT_CHANGES\n" - exit 1 - fi -} - -tc_start_block() { - echo "##teamcity[blockOpened name='$1']" -} - -tc_end_block() { - echo "##teamcity[blockClosed name='$1']" -} - -checks-reporter-with-killswitch() { - if [ "$CHECKS_REPORTER_ACTIVE" == "true" ] ; then - yarn run github-checks-reporter "$@" - else - arguments=("$@"); - "${arguments[@]:1}"; - fi -} - -is_pr() { - [[ "${GITHUB_PR_NUMBER-}" ]] && return - false -} - -# This function is specifcally for retrying test runner steps one time -# A different solution should be used for retrying general steps (e.g. bootstrap) -tc_retry() { - tc_start_block "Retryable Step - Attempt #1" - "$@" || { - tc_end_block "Retryable Step - Attempt #1" - tc_start_block "Retryable Step - Attempt #2" - >&2 echo "First attempt failed. Retrying $*" - if "$@"; then - echo 'Second attempt successful' - echo "##teamcity[buildStatus status='SUCCESS' text='{build.status.text} with a flaky failure']" - echo "##teamcity[setParameter name='elastic.build.flaky' value='true']" - tc_end_block "Retryable Step - Attempt #2" - else - status="$?" - tc_end_block "Retryable Step - Attempt #2" - return "$status" - fi - } - tc_end_block "Retryable Step - Attempt #1" -} - -set_git_merge_base() { - if [[ "${GITHUB_PR_TARGET_BRANCH-}" ]]; then - git fetch origin "$GITHUB_PR_TARGET_BRANCH" - tc_set_env GITHUB_PR_MERGE_BASE "$(git merge-base HEAD FETCH_HEAD)" - fi -} diff --git a/.eslintignore b/.eslintignore index 5d25f3a78c1ec..2d169c45214fe 100644 --- a/.eslintignore +++ b/.eslintignore @@ -46,4 +46,4 @@ snapshots.js /packages/kbn-monaco/src/painless/antlr # Bazel -/bazel-* +/bazel diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 9e31bd31b4037..3884f975c813d 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -149,8 +149,6 @@ /src/cli/keystore/ @elastic/kibana-operations /src/legacy/server/warnings/ @elastic/kibana-operations /.ci/es-snapshots/ @elastic/kibana-operations -/.ci/teamcity/ @elastic/kibana-operations -/.teamcity/ @elastic/kibana-operations /vars/ @elastic/kibana-operations #CC# /packages/kbn-expect/ @elastic/kibana-operations diff --git a/.gitignore b/.gitignore index 2d7dd52e3ef9e..c7d7e37732ca0 100644 --- a/.gitignore +++ b/.gitignore @@ -78,5 +78,5 @@ report.asciidoc .yarn-local-mirror # Bazel -/bazel-* +/bazel /.bazelrc.user diff --git a/.teamcity/.editorconfig b/.teamcity/.editorconfig deleted file mode 100644 index db789a8c72de1..0000000000000 --- a/.teamcity/.editorconfig +++ /dev/null @@ -1,4 +0,0 @@ -[*.{kt,kts}] -disabled_rules=no-wildcard-imports -indent_size=2 -kotlin_imports_layout=idea diff --git a/.teamcity/Kibana.png b/.teamcity/Kibana.png deleted file mode 100644 index c8f78f4575965..0000000000000 Binary files a/.teamcity/Kibana.png and /dev/null differ diff --git a/.teamcity/README.md b/.teamcity/README.md deleted file mode 100644 index 77c0bc5bc4cd3..0000000000000 --- a/.teamcity/README.md +++ /dev/null @@ -1,156 +0,0 @@ -# Kibana TeamCity - -## Implemented so far - -- Project configuration with ability to provide configuration values that are unique per TeamCity instance (e.g. dev vs prod) -- Read-only configuration (no editing through the UI) -- Secrets stored in TeamCity outside of source control -- Setting secret environment variables (they get filtered from console if output on accident) -- GCP agent configurations - - One-time use agents - - Multiple agents configured, of different sizes (cpu, memory) - - Require specific agents per build configuration -- Unit testable DSL code -- Build artifact generation and consumption -- DSL Extensions of various kinds to easily share common configuration between build configurations in the same repo -- Barebones Slack notifications via plugin -- Dynamically creating environment variables / secrets at runtime for subsequent steps -- "Baseline CI" job that runs a subset of CI for every commit -- "Hourly CI" job that runs full CI hourly, if changes are detected. Re-uses builds that ran during "Baseline CI" for same commit -- Performance monitoring enabled for all jobs -- Jobs with multiple VCS roots (Kibana + Elasticsearch) -- GCS uploading using service account key file and gsutil -- Job that has a version string as an "output", rather than an artifact/file, with consumption in a different job -- Clone a list of jobs and modify dependencies/configuration for a second pipeline -- Promote/deploy a built artifact through the UI by selecting previously built artifact (or automatically build a new one and deploy if successful) -- Custom Build IDs using service messages - -## Pull Requests - -The `Pull Request` feature in TeamCity: - -- Automatically discovers pull request branches in GitHub - - Option to filter by contributor type (members of same org, org+external contributor, everyone) - - Option to filter by target branch (e.g. only discover Pull Requests targeting master) - - Works by essentially modifying the VCS root branch spec (so you should NOT add anything related to PRs to branch spec if you are using this) - - Draft PRs do get discovered -- Adds some Pull Request information to build overview pages -- Adds a few parameters available to build configurations: - - teamcity.pullRequest.number - - teamcity.pullRequest.title - - teamcity.pullRequest.source.branch - - teamcity.pullRequest.target.branch - - (Notice that source owner is not available - there's no information for forks) -- Requires a token for API interaction - -That's it. There's no interaction with labels/comments/etc. Triggering is handled via the standard triggering options. - -So, if you only want to: - -- Build on new commit (e.g. not via comment) or via the TeamCity UI -- Start builds for users not covered by the filter options using the TeamCity UI - -The Pull Request feature may be enough to cover your needs. Otherwise, you'll need something additional (an external bot, or a new teamcity plugin, etc). - -### Other PR notes - -- TeamCity doesn't have the ability to cancel currently-running builds when a new commit is pushed -- TeamCity does not add fork information (e.g. the owner) to build configuration parameters -- Builds CAN be triggered for branches not yet discovered - - You can turn off discovery altogether, and a branch will still be build-able. When triggered externally, it will show up in the UI and build. - -How to [trigger a build via API](https://www.jetbrains.com/help/teamcity/rest-api-reference.html#Triggering+a+Build): - -``` -POST https://teamcity-server/app/rest/buildQueue - - - - -``` - -and with additional properties: - -``` - - - - - - - -``` - -## Kibana Builds - -### Baseline CI - -- Generates baseline metrics needed for PR comparisons -- Only runs OSS and default builds, and generates default saved object field metrics -- Runs for each commit (each build should build a single commit) - -### Full CI - -- Runs everything in CI - all tests and builds -- Re-uses builds from Baseline CI if they are finished or in-progress -- Not generally triggered directly, is triggered by other jobs - -### Hourly CI - -- Triggers every hour and groups up all changes since the last run -- Runs whatever is in `Full CI` - -### Pull Request CI - -- Kibana TeamCity PR bot triggers this build for PRs (new commits, trigger comments) -- Sets many PR related parameters/env vars, then runs `Full CI` - -![Diagram](Kibana.png) - -### ES Snapshot Verification - -Build Configurations: - -- Build Snapshot -- Test Builds (e.g. OSS CI Group 1, Default CI Group 3, etc) -- Verify Snapshot -- Promote Snapshot -- Immediately Promote Snapshot - -Desires: - -- Build ES snapshot on a daily basis, run E2E tests against it, promote when successful -- Ability to easily promote old builds that have been verified -- Ability to run verification without promoting it - -#### Build Snapshot - -- checks out both Kibana and ES codebases -- builds ES artifacts -- uses scripts from Kibana repo to create JSON manifest and assemble snapshot files -- uploads artifacts to GCS -- sets parameters via service message that contains the snapshot URL, ID, version so they can be consumed by downstream jobs -- triggers on timer, once per day - -#### Test Builds - -- builds are clones of all "essential ci" functional and integration tests with irrelevant features disabled - - they are clones because runs of this build and runs of the essential ci versions for the same commit hash mean different things -- snapshot dependency on `Build Elasticsearch Snapshot` is added to clones -- set `env.ES_SNAPSHOT_MANIFEST` = `dep..ES_SNAPSHOT_MANIFEST` to "consume" the built artifact - -#### Verify Snapshot - -- composite build that contains all of the cloned test builds - -#### Promote Snapshot - -- snapshot dependency on `Build Snapshot` and `Verify Snapshot` -- uses scripts from Kibana repo to promote elasticsearch snapshot from `Build Snapshot` by updating manifest files in GCS -- triggers whenever a build of `Verify Snapshot` completes successfully - -#### Immediately Promote Snapshot - -- snapshot dependency only on `Build Snapshot` -- same as `Promote Snapshot` but skips testing -- can only be triggered manually diff --git a/.teamcity/pom.xml b/.teamcity/pom.xml deleted file mode 100644 index 6068d34e78099..0000000000000 --- a/.teamcity/pom.xml +++ /dev/null @@ -1,134 +0,0 @@ - - - - - 4.0.0 - Kibana Teamcity Config DSL Script - org.elastic.kibana - kibana-teamcity-dsl - 1.0-SNAPSHOT - - - org.jetbrains.teamcity - configs-dsl-kotlin-parent - 1.0-SNAPSHOT - - - - - jetbrains-all - https://download.jetbrains.com/teamcity-repository - - true - - - - teamcity-server - https://ci.elastic.dev/app/dsl-plugins-repository - - true - - - - teamcity - https://artifactory.elstc.co/artifactory/teamcity - - true - always - - - - - - - JetBrains - https://download.jetbrains.com/teamcity-repository - - - teamcity - https://artifactory.elstc.co/artifactory/teamcity - - - - - tests - src - - - kotlin-maven-plugin - org.jetbrains.kotlin - ${kotlin.version} - - - - compile - process-sources - - compile - - - - test-compile - process-test-sources - - test-compile - - - - - - org.jetbrains.teamcity - teamcity-configs-maven-plugin - ${teamcity.dsl.version} - - kotlin - target/generated-configs - - - - - - - - org.jetbrains.teamcity - configs-dsl-kotlin - ${teamcity.dsl.version} - compile - - - org.jetbrains.teamcity - configs-dsl-kotlin-plugins - 1.0-SNAPSHOT - pom - compile - - - org.jetbrains.kotlin - kotlin-stdlib-jdk8 - ${kotlin.version} - compile - - - org.jetbrains.kotlin - kotlin-script-runtime - ${kotlin.version} - compile - - - junit - junit - 4.13 - - - co.elastic.teamcity - teamcity-common - 1.0.0-SNAPSHOT - - - diff --git a/.teamcity/settings.kts b/.teamcity/settings.kts deleted file mode 100644 index 28108d019327b..0000000000000 --- a/.teamcity/settings.kts +++ /dev/null @@ -1,12 +0,0 @@ -import jetbrains.buildServer.configs.kotlin.v2019_2.* -import projects.Kibana -import projects.KibanaConfiguration - -version = "2020.2" - -val config = KibanaConfiguration { - agentNetwork = DslContext.getParameter("agentNetwork", "teamcity") - agentSubnet = DslContext.getParameter("agentSubnet", "teamcity") -} - -project(Kibana(config)) diff --git a/.teamcity/src/Agents.kt b/.teamcity/src/Agents.kt deleted file mode 100644 index a550fb9e3d375..0000000000000 --- a/.teamcity/src/Agents.kt +++ /dev/null @@ -1,30 +0,0 @@ -import co.elastic.teamcity.common.GoogleCloudAgent -import co.elastic.teamcity.common.GoogleCloudAgentDiskType -import co.elastic.teamcity.common.GoogleCloudProfile - -private val sizes = listOf("2", "4", "8", "16") - -val StandardAgents = sizes.map { size -> size to GoogleCloudAgent { - sourceImageFamily = "elastic-kibana-ci-ubuntu-1804-lts" - agentPrefix = "kibana-standard-$size-" - machineType = "n2-standard-$size" - diskSizeGb = 75 - diskType = GoogleCloudAgentDiskType.SSD - maxInstances = 750 -} }.toMap() - -val BuildAgent = GoogleCloudAgent { - sourceImageFamily = "elastic-kibana-ci-ubuntu-1804-lts" - agentPrefix = "kibana-c2-16-" - machineType = "c2-standard-16" - diskSizeGb = 250 - diskType = GoogleCloudAgentDiskType.SSD - maxInstances = 200 -} - -val CloudProfile = GoogleCloudProfile { - accessKeyId = "447fdd4d-7129-46b7-9822-2e57658c7422" - - agents(StandardAgents) - agent(BuildAgent) -} diff --git a/.teamcity/src/Common.kt b/.teamcity/src/Common.kt deleted file mode 100644 index de3f96a5c790f..0000000000000 --- a/.teamcity/src/Common.kt +++ /dev/null @@ -1,35 +0,0 @@ -import jetbrains.buildServer.configs.kotlin.v2019_2.DslContext - -// If set to true, github check/commit status will be reported, failed-test-reporter will run, etc. -const val ENABLE_REPORTING = false - -// If set to false, jobs with triggers (scheduled, on commit, etc) will be paused -const val ENABLE_TRIGGERS = true - -fun getProjectBranch(): String { - return DslContext.projectName -} - -fun getCorrespondingESBranch(): String { - return getProjectBranch().replace("_teamcity", "") -} - -fun areTriggersEnabled(): Boolean { - return ENABLE_TRIGGERS; -} - -fun isReportingEnabled(): Boolean { - return ENABLE_REPORTING; -} - -// master and 7.x get committed to so often, we only want to run full CI for them hourly -// but for other branches, we can run daily and on merge -fun isHourlyOnlyBranch(): Boolean { - val branch = getProjectBranch() - - return branch == "master" || branch.matches("""^[0-9]+\.x$""".toRegex()) -} - -fun makeSafeId(id: String): String { - return id.replace(Regex("[^a-zA-Z0-9_]"), "_") -} diff --git a/.teamcity/src/Extensions.kt b/.teamcity/src/Extensions.kt deleted file mode 100644 index 0a8abf4a149cf..0000000000000 --- a/.teamcity/src/Extensions.kt +++ /dev/null @@ -1,137 +0,0 @@ -import co.elastic.teamcity.common.requireAgent -import jetbrains.buildServer.configs.kotlin.v2019_2.* -import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.ScriptBuildStep -import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script - -fun BuildFeatures.junit(dirs: String = "target/**/TEST-*.xml") { - feature { - type = "xml-report-plugin" - param("xmlReportParsing.reportType", "junit") - param("xmlReportParsing.reportDirs", dirs) - } -} - -fun BuildType.kibanaAgent(size: String) { - requireAgent(StandardAgents[size]!!) -} - -fun BuildType.kibanaAgent(size: Int) { - kibanaAgent(size.toString()) -} - -val testArtifactRules = """ - target/kibana-* - target/test-metrics/* - target/kibana-security-solution/**/*.png - target/junit/**/* - target/test-suites-ci-plan.json - test/**/screenshots/session/*.png - test/**/screenshots/failure/*.png - test/**/screenshots/diff/*.png - test/functional/failure_debug/html/*.html - x-pack/test/**/screenshots/session/*.png - x-pack/test/**/screenshots/failure/*.png - x-pack/test/**/screenshots/diff/*.png - x-pack/test/functional/failure_debug/html/*.html - x-pack/test/functional/apps/reporting/reports/session/*.pdf - """.trimIndent() - -fun BuildType.addTestSettings() { - artifactRules += "\n" + testArtifactRules - steps { - if(isReportingEnabled()) { - failedTestReporter() - } - } - features { - junit() - } -} - -fun BuildType.addSlackNotifications(to: String = "#kibana-teamcity-testing") { - params { - param("elastic.slack.enabled", isReportingEnabled().toString()) - param("elastic.slack.channels", to) - } -} - -fun BuildType.dependsOn(buildType: BuildType, init: SnapshotDependency.() -> Unit = {}) { - dependencies { - snapshot(buildType) { - reuseBuilds = ReuseBuilds.SUCCESSFUL - onDependencyCancel = FailureAction.ADD_PROBLEM - onDependencyFailure = FailureAction.ADD_PROBLEM - synchronizeRevisions = true - init() - } - } -} - -fun BuildType.dependsOn(vararg buildTypes: BuildType, init: SnapshotDependency.() -> Unit = {}) { - buildTypes.forEach { dependsOn(it, init) } -} - -fun BuildSteps.failedTestReporter(init: ScriptBuildStep.() -> Unit = {}) { - script { - name = "Failed Test Reporter" - scriptContent = - """ - #!/bin/bash - node scripts/report_failed_tests - """.trimIndent() - executionMode = BuildStep.ExecutionMode.RUN_ON_FAILURE - init() - } -} - -// Note: This is currently only used for tests and has a retry in it for flaky tests. -// The retry should be refactored if runbld is ever needed for other tasks. -fun BuildSteps.runbld(stepName: String, script: String) { - script { - name = stepName - - // The indentation for this string is like this to ensure 100% that the RUNBLD-SCRIPT heredoc termination will not have spaces at the beginning - scriptContent = -"""#!/bin/bash - -set -euo pipefail - -source .ci/teamcity/util.sh - -branchName="${'$'}GIT_BRANCH" -branchName="${'$'}{branchName#refs\/heads\/}" - -if [[ "${'$'}{GITHUB_PR_NUMBER-}" ]]; then - branchName=pull-request -fi - -project=kibana -if [[ "${'$'}{ES_SNAPSHOT_MANIFEST-}" ]]; then - project=kibana-es-snapshot-verify -fi - -# These parameters are only for runbld reporting -export JENKINS_HOME="${'$'}HOME" -export BUILD_URL="%teamcity.serverUrl%/build/%teamcity.build.id%" -export branch_specifier=${'$'}branchName -export NODE_LABELS='teamcity' -export BUILD_NUMBER="%build.number%" -export EXECUTOR_NUMBER='' -export NODE_NAME='' - -export OLD_PATH="${'$'}PATH" - -file=${'$'}(mktemp) - -( -cat < ${'$'}file - -tc_retry /usr/local/bin/runbld -d "${'$'}(pwd)" --job-name="elastic+${'$'}project+${'$'}branchName" ${'$'}file -""" - } -} diff --git a/.teamcity/src/builds/BaselineCi.kt b/.teamcity/src/builds/BaselineCi.kt deleted file mode 100644 index de94e292bd63b..0000000000000 --- a/.teamcity/src/builds/BaselineCi.kt +++ /dev/null @@ -1,40 +0,0 @@ -package builds - -import addSlackNotifications -import areTriggersEnabled -import builds.default.DefaultBuild -import builds.default.DefaultSavedObjectFieldMetrics -import builds.oss.OssBuild -import dependsOn -import getProjectBranch -import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType -import jetbrains.buildServer.configs.kotlin.v2019_2.FailureAction -import jetbrains.buildServer.configs.kotlin.v2019_2.triggers.vcs -import templates.KibanaTemplate - -object BaselineCi : BuildType({ - id("Baseline_CI") - name = "Baseline CI" - description = "Runs builds, saved object field metrics for every commit" - type = Type.COMPOSITE - paused = !areTriggersEnabled() - - templates(KibanaTemplate) - - triggers { - vcs { - branchFilter = "refs/heads/${getProjectBranch()}" - perCheckinTriggering = areTriggersEnabled() - } - } - - dependsOn( - OssBuild, - DefaultBuild, - DefaultSavedObjectFieldMetrics - ) { - onDependencyCancel = FailureAction.ADD_PROBLEM - } - - addSlackNotifications() -}) diff --git a/.teamcity/src/builds/Checks.kt b/.teamcity/src/builds/Checks.kt deleted file mode 100644 index 37336316c4c91..0000000000000 --- a/.teamcity/src/builds/Checks.kt +++ /dev/null @@ -1,39 +0,0 @@ -package builds - -import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType -import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script -import kibanaAgent - -object Checks : BuildType({ - name = "Checks" - description = "Executes Various Checks" - - kibanaAgent(4) - - val checkScripts = mapOf( - "Quick Commit Checks" to ".ci/teamcity/checks/commit.sh", - "Check Telemetry Schema" to ".ci/teamcity/checks/telemetry.sh", - "Check TypeScript Projects" to ".ci/teamcity/checks/ts_projects.sh", - "Check File Casing" to ".ci/teamcity/checks/file_casing.sh", - "Check Licenses" to ".ci/teamcity/checks/licenses.sh", - "Verify NOTICE" to ".ci/teamcity/checks/verify_notice.sh", - "Check Types" to ".ci/teamcity/checks/type_check.sh", - "Check Jest Configs" to ".ci/teamcity/checks/jest_configs.sh", - "Check Doc API Changes" to ".ci/teamcity/checks/doc_api_changes.sh", - "Check Bundle Limits" to ".ci/teamcity/checks/bundle_limits.sh", - "Check i18n" to ".ci/teamcity/checks/i18n.sh", - "Check Plugins With Circular Dependencies" to ".ci/teamcity/checks/plugins_with_circular_deps.sh" - ) - - steps { - for (checkScript in checkScripts) { - script { - name = checkScript.key - scriptContent = """ - #!/bin/bash - ${checkScript.value} - """.trimIndent() - } - } - } -}) diff --git a/.teamcity/src/builds/DailyCi.kt b/.teamcity/src/builds/DailyCi.kt deleted file mode 100644 index 9a8f25f5ba014..0000000000000 --- a/.teamcity/src/builds/DailyCi.kt +++ /dev/null @@ -1,37 +0,0 @@ -package builds - -import addSlackNotifications -import areTriggersEnabled -import dependsOn -import getProjectBranch -import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType -import jetbrains.buildServer.configs.kotlin.v2019_2.FailureAction -import jetbrains.buildServer.configs.kotlin.v2019_2.triggers.schedule - -object DailyCi : BuildType({ - id("Daily_CI") - name = "Daily CI" - description = "Runs everything in CI, daily" - type = Type.COMPOSITE - paused = !areTriggersEnabled() - - triggers { - schedule { - schedulingPolicy = cron { - hours = "0" - minutes = "0" - } - branchFilter = "refs/heads/${getProjectBranch()}" - triggerBuild = always() - withPendingChangesOnly = false - } - } - - dependsOn( - FullCi - ) { - onDependencyCancel = FailureAction.ADD_PROBLEM - } - - addSlackNotifications() -}) diff --git a/.teamcity/src/builds/FullCi.kt b/.teamcity/src/builds/FullCi.kt deleted file mode 100644 index 7f19304428d7e..0000000000000 --- a/.teamcity/src/builds/FullCi.kt +++ /dev/null @@ -1,30 +0,0 @@ -package builds - -import builds.default.* -import builds.oss.* -import builds.test.AllTests -import dependsOn -import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType - -object FullCi : BuildType({ - id("Full_CI") - name = "Full CI" - description = "Runs everything in CI. For tracked branches and PRs." - type = Type.COMPOSITE - - dependsOn( - Lint, - Checks, - AllTests, - OssBuild, - OssAccessibility, - OssPluginFunctional, - OssCiGroups, - OssFirefox, - DefaultBuild, - DefaultCiGroups, - DefaultFirefox, - DefaultAccessibility, - DefaultSecuritySolution - ) -}) diff --git a/.teamcity/src/builds/HourlyCi.kt b/.teamcity/src/builds/HourlyCi.kt deleted file mode 100644 index f50a0e9037758..0000000000000 --- a/.teamcity/src/builds/HourlyCi.kt +++ /dev/null @@ -1,37 +0,0 @@ -package builds - -import addSlackNotifications -import areTriggersEnabled -import dependsOn -import getProjectBranch -import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType -import jetbrains.buildServer.configs.kotlin.v2019_2.FailureAction -import jetbrains.buildServer.configs.kotlin.v2019_2.triggers.schedule - -object HourlyCi : BuildType({ - id("Hourly_CI") - name = "Hourly CI" - description = "Runs everything in CI, hourly" - type = Type.COMPOSITE - paused = !areTriggersEnabled() - - triggers { - schedule { - schedulingPolicy = cron { - hours = "*" - minutes = "0" - } - branchFilter = "refs/heads/${getProjectBranch()}" - triggerBuild = always() - withPendingChangesOnly = true - } - } - - dependsOn( - FullCi - ) { - onDependencyCancel = FailureAction.ADD_PROBLEM - } - - addSlackNotifications() -}) diff --git a/.teamcity/src/builds/Lint.kt b/.teamcity/src/builds/Lint.kt deleted file mode 100644 index 4a4bb8651a7c0..0000000000000 --- a/.teamcity/src/builds/Lint.kt +++ /dev/null @@ -1,33 +0,0 @@ -package builds - -import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType -import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script -import kibanaAgent - -object Lint : BuildType({ - name = "Lint" - description = "Executes Linting, such as eslint and stylelint" - - kibanaAgent(2) - - steps { - script { - name = "Stylelint" - - scriptContent = - """ - #!/bin/bash - ./.ci/teamcity/checks/stylelint.sh - """.trimIndent() - } - - script { - name = "ESLint" - scriptContent = - """ - #!/bin/bash - ./.ci/teamcity/checks/eslint.sh - """.trimIndent() - } - } -}) diff --git a/.teamcity/src/builds/OnMergeCi.kt b/.teamcity/src/builds/OnMergeCi.kt deleted file mode 100644 index 174b73d53de61..0000000000000 --- a/.teamcity/src/builds/OnMergeCi.kt +++ /dev/null @@ -1,34 +0,0 @@ -package builds - -import addSlackNotifications -import areTriggersEnabled -import dependsOn -import getProjectBranch -import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType -import jetbrains.buildServer.configs.kotlin.v2019_2.FailureAction -import jetbrains.buildServer.configs.kotlin.v2019_2.triggers.vcs - -object OnMergeCi : BuildType({ - id("OnMerge_CI") - name = "On Merge CI" - description = "Runs everything in CI, on each commit" - type = Type.COMPOSITE - paused = !areTriggersEnabled() - - maxRunningBuilds = 1 - - triggers { - vcs { - perCheckinTriggering = false - branchFilter = "refs/heads/${getProjectBranch()}" - } - } - - dependsOn( - FullCi - ) { - onDependencyCancel = FailureAction.ADD_PROBLEM - } - - addSlackNotifications() -}) diff --git a/.teamcity/src/builds/PullRequestCi.kt b/.teamcity/src/builds/PullRequestCi.kt deleted file mode 100644 index 997cf1771cc8d..0000000000000 --- a/.teamcity/src/builds/PullRequestCi.kt +++ /dev/null @@ -1,84 +0,0 @@ -package builds - -import builds.default.DefaultSavedObjectFieldMetrics -import dependsOn -import getProjectBranch -import isReportingEnabled -import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType -import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.commitStatusPublisher -import vcs.Kibana - -object PullRequestCi : BuildType({ - id("Pull_Request") - name = "Pull Request CI" - type = Type.COMPOSITE - - buildNumberPattern = "%build.counter%-%env.GITHUB_PR_OWNER%-%env.GITHUB_PR_BRANCH%" - - vcs { - root(Kibana) - checkoutDir = "kibana" - - branchFilter = "+:pull/*" - excludeDefaultBranchChanges = true - } - - val prAllowedList = listOf( - "brianseeders", - "alexwizp", - "barlowm", - "DziyanaDzeraviankina", - "maryia-lapata", - "renovate[bot]", - "sulemanof", - "VladLasitsa" - ) - - params { - param("elastic.pull_request.enabled", "true") - param("elastic.pull_request.target_branch", getProjectBranch()) - param("elastic.pull_request.allow_org_users", "true") - param("elastic.pull_request.allowed_repo_permissions", "admin,write") - param("elastic.pull_request.allowed_list", prAllowedList.joinToString(",")) - param("elastic.pull_request.cancel_in_progress_builds_on_update", "true") - - // These params should get filled in by the app that triggers builds - param("env.GITHUB_PR_TARGET_BRANCH", "") - param("env.GITHUB_PR_NUMBER", "") - param("env.GITHUB_PR_OWNER", "") - param("env.GITHUB_PR_REPO", "") - param("env.GITHUB_PR_BRANCH", "") - param("env.GITHUB_PR_TRIGGERED_SHA", "") - param("env.GITHUB_PR_LABELS", "") - param("env.GITHUB_PR_TRIGGER_COMMENT", "") - - param("reverse.dep.*.env.GITHUB_PR_TARGET_BRANCH", "") - param("reverse.dep.*.env.GITHUB_PR_NUMBER", "") - param("reverse.dep.*.env.GITHUB_PR_OWNER", "") - param("reverse.dep.*.env.GITHUB_PR_REPO", "") - param("reverse.dep.*.env.GITHUB_PR_BRANCH", "") - param("reverse.dep.*.env.GITHUB_PR_TRIGGERED_SHA", "") - param("reverse.dep.*.env.GITHUB_PR_LABELS", "") - param("reverse.dep.*.env.GITHUB_PR_TRIGGER_COMMENT", "") - } - - features { - if(isReportingEnabled()) { - commitStatusPublisher { - enabled = true - vcsRootExtId = "${Kibana.id}" - publisher = github { - githubUrl = "https://api.github.com" - authType = personalToken { - token = "credentialsJSON:07d22002-12de-4627-91c3-672bdb23b55b" - } - } - } - } - } - - dependsOn( - FullCi, - DefaultSavedObjectFieldMetrics - ) -}) diff --git a/.teamcity/src/builds/default/DefaultAccessibility.kt b/.teamcity/src/builds/default/DefaultAccessibility.kt deleted file mode 100755 index f0a9c60cf3e45..0000000000000 --- a/.teamcity/src/builds/default/DefaultAccessibility.kt +++ /dev/null @@ -1,12 +0,0 @@ -package builds.default - -import runbld - -object DefaultAccessibility : DefaultFunctionalBase({ - id("DefaultAccessibility") - name = "Accessibility" - - steps { - runbld("Default Accessibility", "./.ci/teamcity/default/accessibility.sh") - } -}) diff --git a/.teamcity/src/builds/default/DefaultBuild.kt b/.teamcity/src/builds/default/DefaultBuild.kt deleted file mode 100644 index f4683e6cf0c1a..0000000000000 --- a/.teamcity/src/builds/default/DefaultBuild.kt +++ /dev/null @@ -1,56 +0,0 @@ -package builds.default - -import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType -import jetbrains.buildServer.configs.kotlin.v2019_2.Dependencies -import jetbrains.buildServer.configs.kotlin.v2019_2.FailureAction -import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script - -object DefaultBuild : BuildType({ - name = "Build Default" - description = "Generates Default Build Distribution artifact" - - artifactRules = """ - +:install/kibana/**/* => kibana-default.tar.gz - target/kibana-* - +:src/**/target/public/**/* => kibana-default-plugins.tar.gz!/src/ - +:x-pack/plugins/**/target/public/**/* => kibana-default-plugins.tar.gz!/x-pack/plugins/ - +:x-pack/test/**/target/public/**/* => kibana-default-plugins.tar.gz!/x-pack/test/ - +:examples/**/target/public/**/* => kibana-default-plugins.tar.gz!/examples/ - +:test/**/target/public/**/* => kibana-default-plugins.tar.gz!/test/ - """.trimIndent() - - requirements { - startsWith("teamcity.agent.name", "kibana-c2-16-", "RQ_AGENT_NAME") - } - - steps { - script { - name = "Build Default Distribution" - scriptContent = - """ - #!/bin/bash - ./.ci/teamcity/default/build.sh - """.trimIndent() - } - } -}) - -fun Dependencies.defaultBuild(rules: String = "+:kibana-default.tar.gz!** => ../build/kibana-build-default") { - dependency(DefaultBuild) { - snapshot { - onDependencyFailure = FailureAction.FAIL_TO_START - onDependencyCancel = FailureAction.FAIL_TO_START - } - - artifacts { - artifactRules = rules - } - } -} - -fun Dependencies.defaultBuildWithPlugins() { - defaultBuild(""" - +:kibana-default.tar.gz!** => ../build/kibana-build-default - +:kibana-default-plugins.tar.gz!** - """.trimIndent()) -} diff --git a/.teamcity/src/builds/default/DefaultCiGroup.kt b/.teamcity/src/builds/default/DefaultCiGroup.kt deleted file mode 100755 index 2c3b0d348591e..0000000000000 --- a/.teamcity/src/builds/default/DefaultCiGroup.kt +++ /dev/null @@ -1,19 +0,0 @@ -package builds.default - -import StandardAgents -import co.elastic.teamcity.common.requireAgent -import jetbrains.buildServer.configs.kotlin.v2019_2.* -import runbld - -class DefaultCiGroup(val ciGroup: Int = 0, init: BuildType.() -> Unit = {}) : DefaultFunctionalBase({ - id("DefaultCiGroup_$ciGroup") - name = "CI Group $ciGroup" - - steps { - runbld("Default CI Group $ciGroup", "./.ci/teamcity/default/ci_group.sh $ciGroup") - } - - requireAgent(StandardAgents["4"]!!) - - init() -}) diff --git a/.teamcity/src/builds/default/DefaultCiGroups.kt b/.teamcity/src/builds/default/DefaultCiGroups.kt deleted file mode 100644 index 948e2ab5782f9..0000000000000 --- a/.teamcity/src/builds/default/DefaultCiGroups.kt +++ /dev/null @@ -1,15 +0,0 @@ -package builds.default - -import dependsOn -import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType - -const val DEFAULT_CI_GROUP_COUNT = 13 -val defaultCiGroups = (1..DEFAULT_CI_GROUP_COUNT).map { DefaultCiGroup(it) } - -object DefaultCiGroups : BuildType({ - id("Default_CIGroups_Composite") - name = "CI Groups" - type = Type.COMPOSITE - - dependsOn(*defaultCiGroups.toTypedArray()) -}) diff --git a/.teamcity/src/builds/default/DefaultFirefox.kt b/.teamcity/src/builds/default/DefaultFirefox.kt deleted file mode 100755 index 2429967d24939..0000000000000 --- a/.teamcity/src/builds/default/DefaultFirefox.kt +++ /dev/null @@ -1,12 +0,0 @@ -package builds.default - -import runbld - -object DefaultFirefox : DefaultFunctionalBase({ - id("DefaultFirefox") - name = "Firefox" - - steps { - runbld("Default Firefox", "./.ci/teamcity/default/firefox.sh") - } -}) diff --git a/.teamcity/src/builds/default/DefaultFunctionalBase.kt b/.teamcity/src/builds/default/DefaultFunctionalBase.kt deleted file mode 100644 index dc2f7756efeb5..0000000000000 --- a/.teamcity/src/builds/default/DefaultFunctionalBase.kt +++ /dev/null @@ -1,23 +0,0 @@ -package builds.default - -import StandardAgents -import addTestSettings -import co.elastic.teamcity.common.requireAgent -import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType - -open class DefaultFunctionalBase(init: BuildType.() -> Unit = {}) : BuildType({ - params { - param("env.KBN_NP_PLUGINS_BUILT", "true") - } - - requireAgent(StandardAgents["4"]!!) - - dependencies { - defaultBuildWithPlugins() - } - - init() - - addTestSettings() -}) - diff --git a/.teamcity/src/builds/default/DefaultSavedObjectFieldMetrics.kt b/.teamcity/src/builds/default/DefaultSavedObjectFieldMetrics.kt deleted file mode 100644 index 61505d4757faa..0000000000000 --- a/.teamcity/src/builds/default/DefaultSavedObjectFieldMetrics.kt +++ /dev/null @@ -1,28 +0,0 @@ -package builds.default - -import jetbrains.buildServer.configs.kotlin.v2019_2.* -import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script - -object DefaultSavedObjectFieldMetrics : BuildType({ - id("DefaultSavedObjectFieldMetrics") - name = "Default Saved Object Field Metrics" - - params { - param("env.KBN_NP_PLUGINS_BUILT", "true") - } - - steps { - script { - name = "Default Saved Object Field Metrics" - scriptContent = - """ - #!/bin/bash - ./.ci/teamcity/default/saved_object_field_metrics.sh - """.trimIndent() - } - } - - dependencies { - defaultBuild() - } -}) diff --git a/.teamcity/src/builds/default/DefaultSecuritySolution.kt b/.teamcity/src/builds/default/DefaultSecuritySolution.kt deleted file mode 100755 index 1c3b85257c28a..0000000000000 --- a/.teamcity/src/builds/default/DefaultSecuritySolution.kt +++ /dev/null @@ -1,15 +0,0 @@ -package builds.default - -import addTestSettings -import runbld - -object DefaultSecuritySolution : DefaultFunctionalBase({ - id("DefaultSecuritySolution") - name = "Security Solution" - - steps { - runbld("Default Security Solution", "./.ci/teamcity/default/security_solution.sh") - } - - addTestSettings() -}) diff --git a/.teamcity/src/builds/es_snapshots/Build.kt b/.teamcity/src/builds/es_snapshots/Build.kt deleted file mode 100644 index d0c849ff5f996..0000000000000 --- a/.teamcity/src/builds/es_snapshots/Build.kt +++ /dev/null @@ -1,84 +0,0 @@ -package builds.es_snapshots - -import addSlackNotifications -import jetbrains.buildServer.configs.kotlin.v2019_2.* -import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script -import vcs.Elasticsearch -import vcs.Kibana - -object ESSnapshotBuild : BuildType({ - name = "Build Snapshot" - paused = true - - requirements { - startsWith("teamcity.agent.name", "kibana-c2-16-", "RQ_AGENT_NAME") - } - - vcs { - root(Kibana, "+:. => kibana") - root(Elasticsearch, "+:. => elasticsearch") - checkoutDir = "" - } - - params { - param("env.ELASTICSEARCH_BRANCH", "%vcsroot.${Elasticsearch.id.toString()}.branch%") - param("env.ELASTICSEARCH_GIT_COMMIT", "%build.vcs.number.${Elasticsearch.id.toString()}%") - - param("env.GOOGLE_APPLICATION_CREDENTIALS", "%teamcity.build.workingDir%/gcp-credentials.json") - password("env.GOOGLE_APPLICATION_CREDENTIALS_JSON", "credentialsJSON:6e0acb7c-f89c-4225-84b8-4fc102f1a5ef", display = ParameterDisplay.HIDDEN) - } - - steps { - script { - name = "Setup Environment" - scriptContent = - """ - #!/bin/bash - cd kibana - ./.ci/teamcity/setup_env.sh - """.trimIndent() - } - - script { - name = "Setup Node and Yarn" - scriptContent = - """ - #!/bin/bash - cd kibana - ./.ci/teamcity/setup_node.sh - """.trimIndent() - } - - script { - name = "Build Elasticsearch Distribution" - scriptContent = - """ - #!/bin/bash - cd kibana - ./.ci/teamcity/es_snapshots/build.sh - """.trimIndent() - } - - script { - name = "Setup Google Cloud Credentials" - scriptContent = - """#!/bin/bash - echo "${"$"}GOOGLE_APPLICATION_CREDENTIALS_JSON" > "${"$"}GOOGLE_APPLICATION_CREDENTIALS" - gcloud auth activate-service-account --key-file "${"$"}GOOGLE_APPLICATION_CREDENTIALS" - """.trimIndent() - } - - script { - name = "Create Snapshot Manifest" - scriptContent = - """#!/bin/bash - cd kibana - node ./.ci/teamcity/es_snapshots/create_manifest.js "$(cd ../es-build && pwd)" - """.trimIndent() - } - } - - artifactRules = "+:es-build/**/*.json" - - addSlackNotifications() -}) diff --git a/.teamcity/src/builds/es_snapshots/Promote.kt b/.teamcity/src/builds/es_snapshots/Promote.kt deleted file mode 100644 index 9303439d49f30..0000000000000 --- a/.teamcity/src/builds/es_snapshots/Promote.kt +++ /dev/null @@ -1,87 +0,0 @@ -package builds.es_snapshots - -import addSlackNotifications -import jetbrains.buildServer.configs.kotlin.v2019_2.* -import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script -import jetbrains.buildServer.configs.kotlin.v2019_2.triggers.finishBuildTrigger -import vcs.Kibana - -object ESSnapshotPromote : BuildType({ - name = "Promote Snapshot" - paused = true - type = Type.DEPLOYMENT - - vcs { - root(Kibana, "+:. => kibana") - checkoutDir = "" - } - - params { - param("env.ES_SNAPSHOT_MANIFEST", "${ESSnapshotBuild.depParamRefs["env.ES_SNAPSHOT_MANIFEST"]}") - param("env.GOOGLE_APPLICATION_CREDENTIALS", "%teamcity.build.workingDir%/gcp-credentials.json") - password("env.GOOGLE_APPLICATION_CREDENTIALS_JSON", "credentialsJSON:6e0acb7c-f89c-4225-84b8-4fc102f1a5ef", display = ParameterDisplay.HIDDEN) - } - - triggers { - finishBuildTrigger { - buildType = Verify.id.toString() - successfulOnly = true - } - } - - steps { - script { - name = "Setup Environment" - scriptContent = - """ - #!/bin/bash - cd kibana - ./.ci/teamcity/setup_env.sh - """.trimIndent() - } - - script { - name = "Setup Node and Yarn" - scriptContent = - """ - #!/bin/bash - cd kibana - ./.ci/teamcity/setup_node.sh - """.trimIndent() - } - - script { - name = "Setup Google Cloud Credentials" - scriptContent = - """#!/bin/bash - echo "${"$"}GOOGLE_APPLICATION_CREDENTIALS_JSON" > "${"$"}GOOGLE_APPLICATION_CREDENTIALS" - gcloud auth activate-service-account --key-file "${"$"}GOOGLE_APPLICATION_CREDENTIALS" - """.trimIndent() - } - - script { - name = "Promote Snapshot Manifest" - scriptContent = - """#!/bin/bash - cd kibana - node ./.ci/teamcity/es_snapshots/promote_manifest.js "${"$"}ES_SNAPSHOT_MANIFEST" - """.trimIndent() - } - } - - dependencies { - dependency(ESSnapshotBuild) { - snapshot { } - - // This is just here to allow build selection in the UI, the file isn't actually used - artifacts { - artifactRules = "manifest.json" - } - } - dependency(Verify) { - snapshot { } - } - } - - addSlackNotifications() -}) diff --git a/.teamcity/src/builds/es_snapshots/PromoteImmediate.kt b/.teamcity/src/builds/es_snapshots/PromoteImmediate.kt deleted file mode 100644 index f80a97873b246..0000000000000 --- a/.teamcity/src/builds/es_snapshots/PromoteImmediate.kt +++ /dev/null @@ -1,79 +0,0 @@ -package builds.es_snapshots - -import addSlackNotifications -import jetbrains.buildServer.configs.kotlin.v2019_2.* -import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script -import jetbrains.buildServer.configs.kotlin.v2019_2.triggers.finishBuildTrigger -import vcs.Elasticsearch -import vcs.Kibana - -object ESSnapshotPromoteImmediate : BuildType({ - name = "Immediately Promote Snapshot" - description = "Skip testing and immediately promote the selected snapshot" - paused = true - type = Type.DEPLOYMENT - - vcs { - root(Kibana, "+:. => kibana") - checkoutDir = "" - } - - params { - param("env.ES_SNAPSHOT_MANIFEST", "${ESSnapshotBuild.depParamRefs["env.ES_SNAPSHOT_MANIFEST"]}") - param("env.GOOGLE_APPLICATION_CREDENTIALS", "%teamcity.build.workingDir%/gcp-credentials.json") - password("env.GOOGLE_APPLICATION_CREDENTIALS_JSON", "credentialsJSON:6e0acb7c-f89c-4225-84b8-4fc102f1a5ef", display = ParameterDisplay.HIDDEN) - } - - steps { - script { - name = "Setup Environment" - scriptContent = - """ - #!/bin/bash - cd kibana - ./.ci/teamcity/setup_env.sh - """.trimIndent() - } - - script { - name = "Setup Node and Yarn" - scriptContent = - """ - #!/bin/bash - cd kibana - ./.ci/teamcity/setup_node.sh - """.trimIndent() - } - - script { - name = "Setup Google Cloud Credentials" - scriptContent = - """#!/bin/bash - echo "${"$"}GOOGLE_APPLICATION_CREDENTIALS_JSON" > "${"$"}GOOGLE_APPLICATION_CREDENTIALS" - gcloud auth activate-service-account --key-file "${"$"}GOOGLE_APPLICATION_CREDENTIALS" - """.trimIndent() - } - - script { - name = "Promote Snapshot Manifest" - scriptContent = - """#!/bin/bash - cd kibana - node ./.ci/teamcity/es_snapshots/promote_manifest.js "${"$"}ES_SNAPSHOT_MANIFEST" - """.trimIndent() - } - } - - dependencies { - dependency(ESSnapshotBuild) { - snapshot { } - - // This is just here to allow build selection in the UI, the file isn't actually used - artifacts { - artifactRules = "manifest.json" - } - } - } - - addSlackNotifications() -}) diff --git a/.teamcity/src/builds/es_snapshots/Verify.kt b/.teamcity/src/builds/es_snapshots/Verify.kt deleted file mode 100644 index 4c0307e9eca55..0000000000000 --- a/.teamcity/src/builds/es_snapshots/Verify.kt +++ /dev/null @@ -1,96 +0,0 @@ -package builds.es_snapshots - -import builds.default.DefaultBuild -import builds.default.DefaultSecuritySolution -import builds.default.defaultCiGroups -import builds.oss.OssBuild -import builds.oss.OssPluginFunctional -import builds.oss.ossCiGroups -import builds.oss.OssApiServerIntegration -import builds.test.JestIntegration -import dependsOn -import jetbrains.buildServer.configs.kotlin.v2019_2.* - -val cloneForVerify = { build: BuildType -> - val newBuild = BuildType() - build.copyTo(newBuild) - newBuild.id = AbsoluteId(build.id?.toString() + "_ES_Snapshots") - newBuild.params { - param("env.ES_SNAPSHOT_MANIFEST", "${ESSnapshotBuild.depParamRefs["env.ES_SNAPSHOT_MANIFEST"]}") - } - newBuild.dependencies { - dependency(ESSnapshotBuild) { - snapshot { - onDependencyFailure = FailureAction.FAIL_TO_START - onDependencyCancel = FailureAction.FAIL_TO_START - } - // This is just here to allow us to select a build when manually triggering a build using the UI - artifacts { - artifactRules = "manifest.json" - } - } - } - newBuild.steps.items.removeIf { it.name == "Failed Test Reporter" } - newBuild -} - -val ossBuildsToClone = listOf( - *ossCiGroups.toTypedArray(), - OssPluginFunctional -) - -val ossCloned = ossBuildsToClone.map { cloneForVerify(it) } - -val defaultBuildsToClone = listOf( - *defaultCiGroups.toTypedArray(), - DefaultSecuritySolution -) - -val defaultCloned = defaultBuildsToClone.map { cloneForVerify(it) } - -val integrationsBuildsToClone = listOf( - OssApiServerIntegration, - JestIntegration -) - -val integrationCloned = integrationsBuildsToClone.map { cloneForVerify(it) } - -object OssTests : BuildType({ - id("ES_Snapshots_OSS_Tests_Composite") - name = "OSS Distro Tests" - type = Type.COMPOSITE - - dependsOn(*ossCloned.toTypedArray()) -}) - -object DefaultTests : BuildType({ - id("ES_Snapshots_Default_Tests_Composite") - name = "Default Distro Tests" - type = Type.COMPOSITE - - dependsOn(*defaultCloned.toTypedArray()) -}) - -object IntegrationTests : BuildType({ - id("ES_Snapshots_Integration_Tests_Composite") - name = "Integration Tests" - type = Type.COMPOSITE - - dependsOn(*integrationCloned.toTypedArray()) -}) - -object Verify : BuildType({ - id("ES_Snapshots_Verify_Composite") - name = "Verify Snapshot" - description = "Run all Kibana functional and integration tests using a given Elasticsearch snapshot" - type = Type.COMPOSITE - - dependsOn( - ESSnapshotBuild, - OssBuild, - DefaultBuild, - OssTests, - DefaultTests, - IntegrationTests - ) -}) diff --git a/.teamcity/src/builds/oss/OssAccessibility.kt b/.teamcity/src/builds/oss/OssAccessibility.kt deleted file mode 100644 index 8e4a7acd77b76..0000000000000 --- a/.teamcity/src/builds/oss/OssAccessibility.kt +++ /dev/null @@ -1,13 +0,0 @@ -package builds.oss - -import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script -import runbld - -object OssAccessibility : OssFunctionalBase({ - id("OssAccessibility") - name = "Accessibility" - - steps { - runbld("OSS Accessibility", "./.ci/teamcity/oss/accessibility.sh") - } -}) diff --git a/.teamcity/src/builds/oss/OssApiServerIntegration.kt b/.teamcity/src/builds/oss/OssApiServerIntegration.kt deleted file mode 100644 index a04512fb2aba5..0000000000000 --- a/.teamcity/src/builds/oss/OssApiServerIntegration.kt +++ /dev/null @@ -1,13 +0,0 @@ -package builds.oss - -import runbld - -object OssApiServerIntegration : OssFunctionalBase({ - name = "API/Server Integration" - description = "Executes API and Server Integration Tests" - - steps { - runbld("API Integration", "./.ci/teamcity/oss/api_integration.sh") - runbld("Server Integration", "./.ci/teamcity/oss/server_integration.sh") - } -}) diff --git a/.teamcity/src/builds/oss/OssBuild.kt b/.teamcity/src/builds/oss/OssBuild.kt deleted file mode 100644 index 50fd73c17ba42..0000000000000 --- a/.teamcity/src/builds/oss/OssBuild.kt +++ /dev/null @@ -1,41 +0,0 @@ -package builds.oss - -import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType -import jetbrains.buildServer.configs.kotlin.v2019_2.Dependencies -import jetbrains.buildServer.configs.kotlin.v2019_2.FailureAction -import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script - -object OssBuild : BuildType({ - name = "Build OSS" - description = "Generates OSS Build Distribution artifact" - - requirements { - startsWith("teamcity.agent.name", "kibana-c2-16-", "RQ_AGENT_NAME") - } - - steps { - script { - name = "Build OSS Distribution" - scriptContent = - """ - #!/bin/bash - ./.ci/teamcity/oss/build.sh - """.trimIndent() - } - } - - artifactRules = "+:build/oss/kibana-build-oss/**/* => kibana-oss.tar.gz" -}) - -fun Dependencies.ossBuild(rules: String = "+:kibana-oss.tar.gz!** => ../build/kibana-build-oss") { - dependency(OssBuild) { - snapshot { - onDependencyFailure = FailureAction.FAIL_TO_START - onDependencyCancel = FailureAction.FAIL_TO_START - } - - artifacts { - artifactRules = rules - } - } -} diff --git a/.teamcity/src/builds/oss/OssCiGroup.kt b/.teamcity/src/builds/oss/OssCiGroup.kt deleted file mode 100644 index 1c188cd4c175f..0000000000000 --- a/.teamcity/src/builds/oss/OssCiGroup.kt +++ /dev/null @@ -1,15 +0,0 @@ -package builds.oss - -import jetbrains.buildServer.configs.kotlin.v2019_2.* -import runbld - -class OssCiGroup(val ciGroup: Int, init: BuildType.() -> Unit = {}) : OssFunctionalBase({ - id("OssCiGroup_$ciGroup") - name = "CI Group $ciGroup" - - steps { - runbld("OSS CI Group $ciGroup", "./.ci/teamcity/oss/ci_group.sh $ciGroup") - } - - init() -}) diff --git a/.teamcity/src/builds/oss/OssCiGroups.kt b/.teamcity/src/builds/oss/OssCiGroups.kt deleted file mode 100644 index 931cca2554a24..0000000000000 --- a/.teamcity/src/builds/oss/OssCiGroups.kt +++ /dev/null @@ -1,15 +0,0 @@ -package builds.oss - -import dependsOn -import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType - -const val OSS_CI_GROUP_COUNT = 12 -val ossCiGroups = (1..OSS_CI_GROUP_COUNT).map { OssCiGroup(it) } - -object OssCiGroups : BuildType({ - id("OSS_CIGroups_Composite") - name = "CI Groups" - type = Type.COMPOSITE - - dependsOn(*ossCiGroups.toTypedArray()) -}) diff --git a/.teamcity/src/builds/oss/OssFirefox.kt b/.teamcity/src/builds/oss/OssFirefox.kt deleted file mode 100644 index 2db8314fa44fc..0000000000000 --- a/.teamcity/src/builds/oss/OssFirefox.kt +++ /dev/null @@ -1,12 +0,0 @@ -package builds.oss - -import runbld - -object OssFirefox : OssFunctionalBase({ - id("OssFirefox") - name = "Firefox" - - steps { - runbld("OSS Firefox", "./.ci/teamcity/oss/firefox.sh") - } -}) diff --git a/.teamcity/src/builds/oss/OssFunctionalBase.kt b/.teamcity/src/builds/oss/OssFunctionalBase.kt deleted file mode 100644 index d8189fd358966..0000000000000 --- a/.teamcity/src/builds/oss/OssFunctionalBase.kt +++ /dev/null @@ -1,18 +0,0 @@ -package builds.oss - -import addTestSettings -import jetbrains.buildServer.configs.kotlin.v2019_2.* - -open class OssFunctionalBase(init: BuildType.() -> Unit = {}) : BuildType({ - params { - param("env.KBN_NP_PLUGINS_BUILT", "true") - } - - dependencies { - ossBuild() - } - - init() - - addTestSettings() -}) diff --git a/.teamcity/src/builds/oss/OssPluginFunctional.kt b/.teamcity/src/builds/oss/OssPluginFunctional.kt deleted file mode 100644 index 7fbf863820e4c..0000000000000 --- a/.teamcity/src/builds/oss/OssPluginFunctional.kt +++ /dev/null @@ -1,29 +0,0 @@ -package builds.oss - -import addTestSettings -import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script -import runbld - -object OssPluginFunctional : OssFunctionalBase({ - id("OssPluginFunctional") - name = "Plugin Functional" - - steps { - script { - name = "Build OSS Plugins" - scriptContent = - """ - #!/bin/bash - ./.ci/teamcity/oss/build_plugins.sh - """.trimIndent() - } - - runbld("OSS Plugin Functional", "./.ci/teamcity/oss/plugin_functional.sh") - } - - dependencies { - ossBuild() - } - - addTestSettings() -}) diff --git a/.teamcity/src/builds/test/AllTests.kt b/.teamcity/src/builds/test/AllTests.kt deleted file mode 100644 index 9506d98cbe50e..0000000000000 --- a/.teamcity/src/builds/test/AllTests.kt +++ /dev/null @@ -1,13 +0,0 @@ -package builds.test - -import builds.oss.OssApiServerIntegration -import dependsOn -import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType - -object AllTests : BuildType({ - name = "All Tests" - description = "All Non-Functional Tests" - type = Type.COMPOSITE - - dependsOn(QuickTests, Jest, XPackJest, JestIntegration, OssApiServerIntegration) -}) diff --git a/.teamcity/src/builds/test/Jest.kt b/.teamcity/src/builds/test/Jest.kt deleted file mode 100644 index c33c9c2678ca4..0000000000000 --- a/.teamcity/src/builds/test/Jest.kt +++ /dev/null @@ -1,19 +0,0 @@ -package builds.test - -import addTestSettings -import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType -import kibanaAgent -import runbld - -object Jest : BuildType({ - name = "Jest Unit" - description = "Executes Jest Unit Tests" - - kibanaAgent(8) - - steps { - runbld("Jest Unit", "./.ci/teamcity/oss/jest.sh") - } - - addTestSettings() -}) diff --git a/.teamcity/src/builds/test/JestIntegration.kt b/.teamcity/src/builds/test/JestIntegration.kt deleted file mode 100644 index 7d44e41493b2b..0000000000000 --- a/.teamcity/src/builds/test/JestIntegration.kt +++ /dev/null @@ -1,16 +0,0 @@ -package builds.test - -import addTestSettings -import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType -import runbld - -object JestIntegration : BuildType({ - name = "Jest Integration" - description = "Executes Jest Integration Tests" - - steps { - runbld("Jest Integration", "./.ci/teamcity/oss/jest_integration.sh") - } - - addTestSettings() -}) diff --git a/.teamcity/src/builds/test/QuickTests.kt b/.teamcity/src/builds/test/QuickTests.kt deleted file mode 100644 index 6ea15bf5350e6..0000000000000 --- a/.teamcity/src/builds/test/QuickTests.kt +++ /dev/null @@ -1,26 +0,0 @@ -package builds.test - -import addTestSettings -import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType -import kibanaAgent -import runbld - -object QuickTests : BuildType({ - name = "Quick Tests" - description = "Executes Quick Tests" - - kibanaAgent(2) - - val testScripts = mapOf( - "Test Hardening" to ".ci/teamcity/checks/test_hardening.sh", - "Test Projects" to ".ci/teamcity/tests/test_projects.sh" - ) - - steps { - for (testScript in testScripts) { - runbld(testScript.key, testScript.value) - } - } - - addTestSettings() -}) diff --git a/.teamcity/src/builds/test/XPackJest.kt b/.teamcity/src/builds/test/XPackJest.kt deleted file mode 100644 index 8246b60823ff9..0000000000000 --- a/.teamcity/src/builds/test/XPackJest.kt +++ /dev/null @@ -1,19 +0,0 @@ -package builds.test - -import addTestSettings -import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType -import kibanaAgent -import runbld - -object XPackJest : BuildType({ - name = "X-Pack Jest Unit" - description = "Executes X-Pack Jest Unit Tests" - - kibanaAgent(16) - - steps { - runbld("X-Pack Jest Unit", "./.ci/teamcity/default/jest.sh") - } - - addTestSettings() -}) diff --git a/.teamcity/src/projects/EsSnapshots.kt b/.teamcity/src/projects/EsSnapshots.kt deleted file mode 100644 index a5aa47d5cae48..0000000000000 --- a/.teamcity/src/projects/EsSnapshots.kt +++ /dev/null @@ -1,55 +0,0 @@ -package projects - -import builds.es_snapshots.* -import jetbrains.buildServer.configs.kotlin.v2019_2.* -import templates.KibanaTemplate - -object EsSnapshotsProject : Project({ - id("ES_Snapshots") - name = "ES Snapshots" - - subProject { - id("ES_Snapshot_Tests") - name = "Tests" - - defaultTemplate = KibanaTemplate - - subProject { - id("ES_Snapshot_Tests_OSS") - name = "OSS Distro Tests" - - ossCloned.forEach { - buildType(it) - } - - buildType(OssTests) - } - - subProject { - id("ES_Snapshot_Tests_Default") - name = "Default Distro Tests" - - defaultCloned.forEach { - buildType(it) - } - - buildType(DefaultTests) - } - - subProject { - id("ES_Snapshot_Tests_Integration") - name = "Integration Tests" - - integrationCloned.forEach { - buildType(it) - } - - buildType(IntegrationTests) - } - } - - buildType(ESSnapshotBuild) - buildType(ESSnapshotPromote) - buildType(ESSnapshotPromoteImmediate) - buildType(Verify) -}) diff --git a/.teamcity/src/projects/Kibana.kt b/.teamcity/src/projects/Kibana.kt deleted file mode 100644 index 5cddcf18e067f..0000000000000 --- a/.teamcity/src/projects/Kibana.kt +++ /dev/null @@ -1,155 +0,0 @@ -package projects - -import vcs.Kibana -import builds.* -import builds.default.* -import builds.oss.* -import builds.test.* -import CloudProfile -import co.elastic.teamcity.common.googleCloudProfile -import isHourlyOnlyBranch -import jetbrains.buildServer.configs.kotlin.v2019_2.* -import jetbrains.buildServer.configs.kotlin.v2019_2.projectFeatures.slackConnection -import templates.KibanaTemplate -import templates.DefaultTemplate -import vcs.Elasticsearch - -class KibanaConfiguration() { - var agentNetwork: String = "teamcity" - var agentSubnet: String = "teamcity" - - constructor(init: KibanaConfiguration.() -> Unit) : this() { - init() - } -} - -var kibanaConfiguration = KibanaConfiguration() - -fun Kibana(config: KibanaConfiguration = KibanaConfiguration()) : Project { - kibanaConfiguration = config - - return Project { - params { - param("teamcity.ui.settings.readOnly", "true") - - // https://github.com/JetBrains/teamcity-webhooks - param("teamcity.internal.webhooks.enable", "true") - param("teamcity.internal.webhooks.events", "BUILD_STARTED;BUILD_FINISHED;BUILD_INTERRUPTED;CHANGES_LOADED;BUILD_TYPE_ADDED_TO_QUEUE;BUILD_PROBLEMS_CHANGED") - param("teamcity.internal.webhooks.url", "https://ci-stats.kibana.dev/_teamcity_webhook") - param("teamcity.internal.webhooks.username", "automation") - password("teamcity.internal.webhooks.password", "credentialsJSON:b2ee34c5-fc89-4596-9b47-ecdeb68e4e7a", display = ParameterDisplay.HIDDEN) - } - - vcsRoot(Kibana) - vcsRoot(Elasticsearch) - - template(DefaultTemplate) - template(KibanaTemplate) - - defaultTemplate = DefaultTemplate - - googleCloudProfile(CloudProfile) - - features { - slackConnection { - id = "KIBANA_SLACK" - displayName = "Kibana Slack" - botToken = "credentialsJSON:39eafcfc-97a6-4877-82c1-115f1f10d14b" - clientId = "12985172978.1291178427153" - clientSecret = "credentialsJSON:8b5901fb-fd2c-4e45-8aff-fdd86dc68f67" - } - } - - subProject { - id("CI") - name = "CI" - defaultTemplate = KibanaTemplate - - buildType(Lint) - buildType(Checks) - - subProject { - id("Test") - name = "Test" - - subProject { - id("Jest") - name = "Jest" - - buildType(Jest) - buildType(XPackJest) - buildType(JestIntegration) - } - - buildType(QuickTests) - buildType(AllTests) - } - - subProject { - id("OSS") - name = "OSS Distro" - - buildType(OssBuild) - - subProject { - id("OSS_Functional") - name = "Functional" - - buildType(OssCiGroups) - buildType(OssFirefox) - buildType(OssAccessibility) - buildType(OssPluginFunctional) - buildType(OssApiServerIntegration) - - subProject { - id("CIGroups") - name = "CI Groups" - - ossCiGroups.forEach { buildType(it) } - } - } - } - - subProject { - id("Default") - name = "Default Distro" - - buildType(DefaultBuild) - - subProject { - id("Default_Functional") - name = "Functional" - - buildType(DefaultCiGroups) - buildType(DefaultFirefox) - buildType(DefaultAccessibility) - buildType(DefaultSecuritySolution) - buildType(DefaultSavedObjectFieldMetrics) - - subProject { - id("Default_CIGroups") - name = "CI Groups" - - defaultCiGroups.forEach { buildType(it) } - } - } - } - - buildType(FullCi) - buildType(BaselineCi) - - // master and 7.x get committed to so often, we only want to run full CI for them hourly - // but for other branches, we can run daily and on merge - if (isHourlyOnlyBranch()) { - buildType(HourlyCi) - } else { - buildType(DailyCi) - buildType(OnMergeCi) - } - - buildType(PullRequestCi) - } - - subProject(EsSnapshotsProject) - } -} diff --git a/.teamcity/src/templates/DefaultTemplate.kt b/.teamcity/src/templates/DefaultTemplate.kt deleted file mode 100644 index 1f7f364600e21..0000000000000 --- a/.teamcity/src/templates/DefaultTemplate.kt +++ /dev/null @@ -1,24 +0,0 @@ -package templates - -import StandardAgents -import co.elastic.teamcity.common.requireAgent -import jetbrains.buildServer.configs.kotlin.v2019_2.Template -import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.perfmon - -object DefaultTemplate : Template({ - name = "Default Template" - - requireAgent(StandardAgents["2"]!!) - - params { - param("env.HOME", "/var/lib/jenkins") // TODO once the agent images are sorted out - } - - features { - perfmon { } - } - - failureConditions { - executionTimeoutMin = 120 - } -}) diff --git a/.teamcity/src/templates/KibanaTemplate.kt b/.teamcity/src/templates/KibanaTemplate.kt deleted file mode 100644 index 2e3c151950dbe..0000000000000 --- a/.teamcity/src/templates/KibanaTemplate.kt +++ /dev/null @@ -1,153 +0,0 @@ -package templates - -import StandardAgents -import co.elastic.teamcity.common.requireAgent -import getProjectBranch -import isReportingEnabled -import vcs.Kibana -import jetbrains.buildServer.configs.kotlin.v2019_2.BuildStep -import jetbrains.buildServer.configs.kotlin.v2019_2.ParameterDisplay -import jetbrains.buildServer.configs.kotlin.v2019_2.Template -import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.PullRequests -import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.perfmon -import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.pullRequests -import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.placeholder -import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script - -object KibanaTemplate : Template({ - name = "Kibana Template" - description = "For builds that need to check out kibana and execute against the repo using node" - - vcs { - root(Kibana) - - checkoutDir = "kibana" -// checkoutDir = "/dev/shm/%system.teamcity.buildType.id%/%system.build.number%/kibana" - } - - requireAgent(StandardAgents["2"]!!) - - features { - perfmon { } - pullRequests { - vcsRootExtId = "${Kibana.id}" - provider = github { - authType = token { - token = "credentialsJSON:07d22002-12de-4627-91c3-672bdb23b55b" - } - filterTargetBranch = "refs/heads/${getProjectBranch()}" - filterAuthorRole = PullRequests.GitHubRoleFilter.MEMBER - } - } - } - - failureConditions { - executionTimeoutMin = 160 - testFailure = false - } - - params { - param("env.CI", "true") - param("env.TEAMCITY_CI", "true") - param("env.HOME", "/var/lib/jenkins") // TODO once the agent images are sorted out - - // TODO remove these - param("env.GCS_UPLOAD_PREFIX", "INVALID_PREFIX") - param("env.CI_PARALLEL_PROCESS_NUMBER", "1") - - param("env.TEAMCITY_URL", "%teamcity.serverUrl%") - param("env.TEAMCITY_BUILD_URL", "%teamcity.serverUrl%/build/%teamcity.build.id%") - param("env.TEAMCITY_JOB_ID", "%system.teamcity.buildType.id%") - param("env.TEAMCITY_BUILD_ID", "%build.number%") - param("env.TEAMCITY_BUILD_NUMBER", "%teamcity.build.id%") - - param("env.GIT_BRANCH", "%vcsroot.branch%") - param("env.GIT_COMMIT", "%build.vcs.number%") - param("env.branch_specifier", "%vcsroot.branch%") - - param("env.CI_REPORTING_ENABLED", isReportingEnabled().toString()) - - password("env.KIBANA_CI_STATS_CONFIG", "", display = ParameterDisplay.HIDDEN) - password("env.CI_STATS_TOKEN", "credentialsJSON:ea975068-ca68-4da5-8189-ce90f4286bc0", display = ParameterDisplay.HIDDEN) - password("env.CI_STATS_HOST", "credentialsJSON:933ba93e-4b06-44c1-8724-8c536651f2b6", display = ParameterDisplay.HIDDEN) - - // TODO move these to vault once the configuration is finalized - // password("env.CI_STATS_TOKEN", "%vault:kibana-issues:secret/kibana-issues/dev/kibana_ci_stats!/api_token%", display = ParameterDisplay.HIDDEN) - // password("env.CI_STATS_HOST", "%vault:kibana-issues:secret/kibana-issues/dev/kibana_ci_stats!/api_host%", display = ParameterDisplay.HIDDEN) - - // TODO remove this once we are able to pull it out of vault and put it closer to the things that require it - if(isReportingEnabled()) { - password( - "env.GITHUB_TOKEN", - "credentialsJSON:07d22002-12de-4627-91c3-672bdb23b55b", - display = ParameterDisplay.HIDDEN - ) - password("env.KIBANA_CI_REPORTER_KEY", "", display = ParameterDisplay.HIDDEN) - password( - "env.KIBANA_CI_REPORTER_KEY_BASE64", - "credentialsJSON:86878779-4cf7-4434-82af-5164a1b992fb", - display = ParameterDisplay.HIDDEN - ) - } - } - - steps { - script { - name = "Setup Environment" - scriptContent = - """ - #!/bin/bash - ./.ci/teamcity/setup_env.sh - """.trimIndent() - } - - script { - name = "Setup Node and Yarn" - scriptContent = - """ - #!/bin/bash - ./.ci/teamcity/setup_node.sh - """.trimIndent() - } - - script { - name = "Setup CI Stats" - scriptContent = - """ - #!/bin/bash - node .ci/teamcity/setup_ci_stats.js - """.trimIndent() - } - - script { - name = "Bootstrap" - scriptContent = - """ - #!/bin/bash - ./.ci/teamcity/bootstrap.sh - """.trimIndent() - } - - placeholder {} - - script { - name = "Set Build Status Success" - scriptContent = - """ - #!/bin/bash - echo "##teamcity[setParameter name='env.BUILD_STATUS' value='SUCCESS']" - """.trimIndent() - executionMode = BuildStep.ExecutionMode.RUN_ON_SUCCESS - } - - script { - name = "CI Stats Complete" - scriptContent = - """ - #!/bin/bash - node .ci/teamcity/ci_stats_complete.js - """.trimIndent() - executionMode = BuildStep.ExecutionMode.RUN_ON_FAILURE - } - } -}) diff --git a/.teamcity/src/vcs/Elasticsearch.kt b/.teamcity/src/vcs/Elasticsearch.kt deleted file mode 100644 index 96982b38fb015..0000000000000 --- a/.teamcity/src/vcs/Elasticsearch.kt +++ /dev/null @@ -1,13 +0,0 @@ -package vcs - -import getCorrespondingESBranch -import jetbrains.buildServer.configs.kotlin.v2019_2.vcs.GitVcsRoot -import makeSafeId - -object Elasticsearch : GitVcsRoot({ - id("elasticsearch_${makeSafeId(getCorrespondingESBranch())}") - - name = "elasticsearch / ${getCorrespondingESBranch()}" - url = "https://github.com/elastic/elasticsearch.git" - branch = "refs/heads/${getCorrespondingESBranch()}" -}) diff --git a/.teamcity/src/vcs/Kibana.kt b/.teamcity/src/vcs/Kibana.kt deleted file mode 100644 index d094cabab86b8..0000000000000 --- a/.teamcity/src/vcs/Kibana.kt +++ /dev/null @@ -1,13 +0,0 @@ -package vcs - -import getProjectBranch -import jetbrains.buildServer.configs.kotlin.v2019_2.vcs.GitVcsRoot -import makeSafeId - -object Kibana : GitVcsRoot({ - id("kibana_${makeSafeId(getProjectBranch())}") - - name = "kibana / ${getProjectBranch()}" - url = "https://github.com/elastic/kibana.git" - branch = "refs/heads/${getProjectBranch()}" -}) diff --git a/.teamcity/tests/projects/KibanaTest.kt b/.teamcity/tests/projects/KibanaTest.kt deleted file mode 100644 index 6a1b5a4e9c0f8..0000000000000 --- a/.teamcity/tests/projects/KibanaTest.kt +++ /dev/null @@ -1,31 +0,0 @@ -package projects - -import jetbrains.buildServer.configs.kotlin.v2019_2.AbsoluteId -import jetbrains.buildServer.configs.kotlin.v2019_2.DslContext -import makeSafeId -import org.junit.Assert.* -import org.junit.Test - -val TestConfig = KibanaConfiguration { - agentNetwork = "network" - agentSubnet = "subnet" -} - -class KibanaTest { - @Test - fun test_Default_Configuration_Exists() { - assertNotNull(kibanaConfiguration) - Kibana() - assertEquals("teamcity", kibanaConfiguration.agentNetwork) - } - - @Test - fun test_CloudImages_Exist() { - DslContext.projectId = AbsoluteId("My Project") - val project = Kibana(TestConfig) - - assertTrue(project.features.items.any { - it.type == "CloudImage" && it.params.any { param -> param.name == "network" && param.value == "teamcity" } - }) - } -} diff --git a/docs/apm/images/apm-services-overview.png b/docs/apm/images/apm-services-overview.png index 85b441d47f0c2..3a56d597abfb7 100644 Binary files a/docs/apm/images/apm-services-overview.png and b/docs/apm/images/apm-services-overview.png differ diff --git a/docs/apm/images/apm-traces.png b/docs/apm/images/apm-traces.png index 97e801606c613..ed15423b42c51 100644 Binary files a/docs/apm/images/apm-traces.png and b/docs/apm/images/apm-traces.png differ diff --git a/docs/apm/images/apm-transactions-overview.png b/docs/apm/images/apm-transactions-overview.png index 80fca19ff96cc..1b25668f0fd92 100644 Binary files a/docs/apm/images/apm-transactions-overview.png and b/docs/apm/images/apm-transactions-overview.png differ diff --git a/docs/apm/images/traffic-transactions.png b/docs/apm/images/traffic-transactions.png index 134bc0e6bcb42..ef429740ceee3 100644 Binary files a/docs/apm/images/traffic-transactions.png and b/docs/apm/images/traffic-transactions.png differ diff --git a/docs/apm/service-overview.asciidoc b/docs/apm/service-overview.asciidoc index 088791e6098e6..5fd214e6ce613 100644 --- a/docs/apm/service-overview.asciidoc +++ b/docs/apm/service-overview.asciidoc @@ -17,8 +17,8 @@ Response times for the service. You can filter the *Latency* chart to display th image::apm/images/latency.png[Service latency] [discrete] -[[service-traffic-transactions]] -=== Traffic and transactions +[[service-throughput-transactions]] +=== Throughput and transactions The *Throughput* chart visualizes the average number of transactions per minute for the selected service. @@ -62,6 +62,9 @@ each dependency. By default, dependencies are sorted by _Impact_ to show the mos If there is a particular dependency you are interested in, click *View service map* to view the related <>. +IMPORTANT: A known issue prevents Real User Monitoring (RUM) dependencies from being shown in the +*Dependencies* table. We are working on a fix for this issue. + [role="screenshot"] image::apm/images/spans-dependencies.png[Span type duration and dependencies] diff --git a/docs/apm/transactions.asciidoc b/docs/apm/transactions.asciidoc index 83ca9e5a10a9b..8c8da81aa577e 100644 --- a/docs/apm/transactions.asciidoc +++ b/docs/apm/transactions.asciidoc @@ -17,10 +17,10 @@ If there's a weird spike that you'd like to investigate, you can simply zoom in on the graph - this will adjust the specific time range, and all of the data on the page will update accordingly. -*Transactions per minute*:: -Visualize response codes: `2xx`, `3xx`, `4xx`, etc., -and is useful for determining if you're serving more of one code than you typically do. -Like in the Transaction duration graph, you can zoom in on anomalies to further investigate them. +*Throughput*:: +Visualize response codes: `2xx`, `3xx`, `4xx`, etc. +Useful for determining if more responses than usual are being served with a particular response code. +Like in the latency graph, you can zoom in on anomalies to further investigate them. *Error rate*:: Visualize the total number of transactions with errors divided by the total number of transactions. @@ -157,4 +157,4 @@ and solve problems. [role="screenshot"] image::apm/images/apm-logs-tab.png[APM logs tab] -// To do: link to log correlation \ No newline at end of file +// To do: link to log correlation diff --git a/docs/apm/troubleshooting.asciidoc b/docs/apm/troubleshooting.asciidoc index 7084777cbb6f9..465a3d652046d 100644 --- a/docs/apm/troubleshooting.asciidoc +++ b/docs/apm/troubleshooting.asciidoc @@ -1,14 +1,30 @@ [[troubleshooting]] -== Troubleshoot common problems +== Troubleshooting ++++ Troubleshooting ++++ -If you have something to add to this section, please consider creating a pull request with -your proposed changes at https://github.com/elastic/kibana. - -Also, check out the https://discuss.elastic.co/c/apm[APM discussion forum]. +This section describes common problems you might encounter with the APM app. +To add to this page, please consider opening a +https://github.com/elastic/kibana/pulls[pull request] with your proposed changes. + +If your issue is potentially related to other components of the APM ecosystem, +don't forget to check our other troubleshooting guides or discussion forum: + +* {apm-server-ref}/troubleshooting.html[APM Server troubleshooting] +* {apm-dotnet-ref}/troubleshooting.html[.NET agent troubleshooting] +* {apm-go-ref}/troubleshooting.html[Go agent troubleshooting] +* {apm-java-ref}/trouble-shooting.html[Java agent troubleshooting] +* {apm-node-ref}/troubleshooting.html[Node.js agent troubleshooting] +* {apm-py-ref}/troubleshooting.html[Python agent troubleshooting] +* {apm-ruby-ref}/debugging.html[Ruby agent troubleshooting] +* {apm-rum-ref/troubleshooting.html[RUM troubleshooting] +* https://discuss.elastic.co/c/apm[APM discussion forum]. + +[discrete] +[[troubleshooting-apm-app]] +== Troubleshoot common APM app problems * <> * <> diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 0ab1c89c1d8f7..6587d5dc422b4 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -370,6 +370,10 @@ and actions. |The features plugin enhance Kibana with a per-feature privilege system. +|{kib-repo}blob/{branch}/x-pack/plugins/file_upload[fileUpload] +|WARNING: Missing README. + + |{kib-repo}blob/{branch}/x-pack/plugins/fleet/README.md[fleet] |Fleet needs to have Elasticsearch API keys enabled, and also to have TLS enabled on kibana, (if you want to run Kibana without TLS you can provide the following config flag --xpack.fleet.agents.tlsCheckDisabled=false) @@ -415,7 +419,7 @@ the infrastructure monitoring use-case within Kibana. |{kib-repo}blob/{branch}/x-pack/plugins/lens/readme.md[lens] -|Run all tests from the x-pack root directory +|Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. |{kib-repo}blob/{branch}/x-pack/plugins/license_management/README.md[licenseManagement] diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md index 79c603165cae4..51e8d1a0b6bef 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md @@ -122,6 +122,7 @@ readonly links: { createPipeline: string; createTransformRequest: string; executeWatchActionModes: string; + indexExists: string; openIndex: string; putComponentTemplate: string; painlessExecute: string; diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.createindexaliasnotfounderror.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.createindexaliasnotfounderror.md deleted file mode 100644 index 2b897db7bba4c..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.createindexaliasnotfounderror.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsErrorHelpers](./kibana-plugin-core-server.savedobjectserrorhelpers.md) > [createIndexAliasNotFoundError](./kibana-plugin-core-server.savedobjectserrorhelpers.createindexaliasnotfounderror.md) - -## SavedObjectsErrorHelpers.createIndexAliasNotFoundError() method - -Signature: - -```typescript -static createIndexAliasNotFoundError(alias: string): DecoratedError; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| alias | string | | - -Returns: - -`DecoratedError` - diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.decorateindexaliasnotfounderror.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.decorateindexaliasnotfounderror.md deleted file mode 100644 index c7e10fc42ead1..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.decorateindexaliasnotfounderror.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsErrorHelpers](./kibana-plugin-core-server.savedobjectserrorhelpers.md) > [decorateIndexAliasNotFoundError](./kibana-plugin-core-server.savedobjectserrorhelpers.decorateindexaliasnotfounderror.md) - -## SavedObjectsErrorHelpers.decorateIndexAliasNotFoundError() method - -Signature: - -```typescript -static decorateIndexAliasNotFoundError(error: Error, alias: string): DecoratedError; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| error | Error | | -| alias | string | | - -Returns: - -`DecoratedError` - diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.isgeneralerror.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.isgeneralerror.md deleted file mode 100644 index 4b4ede2f77a7e..0000000000000 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.isgeneralerror.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [SavedObjectsErrorHelpers](./kibana-plugin-core-server.savedobjectserrorhelpers.md) > [isGeneralError](./kibana-plugin-core-server.savedobjectserrorhelpers.isgeneralerror.md) - -## SavedObjectsErrorHelpers.isGeneralError() method - -Signature: - -```typescript -static isGeneralError(error: Error | DecoratedError): boolean; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| error | Error | DecoratedError | | - -Returns: - -`boolean` - diff --git a/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.md b/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.md index 2dc78f2df3a83..9b69012ed5f12 100644 --- a/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.md +++ b/docs/development/core/server/kibana-plugin-core-server.savedobjectserrorhelpers.md @@ -18,7 +18,6 @@ export declare class SavedObjectsErrorHelpers | [createBadRequestError(reason)](./kibana-plugin-core-server.savedobjectserrorhelpers.createbadrequesterror.md) | static | | | [createConflictError(type, id, reason)](./kibana-plugin-core-server.savedobjectserrorhelpers.createconflicterror.md) | static | | | [createGenericNotFoundError(type, id)](./kibana-plugin-core-server.savedobjectserrorhelpers.creategenericnotfounderror.md) | static | | -| [createIndexAliasNotFoundError(alias)](./kibana-plugin-core-server.savedobjectserrorhelpers.createindexaliasnotfounderror.md) | static | | | [createInvalidVersionError(versionInput)](./kibana-plugin-core-server.savedobjectserrorhelpers.createinvalidversionerror.md) | static | | | [createTooManyRequestsError(type, id)](./kibana-plugin-core-server.savedobjectserrorhelpers.createtoomanyrequestserror.md) | static | | | [createUnsupportedTypeError(type)](./kibana-plugin-core-server.savedobjectserrorhelpers.createunsupportedtypeerror.md) | static | | @@ -28,7 +27,6 @@ export declare class SavedObjectsErrorHelpers | [decorateEsUnavailableError(error, reason)](./kibana-plugin-core-server.savedobjectserrorhelpers.decorateesunavailableerror.md) | static | | | [decorateForbiddenError(error, reason)](./kibana-plugin-core-server.savedobjectserrorhelpers.decorateforbiddenerror.md) | static | | | [decorateGeneralError(error, reason)](./kibana-plugin-core-server.savedobjectserrorhelpers.decorategeneralerror.md) | static | | -| [decorateIndexAliasNotFoundError(error, alias)](./kibana-plugin-core-server.savedobjectserrorhelpers.decorateindexaliasnotfounderror.md) | static | | | [decorateNotAuthorizedError(error, reason)](./kibana-plugin-core-server.savedobjectserrorhelpers.decoratenotauthorizederror.md) | static | | | [decorateRequestEntityTooLargeError(error, reason)](./kibana-plugin-core-server.savedobjectserrorhelpers.decoraterequestentitytoolargeerror.md) | static | | | [decorateTooManyRequestsError(error, reason)](./kibana-plugin-core-server.savedobjectserrorhelpers.decoratetoomanyrequestserror.md) | static | | @@ -37,7 +35,6 @@ export declare class SavedObjectsErrorHelpers | [isEsCannotExecuteScriptError(error)](./kibana-plugin-core-server.savedobjectserrorhelpers.isescannotexecutescripterror.md) | static | | | [isEsUnavailableError(error)](./kibana-plugin-core-server.savedobjectserrorhelpers.isesunavailableerror.md) | static | | | [isForbiddenError(error)](./kibana-plugin-core-server.savedobjectserrorhelpers.isforbiddenerror.md) | static | | -| [isGeneralError(error)](./kibana-plugin-core-server.savedobjectserrorhelpers.isgeneralerror.md) | static | | | [isInvalidVersionError(error)](./kibana-plugin-core-server.savedobjectserrorhelpers.isinvalidversionerror.md) | static | | | [isNotAuthorizedError(error)](./kibana-plugin-core-server.savedobjectserrorhelpers.isnotauthorizederror.md) | static | | | [isNotFoundError(error)](./kibana-plugin-core-server.savedobjectserrorhelpers.isnotfounderror.md) | static | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.legacyhitstotal.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.legacyhitstotal.md new file mode 100644 index 0000000000000..937e20a7a9579 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.legacyhitstotal.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [ISearchOptions](./kibana-plugin-plugins-data-public.isearchoptions.md) > [legacyHitsTotal](./kibana-plugin-plugins-data-public.isearchoptions.legacyhitstotal.md) + +## ISearchOptions.legacyHitsTotal property + +Request the legacy format for the total number of hits. If sending `rest_total_hits_as_int` to something other than `true`, this should be set to `false`. + +Signature: + +```typescript +legacyHitsTotal?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md index 5acd837495dac..fc2767cd0231f 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.isearchoptions.md @@ -17,6 +17,7 @@ export interface ISearchOptions | [abortSignal](./kibana-plugin-plugins-data-public.isearchoptions.abortsignal.md) | AbortSignal | An AbortSignal that allows the caller of search to abort a search request. | | [isRestore](./kibana-plugin-plugins-data-public.isearchoptions.isrestore.md) | boolean | Whether the session is restored (i.e. search requests should re-use the stored search IDs, rather than starting from scratch) | | [isStored](./kibana-plugin-plugins-data-public.isearchoptions.isstored.md) | boolean | Whether the session is already saved (i.e. sent to background) | +| [legacyHitsTotal](./kibana-plugin-plugins-data-public.isearchoptions.legacyhitstotal.md) | boolean | Request the legacy format for the total number of hits. If sending rest_total_hits_as_int to something other than true, this should be set to false. | | [sessionId](./kibana-plugin-plugins-data-public.isearchoptions.sessionid.md) | string | A session ID, grouping multiple search requests into a single session. | | [strategy](./kibana-plugin-plugins-data-public.isearchoptions.strategy.md) | string | Use this option to force using a specific server side search strategy. Leave empty to use the default strategy. | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md index 4bbc76b78ba03..f576d795b93a5 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md @@ -92,8 +92,6 @@ | [SearchInterceptorDeps](./kibana-plugin-plugins-data-public.searchinterceptordeps.md) | | | [SearchSessionInfoProvider](./kibana-plugin-plugins-data-public.searchsessioninfoprovider.md) | Provide info about current search session to be stored in the Search Session saved object | | [SearchSourceFields](./kibana-plugin-plugins-data-public.searchsourcefields.md) | search source fields | -| [TabbedAggColumn](./kibana-plugin-plugins-data-public.tabbedaggcolumn.md) | \* | -| [TabbedTable](./kibana-plugin-plugins-data-public.tabbedtable.md) | \* | ## Variables @@ -187,7 +185,6 @@ | [SavedQueryTimeFilter](./kibana-plugin-plugins-data-public.savedquerytimefilter.md) | | | [SearchBarProps](./kibana-plugin-plugins-data-public.searchbarprops.md) | | | [StatefulSearchBarProps](./kibana-plugin-plugins-data-public.statefulsearchbarprops.md) | | -| [TabbedAggRow](./kibana-plugin-plugins-data-public.tabbedaggrow.md) | \* | | [TimefilterContract](./kibana-plugin-plugins-data-public.timefiltercontract.md) | | | [TimeHistoryContract](./kibana-plugin-plugins-data-public.timehistorycontract.md) | | | [TimeRange](./kibana-plugin-plugins-data-public.timerange.md) | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getfields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getfields.md index faff901bfc167..b0ccedb819c95 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getfields.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getfields.md @@ -9,45 +9,16 @@ returns all search source fields Signature: ```typescript -getFields(): { - type?: string | undefined; - query?: import("../..").Query | undefined; - filter?: Filter | Filter[] | (() => Filter | Filter[] | undefined) | undefined; - sort?: Record | Record[] | undefined; - highlight?: any; - highlightAll?: boolean | undefined; - aggs?: any; - from?: number | undefined; - size?: number | undefined; - source?: string | boolean | string[] | undefined; - version?: boolean | undefined; - fields?: SearchFieldValue[] | undefined; - fieldsFromSource?: string | boolean | string[] | undefined; - index?: import("../..").IndexPattern | undefined; - searchAfter?: import("./types").EsQuerySearchAfter | undefined; - timeout?: string | undefined; - terminate_after?: number | undefined; - }; +getFields(recurse?: boolean): SearchSourceFields; ``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| recurse | boolean | | + Returns: -`{ - type?: string | undefined; - query?: import("../..").Query | undefined; - filter?: Filter | Filter[] | (() => Filter | Filter[] | undefined) | undefined; - sort?: Record | Record[] | undefined; - highlight?: any; - highlightAll?: boolean | undefined; - aggs?: any; - from?: number | undefined; - size?: number | undefined; - source?: string | boolean | string[] | undefined; - version?: boolean | undefined; - fields?: SearchFieldValue[] | undefined; - fieldsFromSource?: string | boolean | string[] | undefined; - index?: import("../..").IndexPattern | undefined; - searchAfter?: import("./types").EsQuerySearchAfter | undefined; - timeout?: string | undefined; - terminate_after?: number | undefined; - }` +`SearchSourceFields` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getserializedfields.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getserializedfields.md index 3f58a76b24cd0..19bd4a7888bf2 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getserializedfields.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.getserializedfields.md @@ -9,8 +9,15 @@ serializes search source fields (which can later be passed to [ISearchStartSearc Signature: ```typescript -getSerializedFields(): SearchSourceFields; +getSerializedFields(recurse?: boolean): SearchSourceFields; ``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| recurse | boolean | | + Returns: `SearchSourceFields` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md index 2af9cc14e3668..3250561c8b82e 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchsource.md @@ -35,12 +35,12 @@ export declare class SearchSource | [fetch(options)](./kibana-plugin-plugins-data-public.searchsource.fetch.md) | | Fetch this source and reject the returned Promise on error | | [fetch$(options)](./kibana-plugin-plugins-data-public.searchsource.fetch_.md) | | Fetch this source from Elasticsearch, returning an observable over the response(s) | | [getField(field, recurse)](./kibana-plugin-plugins-data-public.searchsource.getfield.md) | | Gets a single field from the fields | -| [getFields()](./kibana-plugin-plugins-data-public.searchsource.getfields.md) | | returns all search source fields | +| [getFields(recurse)](./kibana-plugin-plugins-data-public.searchsource.getfields.md) | | returns all search source fields | | [getId()](./kibana-plugin-plugins-data-public.searchsource.getid.md) | | returns search source id | | [getOwnField(field)](./kibana-plugin-plugins-data-public.searchsource.getownfield.md) | | Get the field from our own fields, don't traverse up the chain | | [getParent()](./kibana-plugin-plugins-data-public.searchsource.getparent.md) | | Get the parent of this SearchSource {undefined\|searchSource} | | [getSearchRequestBody()](./kibana-plugin-plugins-data-public.searchsource.getsearchrequestbody.md) | | Returns body contents of the search request, often referred as query DSL. | -| [getSerializedFields()](./kibana-plugin-plugins-data-public.searchsource.getserializedfields.md) | | serializes search source fields (which can later be passed to [ISearchStartSearchSource](./kibana-plugin-plugins-data-public.isearchstartsearchsource.md)) | +| [getSerializedFields(recurse)](./kibana-plugin-plugins-data-public.searchsource.getserializedfields.md) | | serializes search source fields (which can later be passed to [ISearchStartSearchSource](./kibana-plugin-plugins-data-public.isearchstartsearchsource.md)) | | [onRequestStart(handler)](./kibana-plugin-plugins-data-public.searchsource.onrequeststart.md) | | Add a handler that will be notified whenever requests start | | [removeField(field)](./kibana-plugin-plugins-data-public.searchsource.removefield.md) | | remove field | | [serialize()](./kibana-plugin-plugins-data-public.searchsource.serialize.md) | | Serializes the instance to a JSON string and a set of referenced objects. Use this method to get a representation of the search source which can be stored in a saved object.The references returned by this function can be mixed with other references in the same object, however make sure there are no name-collisions. The references will be named kibanaSavedObjectMeta.searchSourceJSON.index and kibanaSavedObjectMeta.searchSourceJSON.filter[<number>].meta.index.Using createSearchSource, the instance can be re-created. | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.tabbedaggcolumn.aggconfig.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.tabbedaggcolumn.aggconfig.md deleted file mode 100644 index b010667af79e4..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.tabbedaggcolumn.aggconfig.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [TabbedAggColumn](./kibana-plugin-plugins-data-public.tabbedaggcolumn.md) > [aggConfig](./kibana-plugin-plugins-data-public.tabbedaggcolumn.aggconfig.md) - -## TabbedAggColumn.aggConfig property - -Signature: - -```typescript -aggConfig: IAggConfig; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.tabbedaggcolumn.id.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.tabbedaggcolumn.id.md deleted file mode 100644 index 86f8b01312047..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.tabbedaggcolumn.id.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [TabbedAggColumn](./kibana-plugin-plugins-data-public.tabbedaggcolumn.md) > [id](./kibana-plugin-plugins-data-public.tabbedaggcolumn.id.md) - -## TabbedAggColumn.id property - -Signature: - -```typescript -id: string; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.tabbedaggcolumn.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.tabbedaggcolumn.md deleted file mode 100644 index 578a2b159f9eb..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.tabbedaggcolumn.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [TabbedAggColumn](./kibana-plugin-plugins-data-public.tabbedaggcolumn.md) - -## TabbedAggColumn interface - -\* - -Signature: - -```typescript -export interface TabbedAggColumn -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [aggConfig](./kibana-plugin-plugins-data-public.tabbedaggcolumn.aggconfig.md) | IAggConfig | | -| [id](./kibana-plugin-plugins-data-public.tabbedaggcolumn.id.md) | string | | -| [name](./kibana-plugin-plugins-data-public.tabbedaggcolumn.name.md) | string | | - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.tabbedaggcolumn.name.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.tabbedaggcolumn.name.md deleted file mode 100644 index ce20c1c50b984..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.tabbedaggcolumn.name.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [TabbedAggColumn](./kibana-plugin-plugins-data-public.tabbedaggcolumn.md) > [name](./kibana-plugin-plugins-data-public.tabbedaggcolumn.name.md) - -## TabbedAggColumn.name property - -Signature: - -```typescript -name: string; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.tabbedaggrow.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.tabbedaggrow.md deleted file mode 100644 index 28519d95c4374..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.tabbedaggrow.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [TabbedAggRow](./kibana-plugin-plugins-data-public.tabbedaggrow.md) - -## TabbedAggRow type - -\* - -Signature: - -```typescript -export declare type TabbedAggRow = Record; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.tabbedtable.columns.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.tabbedtable.columns.md deleted file mode 100644 index 8256291d368c3..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.tabbedtable.columns.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [TabbedTable](./kibana-plugin-plugins-data-public.tabbedtable.md) > [columns](./kibana-plugin-plugins-data-public.tabbedtable.columns.md) - -## TabbedTable.columns property - -Signature: - -```typescript -columns: TabbedAggColumn[]; -``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.tabbedtable.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.tabbedtable.md deleted file mode 100644 index 51b1bfa9b4362..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.tabbedtable.md +++ /dev/null @@ -1,21 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [TabbedTable](./kibana-plugin-plugins-data-public.tabbedtable.md) - -## TabbedTable interface - -\* - -Signature: - -```typescript -export interface TabbedTable -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [columns](./kibana-plugin-plugins-data-public.tabbedtable.columns.md) | TabbedAggColumn[] | | -| [rows](./kibana-plugin-plugins-data-public.tabbedtable.rows.md) | TabbedAggRow[] | | - diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.tabbedtable.rows.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.tabbedtable.rows.md deleted file mode 100644 index 19a973b18d75c..0000000000000 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.tabbedtable.rows.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [TabbedTable](./kibana-plugin-plugins-data-public.tabbedtable.md) > [rows](./kibana-plugin-plugins-data-public.tabbedtable.rows.md) - -## TabbedTable.rows property - -Signature: - -```typescript -rows: TabbedAggRow[]; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.dataapirequesthandlercontext.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.dataapirequesthandlercontext.md deleted file mode 100644 index 8b7b025d80181..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.dataapirequesthandlercontext.md +++ /dev/null @@ -1,18 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [DataApiRequestHandlerContext](./kibana-plugin-plugins-data-server.dataapirequesthandlercontext.md) - -## DataApiRequestHandlerContext interface - -Signature: - -```typescript -export interface DataApiRequestHandlerContext extends ISearchClient -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [session](./kibana-plugin-plugins-data-server.dataapirequesthandlercontext.session.md) | IScopedSessionService | | - diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.dataapirequesthandlercontext.session.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.dataapirequesthandlercontext.session.md deleted file mode 100644 index 9a6e3f55d3929..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.dataapirequesthandlercontext.session.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [DataApiRequestHandlerContext](./kibana-plugin-plugins-data-server.dataapirequesthandlercontext.md) > [session](./kibana-plugin-plugins-data-server.dataapirequesthandlercontext.session.md) - -## DataApiRequestHandlerContext.session property - -Signature: - -```typescript -session: IScopedSessionService; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.legacyhitstotal.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.legacyhitstotal.md new file mode 100644 index 0000000000000..59b8b2c6b446f --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.legacyhitstotal.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchOptions](./kibana-plugin-plugins-data-server.isearchoptions.md) > [legacyHitsTotal](./kibana-plugin-plugins-data-server.isearchoptions.legacyhitstotal.md) + +## ISearchOptions.legacyHitsTotal property + +Request the legacy format for the total number of hits. If sending `rest_total_hits_as_int` to something other than `true`, this should be set to `false`. + +Signature: + +```typescript +legacyHitsTotal?: boolean; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md index 85847e1c61d25..9de351b2b9019 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchoptions.md @@ -17,6 +17,7 @@ export interface ISearchOptions | [abortSignal](./kibana-plugin-plugins-data-server.isearchoptions.abortsignal.md) | AbortSignal | An AbortSignal that allows the caller of search to abort a search request. | | [isRestore](./kibana-plugin-plugins-data-server.isearchoptions.isrestore.md) | boolean | Whether the session is restored (i.e. search requests should re-use the stored search IDs, rather than starting from scratch) | | [isStored](./kibana-plugin-plugins-data-server.isearchoptions.isstored.md) | boolean | Whether the session is already saved (i.e. sent to background) | +| [legacyHitsTotal](./kibana-plugin-plugins-data-server.isearchoptions.legacyhitstotal.md) | boolean | Request the legacy format for the total number of hits. If sending rest_total_hits_as_int to something other than true, this should be set to false. | | [sessionId](./kibana-plugin-plugins-data-server.isearchoptions.sessionid.md) | string | A session ID, grouping multiple search requests into a single session. | | [strategy](./kibana-plugin-plugins-data-server.isearchoptions.strategy.md) | string | Use this option to force using a specific server side search strategy. Leave empty to use the default strategy. | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsessionservice.asscopedprovider.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsessionservice.asscopedprovider.md new file mode 100644 index 0000000000000..3f3d1a2429933 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsessionservice.asscopedprovider.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchSessionService](./kibana-plugin-plugins-data-server.isearchsessionservice.md) > [asScopedProvider](./kibana-plugin-plugins-data-server.isearchsessionservice.asscopedprovider.md) + +## ISearchSessionService.asScopedProvider property + +Signature: + +```typescript +asScopedProvider: (core: CoreStart) => (request: KibanaRequest) => IScopedSearchSessionsClient; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsessionservice.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsessionservice.md new file mode 100644 index 0000000000000..e7a92497308b9 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchsessionservice.md @@ -0,0 +1,18 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISearchSessionService](./kibana-plugin-plugins-data-server.isearchsessionservice.md) + +## ISearchSessionService interface + +Signature: + +```typescript +export interface ISearchSessionService +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [asScopedProvider](./kibana-plugin-plugins-data-server.isearchsessionservice.asscopedprovider.md) | (core: CoreStart) => (request: KibanaRequest) => IScopedSearchSessionsClient<T> | | + diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.asscoped.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.asscoped.md index f97cc22a53001..1c65aeb8f137f 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.asscoped.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.asscoped.md @@ -7,5 +7,5 @@ Signature: ```typescript -asScoped: (request: KibanaRequest) => ISearchClient; +asScoped: (request: KibanaRequest) => IScopedSearchClient; ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md index 771b529f23824..52579a0a14b4d 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isearchstart.md @@ -15,7 +15,7 @@ export interface ISearchStartAggsStart | | -| [asScoped](./kibana-plugin-plugins-data-server.isearchstart.asscoped.md) | (request: KibanaRequest) => ISearchClient | | +| [asScoped](./kibana-plugin-plugins-data-server.isearchstart.asscoped.md) | (request: KibanaRequest) => IScopedSearchClient | | | [getSearchStrategy](./kibana-plugin-plugins-data-server.isearchstart.getsearchstrategy.md) | (name?: string) => ISearchStrategy<SearchStrategyRequest, SearchStrategyResponse> | Get other registered search strategies by name (or, by default, the Elasticsearch strategy). For example, if a new strategy needs to use the already-registered ES search strategy, it can use this function to accomplish that. | | [searchSource](./kibana-plugin-plugins-data-server.isearchstart.searchsource.md) | {
asScoped: (request: KibanaRequest) => Promise<ISearchStartSearchSource>;
} | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isessionservice.asscopedprovider.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isessionservice.asscopedprovider.md deleted file mode 100644 index d52b9b783919b..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isessionservice.asscopedprovider.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISessionService](./kibana-plugin-plugins-data-server.isessionservice.md) > [asScopedProvider](./kibana-plugin-plugins-data-server.isessionservice.asscopedprovider.md) - -## ISessionService.asScopedProvider property - -Signature: - -```typescript -asScopedProvider: (core: CoreStart) => (request: KibanaRequest) => IScopedSessionService; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isessionservice.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isessionservice.md deleted file mode 100644 index dcc7dfc8bb946..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.isessionservice.md +++ /dev/null @@ -1,18 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [ISessionService](./kibana-plugin-plugins-data-server.isessionservice.md) - -## ISessionService interface - -Signature: - -```typescript -export interface ISessionService -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [asScopedProvider](./kibana-plugin-plugins-data-server.isessionservice.asscopedprovider.md) | (core: CoreStart) => (request: KibanaRequest) => IScopedSessionService | | - diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md index 84c7875c26ce8..4739de481e020 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md @@ -14,7 +14,6 @@ | [IndexPatternsService](./kibana-plugin-plugins-data-server.indexpatternsservice.md) | | | [OptionedParamType](./kibana-plugin-plugins-data-server.optionedparamtype.md) | | | [Plugin](./kibana-plugin-plugins-data-server.plugin.md) | | -| [SessionService](./kibana-plugin-plugins-data-server.sessionservice.md) | The OSS session service. See data\_enhanced in X-Pack for the search session service. | ## Enumerations @@ -45,7 +44,6 @@ | --- | --- | | [AggFunctionsMapping](./kibana-plugin-plugins-data-server.aggfunctionsmapping.md) | A global list of the expression function definitions for each agg type function. | | [AggParamOption](./kibana-plugin-plugins-data-server.aggparamoption.md) | | -| [DataApiRequestHandlerContext](./kibana-plugin-plugins-data-server.dataapirequesthandlercontext.md) | | | [EsQueryConfig](./kibana-plugin-plugins-data-server.esqueryconfig.md) | | | [FieldDescriptor](./kibana-plugin-plugins-data-server.fielddescriptor.md) | | | [FieldFormatConfig](./kibana-plugin-plugins-data-server.fieldformatconfig.md) | | @@ -54,10 +52,10 @@ | [IFieldType](./kibana-plugin-plugins-data-server.ifieldtype.md) | | | [IndexPatternAttributes](./kibana-plugin-plugins-data-server.indexpatternattributes.md) | Interface for an index pattern saved object | | [ISearchOptions](./kibana-plugin-plugins-data-server.isearchoptions.md) | | +| [ISearchSessionService](./kibana-plugin-plugins-data-server.isearchsessionservice.md) | | | [ISearchSetup](./kibana-plugin-plugins-data-server.isearchsetup.md) | | | [ISearchStart](./kibana-plugin-plugins-data-server.isearchstart.md) | | | [ISearchStrategy](./kibana-plugin-plugins-data-server.isearchstrategy.md) | Search strategy interface contains a search method that takes in a request and returns a promise that resolves to a response. | -| [ISessionService](./kibana-plugin-plugins-data-server.isessionservice.md) | | | [KueryNode](./kibana-plugin-plugins-data-server.kuerynode.md) | | | [OptionedValueProp](./kibana-plugin-plugins-data-server.optionedvalueprop.md) | | | [PluginSetup](./kibana-plugin-plugins-data-server.pluginsetup.md) | | @@ -65,8 +63,6 @@ | [RefreshInterval](./kibana-plugin-plugins-data-server.refreshinterval.md) | | | [SearchStrategyDependencies](./kibana-plugin-plugins-data-server.searchstrategydependencies.md) | | | [SearchUsage](./kibana-plugin-plugins-data-server.searchusage.md) | | -| [TabbedAggColumn](./kibana-plugin-plugins-data-server.tabbedaggcolumn.md) | \* | -| [TabbedTable](./kibana-plugin-plugins-data-server.tabbedtable.md) | \* | ## Variables @@ -111,6 +107,6 @@ | [KibanaContext](./kibana-plugin-plugins-data-server.kibanacontext.md) | | | [ParsedInterval](./kibana-plugin-plugins-data-server.parsedinterval.md) | | | [Query](./kibana-plugin-plugins-data-server.query.md) | | -| [TabbedAggRow](./kibana-plugin-plugins-data-server.tabbedaggrow.md) | \* | +| [SearchRequestHandlerContext](./kibana-plugin-plugins-data-server.searchrequesthandlercontext.md) | | | [TimeRange](./kibana-plugin-plugins-data-server.timerange.md) | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchrequesthandlercontext.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchrequesthandlercontext.md new file mode 100644 index 0000000000000..f031ddfbd09af --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchrequesthandlercontext.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [SearchRequestHandlerContext](./kibana-plugin-plugins-data-server.searchrequesthandlercontext.md) + +## SearchRequestHandlerContext type + +Signature: + +```typescript +export declare type SearchRequestHandlerContext = IScopedSearchClient; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchstrategydependencies.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchstrategydependencies.md index be95fb04a2c4f..b47e00542da97 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchstrategydependencies.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchstrategydependencies.md @@ -16,5 +16,6 @@ export interface SearchStrategyDependencies | --- | --- | --- | | [esClient](./kibana-plugin-plugins-data-server.searchstrategydependencies.esclient.md) | IScopedClusterClient | | | [savedObjectsClient](./kibana-plugin-plugins-data-server.searchstrategydependencies.savedobjectsclient.md) | SavedObjectsClientContract | | +| [searchSessionsClient](./kibana-plugin-plugins-data-server.searchstrategydependencies.searchsessionsclient.md) | IScopedSearchSessionsClient | | | [uiSettingsClient](./kibana-plugin-plugins-data-server.searchstrategydependencies.uisettingsclient.md) | IUiSettingsClient | | diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchstrategydependencies.searchsessionsclient.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchstrategydependencies.searchsessionsclient.md new file mode 100644 index 0000000000000..5340ed9673c02 --- /dev/null +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.searchstrategydependencies.searchsessionsclient.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [SearchStrategyDependencies](./kibana-plugin-plugins-data-server.searchstrategydependencies.md) > [searchSessionsClient](./kibana-plugin-plugins-data-server.searchstrategydependencies.searchsessionsclient.md) + +## SearchStrategyDependencies.searchSessionsClient property + +Signature: + +```typescript +searchSessionsClient: IScopedSearchSessionsClient; +``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice._constructor_.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice._constructor_.md deleted file mode 100644 index 73d658455a66f..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice._constructor_.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [SessionService](./kibana-plugin-plugins-data-server.sessionservice.md) > [(constructor)](./kibana-plugin-plugins-data-server.sessionservice._constructor_.md) - -## SessionService.(constructor) - -Constructs a new instance of the `SessionService` class - -Signature: - -```typescript -constructor(); -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.asscopedprovider.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.asscopedprovider.md deleted file mode 100644 index f3af7fb0f61d9..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.asscopedprovider.md +++ /dev/null @@ -1,26 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [SessionService](./kibana-plugin-plugins-data-server.sessionservice.md) > [asScopedProvider](./kibana-plugin-plugins-data-server.sessionservice.asscopedprovider.md) - -## SessionService.asScopedProvider() method - -Signature: - -```typescript -asScopedProvider(core: CoreStart): (request: KibanaRequest) => { - search: , Response_1 extends IKibanaSearchResponse>(strategy: ISearchStrategy, request: Request_1, options: import("../../../common").ISearchOptions, deps: import("../types").SearchStrategyDependencies) => import("rxjs").Observable; - }; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| core | CoreStart | | - -Returns: - -`(request: KibanaRequest) => { - search: , Response_1 extends IKibanaSearchResponse>(strategy: ISearchStrategy, request: Request_1, options: import("../../../common").ISearchOptions, deps: import("../types").SearchStrategyDependencies) => import("rxjs").Observable; - }` - diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.md deleted file mode 100644 index 2457f7103d8fa..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.md +++ /dev/null @@ -1,27 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [SessionService](./kibana-plugin-plugins-data-server.sessionservice.md) - -## SessionService class - -The OSS session service. See data\_enhanced in X-Pack for the search session service. - -Signature: - -```typescript -export declare class SessionService implements ISessionService -``` - -## Constructors - -| Constructor | Modifiers | Description | -| --- | --- | --- | -| [(constructor)()](./kibana-plugin-plugins-data-server.sessionservice._constructor_.md) | | Constructs a new instance of the SessionService class | - -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [asScopedProvider(core)](./kibana-plugin-plugins-data-server.sessionservice.asscopedprovider.md) | | | -| [search(strategy, args)](./kibana-plugin-plugins-data-server.sessionservice.search.md) | | | - diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.search.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.search.md deleted file mode 100644 index 8f8620a6ed5ee..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.sessionservice.search.md +++ /dev/null @@ -1,23 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [SessionService](./kibana-plugin-plugins-data-server.sessionservice.md) > [search](./kibana-plugin-plugins-data-server.sessionservice.search.md) - -## SessionService.search() method - -Signature: - -```typescript -search(strategy: ISearchStrategy, ...args: Parameters['search']>): import("rxjs").Observable; -``` - -## Parameters - -| Parameter | Type | Description | -| --- | --- | --- | -| strategy | ISearchStrategy<Request, Response> | | -| args | Parameters<ISearchStrategy<Request, Response>['search']> | | - -Returns: - -`import("rxjs").Observable` - diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggcolumn.aggconfig.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggcolumn.aggconfig.md deleted file mode 100644 index 9870f7380e16a..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggcolumn.aggconfig.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [TabbedAggColumn](./kibana-plugin-plugins-data-server.tabbedaggcolumn.md) > [aggConfig](./kibana-plugin-plugins-data-server.tabbedaggcolumn.aggconfig.md) - -## TabbedAggColumn.aggConfig property - -Signature: - -```typescript -aggConfig: IAggConfig; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggcolumn.id.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggcolumn.id.md deleted file mode 100644 index 4f5a964a07a0c..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggcolumn.id.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [TabbedAggColumn](./kibana-plugin-plugins-data-server.tabbedaggcolumn.md) > [id](./kibana-plugin-plugins-data-server.tabbedaggcolumn.id.md) - -## TabbedAggColumn.id property - -Signature: - -```typescript -id: string; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggcolumn.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggcolumn.md deleted file mode 100644 index 5e47f745fa17a..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggcolumn.md +++ /dev/null @@ -1,22 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [TabbedAggColumn](./kibana-plugin-plugins-data-server.tabbedaggcolumn.md) - -## TabbedAggColumn interface - -\* - -Signature: - -```typescript -export interface TabbedAggColumn -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [aggConfig](./kibana-plugin-plugins-data-server.tabbedaggcolumn.aggconfig.md) | IAggConfig | | -| [id](./kibana-plugin-plugins-data-server.tabbedaggcolumn.id.md) | string | | -| [name](./kibana-plugin-plugins-data-server.tabbedaggcolumn.name.md) | string | | - diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggcolumn.name.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggcolumn.name.md deleted file mode 100644 index 8a07e2708066f..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggcolumn.name.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [TabbedAggColumn](./kibana-plugin-plugins-data-server.tabbedaggcolumn.md) > [name](./kibana-plugin-plugins-data-server.tabbedaggcolumn.name.md) - -## TabbedAggColumn.name property - -Signature: - -```typescript -name: string; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggrow.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggrow.md deleted file mode 100644 index d592aeff89639..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedaggrow.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [TabbedAggRow](./kibana-plugin-plugins-data-server.tabbedaggrow.md) - -## TabbedAggRow type - -\* - -Signature: - -```typescript -export declare type TabbedAggRow = Record; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedtable.columns.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedtable.columns.md deleted file mode 100644 index 55f079c581c8b..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedtable.columns.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [TabbedTable](./kibana-plugin-plugins-data-server.tabbedtable.md) > [columns](./kibana-plugin-plugins-data-server.tabbedtable.columns.md) - -## TabbedTable.columns property - -Signature: - -```typescript -columns: TabbedAggColumn[]; -``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedtable.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedtable.md deleted file mode 100644 index 1bb055a2a3ce9..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedtable.md +++ /dev/null @@ -1,21 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [TabbedTable](./kibana-plugin-plugins-data-server.tabbedtable.md) - -## TabbedTable interface - -\* - -Signature: - -```typescript -export interface TabbedTable -``` - -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [columns](./kibana-plugin-plugins-data-server.tabbedtable.columns.md) | TabbedAggColumn[] | | -| [rows](./kibana-plugin-plugins-data-server.tabbedtable.rows.md) | TabbedAggRow[] | | - diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedtable.rows.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedtable.rows.md deleted file mode 100644 index b783919a26573..0000000000000 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.tabbedtable.rows.md +++ /dev/null @@ -1,11 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-data-server](./kibana-plugin-plugins-data-server.md) > [TabbedTable](./kibana-plugin-plugins-data-server.tabbedtable.md) > [rows](./kibana-plugin-plugins-data-server.tabbedtable.rows.md) - -## TabbedTable.rows property - -Signature: - -```typescript -rows: TabbedAggRow[]; -``` diff --git a/docs/development/plugins/kibana_utils/public/state_sync/kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.cancel.md b/docs/development/plugins/kibana_utils/public/state_sync/kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.cancel.md new file mode 100644 index 0000000000000..ed78bc0169ebf --- /dev/null +++ b/docs/development/plugins/kibana_utils/public/state_sync/kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.cancel.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-kibana\_utils-public-state\_sync](./kibana-plugin-plugins-kibana_utils-public-state_sync.md) > [IKbnUrlStateStorage](./kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.md) > [cancel](./kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.cancel.md) + +## IKbnUrlStateStorage.cancel property + +Cancels any pending url updates + +Signature: + +```typescript +cancel: () => void; +``` diff --git a/docs/development/plugins/kibana_utils/public/state_sync/kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.md b/docs/development/plugins/kibana_utils/public/state_sync/kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.md index 7fb8717fae003..be77e5887e98f 100644 --- a/docs/development/plugins/kibana_utils/public/state_sync/kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.md +++ b/docs/development/plugins/kibana_utils/public/state_sync/kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.md @@ -20,6 +20,7 @@ export interface IKbnUrlStateStorage extends IStateStorage | Property | Type | Description | | --- | --- | --- | +| [cancel](./kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.cancel.md) | () => void | Cancels any pending url updates | | [change$](./kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.change_.md) | <State = unknown>(key: string) => Observable<State | null> | | | [get](./kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.get.md) | <State = unknown>(key: string) => State | null | | | [kbnUrlControls](./kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.kbnurlcontrols.md) | IKbnUrlControls | Lower level wrapper around history library that handles batching multiple URL updates into one history change | diff --git a/docs/maps/maps-getting-started.asciidoc b/docs/maps/maps-getting-started.asciidoc index 32a81c8e65f56..609a133c92ad1 100644 --- a/docs/maps/maps-getting-started.asciidoc +++ b/docs/maps/maps-getting-started.asciidoc @@ -1,15 +1,11 @@ [role="xpack"] [[maps-getting-started]] -== Create a map with multiple layers and data sources - -++++ -Create a multilayer map -++++ +== Build a map to compare metrics by country or region If you are new to **Maps**, this tutorial is a good place to start. It guides you through the common steps for working with your location data. -You'll learn to: +You will learn to: - Create a map with multiple layers and data sources - Use symbols, colors, and labels to style data values diff --git a/examples/expressions_explorer/public/actions_and_expressions.tsx b/examples/expressions_explorer/public/actions_and_expressions.tsx index 6e2eebcde4a0f..5c34dc878aa7c 100644 --- a/examples/expressions_explorer/public/actions_and_expressions.tsx +++ b/examples/expressions_explorer/public/actions_and_expressions.tsx @@ -63,7 +63,7 @@ export function ActionsExpressionsExample({ expressions, actions }: Props) { - + diff --git a/examples/expressions_explorer/public/actions_and_expressions2.tsx b/examples/expressions_explorer/public/actions_and_expressions2.tsx new file mode 100644 index 0000000000000..93287d15e63d9 --- /dev/null +++ b/examples/expressions_explorer/public/actions_and_expressions2.tsx @@ -0,0 +1,102 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import React, { useState } from 'react'; +import { + EuiFlexItem, + EuiFlexGroup, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiPageHeader, + EuiPageHeaderSection, + EuiPanel, + EuiText, + EuiTitle, +} from '@elastic/eui'; +import { + ExpressionsStart, + ReactExpressionRenderer, + ExpressionsInspectorAdapter, +} from '../../../src/plugins/expressions/public'; +import { ExpressionEditor } from './editor/expression_editor'; +import { UiActionsStart } from '../../../src/plugins/ui_actions/public'; + +interface Props { + expressions: ExpressionsStart; + actions: UiActionsStart; +} + +export function ActionsExpressionsExample2({ expressions, actions }: Props) { + const [expression, updateExpression] = useState( + 'button name="click me" href="http://www.google.com" color={var color}' + ); + + const [variables, updateVariables] = useState({ + color: 'blue', + }); + + const expressionChanged = (value: string) => { + updateExpression(value); + }; + + const inspectorAdapters = { + expression: new ExpressionsInspectorAdapter(), + }; + + const handleEvents = (event: any) => { + updateVariables({ color: event.value.href === 'http://www.google.com' ? 'red' : 'blue' }); + }; + + return ( + + + + +

Actions from expression renderers

+
+
+
+ + + + + + This example is similar to previous one, but clicking the button will rerender the + expression with new set of variables. + + + + + + + + + + + + + { + return
{message}
; + }} + /> +
+
+
+
+
+
+ ); +} diff --git a/examples/expressions_explorer/public/app.tsx b/examples/expressions_explorer/public/app.tsx index d72cf08128a5a..883f935ba0e84 100644 --- a/examples/expressions_explorer/public/app.tsx +++ b/examples/expressions_explorer/public/app.tsx @@ -25,6 +25,7 @@ import { RunExpressionsExample } from './run_expressions'; import { RenderExpressionsExample } from './render_expressions'; import { ActionsExpressionsExample } from './actions_and_expressions'; import { UiActionsStart } from '../../../src/plugins/ui_actions/public'; +import { ActionsExpressionsExample2 } from './actions_and_expressions2'; interface Props { expressions: ExpressionsStart; @@ -64,6 +65,10 @@ const ExpressionsExplorer = ({ expressions, inspector, actions }: Props) => { + + + +
diff --git a/examples/expressions_explorer/public/functions/button.ts b/examples/expressions_explorer/public/functions/button.ts index 8c39aa2743b30..e237ec64ab983 100644 --- a/examples/expressions_explorer/public/functions/button.ts +++ b/examples/expressions_explorer/public/functions/button.ts @@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n'; import { ExpressionFunctionDefinition } from '../../../../src/plugins/expressions/common'; interface Arguments { + color: string; href: string; name: string; } @@ -24,15 +25,20 @@ export type ExpressionFunctionButton = ExpressionFunctionDefinition< export const buttonFn: ExpressionFunctionButton = { name: 'button', args: { + color: { + help: i18n.translate('expressions.functions.button.args.color', { + defaultMessage: 'Color of the button', + }), + }, href: { - help: i18n.translate('expressions.functions.font.args.href', { + help: i18n.translate('expressions.functions.button.args.href', { defaultMessage: 'Link to which to navigate', }), types: ['string'], required: true, }, name: { - help: i18n.translate('expressions.functions.font.args.name', { + help: i18n.translate('expressions.functions.button.args.name', { defaultMessage: 'Name of the button', }), types: ['string'], diff --git a/examples/expressions_explorer/public/renderers/button.tsx b/examples/expressions_explorer/public/renderers/button.tsx index 32f1f31894dce..8cf3841fa2289 100644 --- a/examples/expressions_explorer/public/renderers/button.tsx +++ b/examples/expressions_explorer/public/renderers/button.tsx @@ -26,8 +26,17 @@ export const buttonRenderer: ExpressionRenderDefinition = { }; const renderDebug = () => ( -
- +
+ {config.name}
diff --git a/examples/search_examples/server/plugin.ts b/examples/search_examples/server/plugin.ts index e7ee311c8d652..595af844b0997 100644 --- a/examples/search_examples/server/plugin.ts +++ b/examples/search_examples/server/plugin.ts @@ -12,10 +12,9 @@ import type { CoreStart, Plugin, Logger, - RequestHandlerContext, } from 'src/core/server'; -import type { DataApiRequestHandlerContext } from 'src/plugins/data/server'; +import type { DataRequestHandlerContext } from 'src/plugins/data/server'; import { SearchExamplesPluginSetup, @@ -45,9 +44,7 @@ export class SearchExamplesPlugin deps: SearchExamplesPluginSetupDeps ) { this.logger.debug('search_examples: Setup'); - const router = core.http.createRouter< - RequestHandlerContext & { search: DataApiRequestHandlerContext } - >(); + const router = core.http.createRouter(); core.getStartServices().then(([_, depsStart]) => { const myStrategy = mySearchStrategyProvider(depsStart.data); diff --git a/examples/search_examples/server/routes/register_routes.ts b/examples/search_examples/server/routes/register_routes.ts index d7a18509b9a79..1159864bed31a 100644 --- a/examples/search_examples/server/routes/register_routes.ts +++ b/examples/search_examples/server/routes/register_routes.ts @@ -6,12 +6,10 @@ * Public License, v 1. */ -import type { IRouter, RequestHandlerContext } from 'kibana/server'; -import { DataApiRequestHandlerContext } from 'src/plugins/data/server'; +import type { IRouter } from 'kibana/server'; +import { DataRequestHandlerContext } from 'src/plugins/data/server'; import { registerServerSearchRoute } from './server_search_route'; -export function registerRoutes( - router: IRouter -) { +export function registerRoutes(router: IRouter) { registerServerSearchRoute(router); } diff --git a/examples/search_examples/server/routes/server_search_route.ts b/examples/search_examples/server/routes/server_search_route.ts index 99a1aba99d8ec..b4805a8de0d3c 100644 --- a/examples/search_examples/server/routes/server_search_route.ts +++ b/examples/search_examples/server/routes/server_search_route.ts @@ -9,13 +9,11 @@ import { IEsSearchRequest } from 'src/plugins/data/server'; import { schema } from '@kbn/config-schema'; import { IEsSearchResponse } from 'src/plugins/data/common'; -import type { DataApiRequestHandlerContext } from 'src/plugins/data/server'; -import type { IRouter, RequestHandlerContext } from 'src/core/server'; +import type { DataRequestHandlerContext } from 'src/plugins/data/server'; +import type { IRouter } from 'src/core/server'; import { SERVER_SEARCH_ROUTE_PATH } from '../../common'; -export function registerServerSearchRoute( - router: IRouter -) { +export function registerServerSearchRoute(router: IRouter) { router.get( { path: SERVER_SEARCH_ROUTE_PATH, diff --git a/package.json b/package.json index 920e0c8ba5192..27cbbf3fb1299 100644 --- a/package.json +++ b/package.json @@ -488,7 +488,7 @@ "@types/mime": "^2.0.1", "@types/mime-types": "^2.1.0", "@types/minimatch": "^2.0.29", - "@types/mocha": "^7.0.2", + "@types/mocha": "^8.2.0", "@types/mock-fs": "^4.10.0", "@types/moment-timezone": "^0.5.12", "@types/mustache": "^0.8.31", @@ -568,6 +568,7 @@ "@welldone-software/why-did-you-render": "^5.0.0", "@yarnpkg/lockfile": "^1.1.0", "abab": "^2.0.4", + "aggregate-error": "^3.1.0", "angular-aria": "^1.8.0", "angular-mocks": "^1.7.9", "angular-recursion": "^1.0.5", @@ -730,10 +731,10 @@ "micromatch": "3.1.10", "minimist": "^1.2.5", "mkdirp": "0.5.1", - "mocha": "^7.1.1", - "mocha-junit-reporter": "^1.23.1", - "mochawesome": "^4.1.0", - "mochawesome-merge": "^4.1.0", + "mocha": "^8.2.1", + "mocha-junit-reporter": "^2.0.0", + "mochawesome": "^6.2.1", + "mochawesome-merge": "^4.2.0", "mock-fs": "^4.12.0", "mock-http-server": "1.3.0", "ms-chromium-edge-driver": "^0.2.3", diff --git a/packages/kbn-dev-utils/stdio/package.json b/packages/kbn-dev-utils/stdio/package.json new file mode 100644 index 0000000000000..415ab0e6de316 --- /dev/null +++ b/packages/kbn-dev-utils/stdio/package.json @@ -0,0 +1,3 @@ +{ + "main": "../target/stdio" +} \ No newline at end of file diff --git a/packages/kbn-es-archiver/src/actions/empty_kibana_index.ts b/packages/kbn-es-archiver/src/actions/empty_kibana_index.ts index 6272d6ba00ee8..db7c79c7dfc50 100644 --- a/packages/kbn-es-archiver/src/actions/empty_kibana_index.ts +++ b/packages/kbn-es-archiver/src/actions/empty_kibana_index.ts @@ -6,7 +6,7 @@ * Public License, v 1. */ -import { Client } from 'elasticsearch'; +import { Client } from '@elastic/elasticsearch'; import { ToolingLog, KbnClient } from '@kbn/dev-utils'; import { migrateKibanaIndex, createStats, cleanKibanaIndices } from '../lib'; @@ -25,6 +25,5 @@ export async function emptyKibanaIndexAction({ await cleanKibanaIndices({ client, stats, log, kibanaPluginIds }); await migrateKibanaIndex({ client, kbnClient }); - stats.createdIndex('.kibana'); - return stats.toJSON(); + return stats; } diff --git a/packages/kbn-es-archiver/src/actions/load.ts b/packages/kbn-es-archiver/src/actions/load.ts index 8afd7d79a98f7..592fb3ff90af8 100644 --- a/packages/kbn-es-archiver/src/actions/load.ts +++ b/packages/kbn-es-archiver/src/actions/load.ts @@ -10,7 +10,7 @@ import { resolve } from 'path'; import { createReadStream } from 'fs'; import { Readable } from 'stream'; import { ToolingLog, KbnClient } from '@kbn/dev-utils'; -import { Client } from 'elasticsearch'; +import { Client } from '@elastic/elasticsearch'; import { createPromiseFromStreams, concatStreamProviders } from '@kbn/utils'; @@ -92,15 +92,17 @@ export async function loadAction({ await client.indices.refresh({ index: '_all', - allowNoIndices: true, + allow_no_indices: true, }); // If we affected the Kibana index, we need to ensure it's migrated... if (Object.keys(result).some((k) => k.startsWith('.kibana'))) { await migrateKibanaIndex({ client, kbnClient }); + log.debug('[%s] Migrated Kibana index after loading Kibana data', name); if (kibanaPluginIds.includes('spaces')) { await createDefaultSpace({ client, index: '.kibana' }); + log.debug('[%s] Ensured that default space exists in .kibana', name); } } diff --git a/packages/kbn-es-archiver/src/actions/save.ts b/packages/kbn-es-archiver/src/actions/save.ts index d88871f5b4222..63b09ef8981a2 100644 --- a/packages/kbn-es-archiver/src/actions/save.ts +++ b/packages/kbn-es-archiver/src/actions/save.ts @@ -9,7 +9,7 @@ import { resolve } from 'path'; import { createWriteStream, mkdirSync } from 'fs'; import { Readable, Writable } from 'stream'; -import { Client } from 'elasticsearch'; +import { Client } from '@elastic/elasticsearch'; import { ToolingLog } from '@kbn/dev-utils'; import { createListStream, createPromiseFromStreams } from '@kbn/utils'; diff --git a/packages/kbn-es-archiver/src/actions/unload.ts b/packages/kbn-es-archiver/src/actions/unload.ts index 07cbf2aec39ff..94b8387d3df02 100644 --- a/packages/kbn-es-archiver/src/actions/unload.ts +++ b/packages/kbn-es-archiver/src/actions/unload.ts @@ -9,7 +9,7 @@ import { resolve } from 'path'; import { createReadStream } from 'fs'; import { Readable, Writable } from 'stream'; -import { Client } from 'elasticsearch'; +import { Client } from '@elastic/elasticsearch'; import { ToolingLog, KbnClient } from '@kbn/dev-utils'; import { createPromiseFromStreams } from '@kbn/utils'; diff --git a/packages/kbn-es-archiver/src/cli.ts b/packages/kbn-es-archiver/src/cli.ts index e3114b4e78cf4..cba2a25b9e274 100644 --- a/packages/kbn-es-archiver/src/cli.ts +++ b/packages/kbn-es-archiver/src/cli.ts @@ -19,7 +19,7 @@ import Fs from 'fs'; import { RunWithCommands, createFlagError, KbnClient, CA_CERT_PATH } from '@kbn/dev-utils'; import { readConfigFile } from '@kbn/test'; -import legacyElasticsearch from 'elasticsearch'; +import { Client } from '@elastic/elasticsearch'; import { EsArchiver } from './es_archiver'; @@ -115,10 +115,9 @@ export function runCli() { throw createFlagError('--dir or --config must be defined'); } - const client = new legacyElasticsearch.Client({ - host: esUrl, + const client = new Client({ + node: esUrl, ssl: esCa ? { ca: esCa } : undefined, - log: flags.verbose ? 'trace' : [], }); addCleanupTask(() => client.close()); diff --git a/packages/kbn-es-archiver/src/es_archiver.ts b/packages/kbn-es-archiver/src/es_archiver.ts index 8601dedad0e27..9176de60544f6 100644 --- a/packages/kbn-es-archiver/src/es_archiver.ts +++ b/packages/kbn-es-archiver/src/es_archiver.ts @@ -6,7 +6,7 @@ * Public License, v 1. */ -import { Client } from 'elasticsearch'; +import { Client } from '@elastic/elasticsearch'; import { ToolingLog, KbnClient } from '@kbn/dev-utils'; import { @@ -155,7 +155,7 @@ export class EsArchiver { * @return Promise */ async emptyKibanaIndex() { - return await emptyKibanaIndexAction({ + await emptyKibanaIndexAction({ client: this.client, log: this.log, kbnClient: this.kbnClient, diff --git a/packages/kbn-es-archiver/src/lib/docs/__mocks__/stubs.ts b/packages/kbn-es-archiver/src/lib/docs/__mocks__/stubs.ts deleted file mode 100644 index 3cdf3e24c328b..0000000000000 --- a/packages/kbn-es-archiver/src/lib/docs/__mocks__/stubs.ts +++ /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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { Client } from 'elasticsearch'; -import sinon from 'sinon'; -import Chance from 'chance'; -import { times } from 'lodash'; - -import { Stats } from '../../stats'; - -const chance = new Chance(); - -export const createStubStats = (): Stats => - ({ - indexedDoc: sinon.stub(), - archivedDoc: sinon.stub(), - } as any); - -export const createPersonDocRecords = (n: number) => - times(n, () => ({ - type: 'doc', - value: { - index: 'people', - type: 'person', - id: chance.natural(), - source: { - name: chance.name(), - birthday: chance.birthday(), - ssn: chance.ssn(), - }, - }, - })); - -type MockClient = Client & { - assertNoPendingResponses: () => void; -}; - -export const createStubClient = ( - responses: Array<(name: string, params: any) => any | Promise> = [] -): MockClient => { - const createStubClientMethod = (name: string) => - sinon.spy(async (params) => { - if (responses.length === 0) { - throw new Error(`unexpected client.${name} call`); - } - - const response = responses.shift(); - return await response!(name, params); - }); - - return { - search: createStubClientMethod('search'), - scroll: createStubClientMethod('scroll'), - bulk: createStubClientMethod('bulk'), - - assertNoPendingResponses() { - if (responses.length) { - throw new Error(`There are ${responses.length} unsent responses.`); - } - }, - } as any; -}; diff --git a/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.test.ts b/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.test.ts index e28711ad140ba..217c5aaf767ff 100644 --- a/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.test.ts +++ b/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.test.ts @@ -6,128 +6,185 @@ * Public License, v 1. */ -import sinon from 'sinon'; -import { delay } from 'bluebird'; -import { createListStream, createPromiseFromStreams, createConcatStream } from '@kbn/utils'; +import { + createListStream, + createPromiseFromStreams, + createConcatStream, + createMapStream, + ToolingLog, +} from '@kbn/dev-utils'; import { createGenerateDocRecordsStream } from './generate_doc_records_stream'; import { Progress } from '../progress'; -import { createStubStats, createStubClient } from './__mocks__/stubs'; +import { createStats } from '../stats'; -describe('esArchiver: createGenerateDocRecordsStream()', () => { - it('scolls 1000 documents at a time', async () => { - const stats = createStubStats(); - const client = createStubClient([ - (name, params) => { - expect(name).toBe('search'); - expect(params).toHaveProperty('index', 'logstash-*'); - expect(params).toHaveProperty('size', 1000); - return { +const log = new ToolingLog(); + +it('transforms each input index to a stream of docs using scrollSearch helper', async () => { + const responses: any = { + foo: [ + { + body: { hits: { - total: 0, - hits: [], + total: 5, + hits: [ + { _index: 'foo', _type: '_doc', _id: '0', _source: {} }, + { _index: 'foo', _type: '_doc', _id: '1', _source: {} }, + { _index: 'foo', _type: '_doc', _id: '2', _source: {} }, + ], }, - }; + }, }, - ]); - - const progress = new Progress(); - await createPromiseFromStreams([ - createListStream(['logstash-*']), - createGenerateDocRecordsStream({ client, stats, progress }), - ]); - - expect(progress.getTotal()).toBe(0); - expect(progress.getComplete()).toBe(0); - }); - - it('uses a 1 minute scroll timeout', async () => { - const stats = createStubStats(); - const client = createStubClient([ - (name, params) => { - expect(name).toBe('search'); - expect(params).toHaveProperty('index', 'logstash-*'); - expect(params).toHaveProperty('scroll', '1m'); - expect(params).toHaveProperty('rest_total_hits_as_int', true); - return { + { + body: { hits: { - total: 0, - hits: [], + total: 5, + hits: [ + { _index: 'foo', _type: '_doc', _id: '3', _source: {} }, + { _index: 'foo', _type: '_doc', _id: '4', _source: {} }, + ], }, - }; + }, }, - ]); - - const progress = new Progress(); - await createPromiseFromStreams([ - createListStream(['logstash-*']), - createGenerateDocRecordsStream({ client, stats, progress }), - ]); + ], + bar: [ + { + body: { + hits: { + total: 2, + hits: [ + { _index: 'bar', _type: '_doc', _id: '0', _source: {} }, + { _index: 'bar', _type: '_doc', _id: '1', _source: {} }, + ], + }, + }, + }, + ], + }; - expect(progress.getTotal()).toBe(0); - expect(progress.getComplete()).toBe(0); - }); + const client: any = { + helpers: { + scrollSearch: jest.fn(function* ({ index }) { + while (responses[index] && responses[index].length) { + yield responses[index].shift()!; + } + }), + }, + }; - it('consumes index names and scrolls completely before continuing', async () => { - const stats = createStubStats(); - let checkpoint = Date.now(); - const client = createStubClient([ - async (name, params) => { - expect(name).toBe('search'); - expect(params).toHaveProperty('index', 'index1'); - await delay(200); - return { - _scroll_id: 'index1ScrollId', - hits: { total: 2, hits: [{ _id: 1, _index: '.kibana_foo' }] }, - }; - }, - async (name, params) => { - expect(name).toBe('scroll'); - expect(params).toHaveProperty('scrollId', 'index1ScrollId'); - expect(Date.now() - checkpoint).not.toBeLessThan(200); - checkpoint = Date.now(); - await delay(200); - return { hits: { total: 2, hits: [{ _id: 2, _index: 'foo' }] } }; - }, - async (name, params) => { - expect(name).toBe('search'); - expect(params).toHaveProperty('index', 'index2'); - expect(Date.now() - checkpoint).not.toBeLessThan(200); - checkpoint = Date.now(); - await delay(200); - return { hits: { total: 0, hits: [] } }; - }, - ]); + const stats = createStats('test', log); + const progress = new Progress(); - const progress = new Progress(); - const docRecords = await createPromiseFromStreams([ - createListStream(['index1', 'index2']), - createGenerateDocRecordsStream({ client, stats, progress }), - createConcatStream([]), - ]); + const results = await createPromiseFromStreams([ + createListStream(['bar', 'foo']), + createGenerateDocRecordsStream({ + client, + stats, + progress, + }), + createMapStream((record: any) => { + expect(record).toHaveProperty('type', 'doc'); + expect(record.value.source).toEqual({}); + expect(record.value.type).toBe('_doc'); + expect(record.value.index).toMatch(/^(foo|bar)$/); + expect(record.value.id).toMatch(/^\d+$/); + return `${record.value.index}:${record.value.id}`; + }), + createConcatStream([]), + ]); - expect(docRecords).toEqual([ - { - type: 'doc', - value: { - index: '.kibana_1', - type: undefined, - id: 1, - source: undefined, + expect(client.helpers.scrollSearch).toMatchInlineSnapshot(` + [MockFunction] { + "calls": Array [ + Array [ + Object { + "_source": "true", + "body": Object { + "query": undefined, + }, + "index": "bar", + "rest_total_hits_as_int": true, + "scroll": "1m", + "size": 1000, + }, + ], + Array [ + Object { + "_source": "true", + "body": Object { + "query": undefined, + }, + "index": "foo", + "rest_total_hits_as_int": true, + "scroll": "1m", + "size": 1000, + }, + ], + ], + "results": Array [ + Object { + "type": "return", + "value": Object {}, + }, + Object { + "type": "return", + "value": Object {}, + }, + ], + } + `); + expect(results).toMatchInlineSnapshot(` + Array [ + "bar:0", + "bar:1", + "foo:0", + "foo:1", + "foo:2", + "foo:3", + "foo:4", + ] + `); + expect(progress).toMatchInlineSnapshot(` + Progress { + "complete": 7, + "loggingInterval": undefined, + "total": 7, + } + `); + expect(stats).toMatchInlineSnapshot(` + Object { + "bar": Object { + "archived": false, + "configDocs": Object { + "tagged": 0, + "upToDate": 0, + "upgraded": 0, }, + "created": false, + "deleted": false, + "docs": Object { + "archived": 2, + "indexed": 0, + }, + "skipped": false, + "waitForSnapshot": 0, }, - { - type: 'doc', - value: { - index: 'foo', - type: undefined, - id: 2, - source: undefined, + "foo": Object { + "archived": false, + "configDocs": Object { + "tagged": 0, + "upToDate": 0, + "upgraded": 0, + }, + "created": false, + "deleted": false, + "docs": Object { + "archived": 5, + "indexed": 0, }, + "skipped": false, + "waitForSnapshot": 0, }, - ]); - sinon.assert.calledTwice(stats.archivedDoc as any); - expect(progress.getTotal()).toBe(2); - expect(progress.getComplete()).toBe(2); - }); + } + `); }); diff --git a/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.ts b/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.ts index 7c236214fb031..a375753628e44 100644 --- a/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.ts +++ b/packages/kbn-es-archiver/src/lib/docs/generate_doc_records_stream.ts @@ -7,7 +7,7 @@ */ import { Transform } from 'stream'; -import { Client, SearchParams, SearchResponse } from 'elasticsearch'; +import { Client } from '@elastic/elasticsearch'; import { Stats } from '../stats'; import { Progress } from '../progress'; @@ -30,31 +30,26 @@ export function createGenerateDocRecordsStream({ readableObjectMode: true, async transform(index, enc, callback) { try { - let remainingHits = 0; - let resp: SearchResponse | null = null; + const interator = client.helpers.scrollSearch({ + index, + scroll: SCROLL_TIMEOUT, + size: SCROLL_SIZE, + _source: 'true', + body: { + query, + }, + rest_total_hits_as_int: true, + }); - while (!resp || remainingHits > 0) { - if (!resp) { - resp = await client.search({ - index, - scroll: SCROLL_TIMEOUT, - size: SCROLL_SIZE, - _source: true, - body: { - query, - }, - rest_total_hits_as_int: true, // not declared on SearchParams type - } as SearchParams); - remainingHits = resp.hits.total; + let remainingHits: number | null = null; + + for await (const resp of interator) { + if (remainingHits === null) { + remainingHits = resp.body.hits.total as number; progress.addToTotal(remainingHits); - } else { - resp = await client.scroll({ - scrollId: resp._scroll_id!, - scroll: SCROLL_TIMEOUT, - }); } - for (const hit of resp.hits.hits) { + for (const hit of resp.body.hits.hits) { remainingHits -= 1; stats.archivedDoc(hit._index); this.push({ @@ -70,7 +65,7 @@ export function createGenerateDocRecordsStream({ }); } - progress.addToComplete(resp.hits.hits.length); + progress.addToComplete(resp.body.hits.hits.length); } callback(undefined); diff --git a/packages/kbn-es-archiver/src/lib/docs/index_doc_records_stream.test.ts b/packages/kbn-es-archiver/src/lib/docs/index_doc_records_stream.test.ts index b2b8d9d310ab3..a86359262bd41 100644 --- a/packages/kbn-es-archiver/src/lib/docs/index_doc_records_stream.test.ts +++ b/packages/kbn-es-archiver/src/lib/docs/index_doc_records_stream.test.ts @@ -6,170 +6,278 @@ * Public License, v 1. */ -import { delay } from 'bluebird'; -import { createListStream, createPromiseFromStreams } from '@kbn/utils'; +import { + createListStream, + createPromiseFromStreams, + ToolingLog, + createRecursiveSerializer, +} from '@kbn/dev-utils'; import { Progress } from '../progress'; import { createIndexDocRecordsStream } from './index_doc_records_stream'; -import { createStubStats, createStubClient, createPersonDocRecords } from './__mocks__/stubs'; - -const recordsToBulkBody = (records: any[]) => { - return records.reduce((acc, record) => { - const { index, id, source } = record.value; - - return [...acc, { index: { _index: index, _id: id } }, source]; - }, [] as any[]); -}; - -describe('esArchiver: createIndexDocRecordsStream()', () => { - it('consumes doc records and sends to `_bulk` api', async () => { - const records = createPersonDocRecords(1); - const client = createStubClient([ - async (name, params) => { - expect(name).toBe('bulk'); - expect(params).toEqual({ - body: recordsToBulkBody(records), - requestTimeout: 120000, - }); - return { ok: true }; - }, - ]); - const stats = createStubStats(); - const progress = new Progress(); +import { createStats } from '../stats'; - await createPromiseFromStreams([ - createListStream(records), - createIndexDocRecordsStream(client, stats, progress), - ]); +const AT_LINE_RE = /^\s+at /m; - client.assertNoPendingResponses(); - expect(progress.getComplete()).toBe(1); - expect(progress.getTotal()).toBe(undefined); - }); +expect.addSnapshotSerializer( + createRecursiveSerializer( + (v) => typeof v === 'string' && AT_LINE_RE.test(v), + (v: string) => { + const lines = v.split('\n'); + const withoutStack: string[] = []; - it('consumes multiple doc records and sends to `_bulk` api together', async () => { - const records = createPersonDocRecords(10); - const client = createStubClient([ - async (name, params) => { - expect(name).toBe('bulk'); - expect(params).toEqual({ - body: recordsToBulkBody(records.slice(0, 1)), - requestTimeout: 120000, - }); - return { ok: true }; + // move source lines to withoutStack, filtering out stacktrace lines + while (lines.length) { + const line = lines.shift()!; + + if (!AT_LINE_RE.test(line)) { + withoutStack.push(line); + } else { + // push in representation of stack trace indented to match "at" + withoutStack.push(`${' '.repeat(line.indexOf('at'))}`); + + // shift off all subsequent `at ...` lines + while (lines.length && AT_LINE_RE.test(lines[0])) { + lines.shift(); + } + } + } + + return withoutStack.join('\n'); + } + ) +); + +const log = new ToolingLog(); + +class MockClient { + helpers = { + bulk: jest.fn(), + }; +} + +const testRecords = [ + { + type: 'doc', + value: { + index: 'foo', + id: '0', + source: { + hello: 'world', }, - async (name, params) => { - expect(name).toBe('bulk'); - expect(params).toEqual({ - body: recordsToBulkBody(records.slice(1)), - requestTimeout: 120000, - }); - return { ok: true }; + }, + }, + { + type: 'doc', + value: { + index: 'foo', + id: '1', + source: { + hello: 'world', }, - ]); - const stats = createStubStats(); - const progress = new Progress(); + }, + }, + { + type: 'doc', + value: { + index: 'foo', + id: '2', + source: { + hello: 'world', + }, + }, + }, + { + type: 'doc', + value: { + index: 'foo', + id: '3', + source: { + hello: 'world', + }, + }, + }, +]; - await createPromiseFromStreams([ - createListStream(records), - createIndexDocRecordsStream(client, stats, progress), - ]); +it('indexes documents using the bulk client helper', async () => { + const client = new MockClient(); + client.helpers.bulk.mockImplementation(async () => {}); - client.assertNoPendingResponses(); - expect(progress.getComplete()).toBe(10); - expect(progress.getTotal()).toBe(undefined); - }); + const progress = new Progress(); + const stats = createStats('test', log); - it('waits until request is complete before sending more', async () => { - const records = createPersonDocRecords(10); - const stats = createStubStats(); - const start = Date.now(); - const delayMs = 1234; - const client = createStubClient([ - async (name, params) => { - expect(name).toBe('bulk'); - expect(params).toEqual({ - body: recordsToBulkBody(records.slice(0, 1)), - requestTimeout: 120000, - }); - await delay(delayMs); - return { ok: true }; + await createPromiseFromStreams([ + createListStream(testRecords), + createIndexDocRecordsStream(client as any, stats, progress), + ]); + + expect(stats).toMatchInlineSnapshot(` + Object { + "foo": Object { + "archived": false, + "configDocs": Object { + "tagged": 0, + "upToDate": 0, + "upgraded": 0, + }, + "created": false, + "deleted": false, + "docs": Object { + "archived": 0, + "indexed": 4, + }, + "skipped": false, + "waitForSnapshot": 0, }, - async (name, params) => { - expect(name).toBe('bulk'); - expect(params).toEqual({ - body: recordsToBulkBody(records.slice(1)), - requestTimeout: 120000, + } + `); + expect(progress).toMatchInlineSnapshot(` + Progress { + "complete": 4, + "loggingInterval": undefined, + "total": undefined, + } + `); + expect(client.helpers.bulk).toMatchInlineSnapshot(` + [MockFunction] { + "calls": Array [ + Array [ + Object { + "datasource": Array [ + Object { + "hello": "world", + }, + ], + "onDocument": [Function], + "onDrop": [Function], + "retries": 5, + }, + ], + Array [ + Object { + "datasource": Array [ + Object { + "hello": "world", + }, + Object { + "hello": "world", + }, + Object { + "hello": "world", + }, + ], + "onDocument": [Function], + "onDrop": [Function], + "retries": 5, + }, + ], + ], + "results": Array [ + Object { + "type": "return", + "value": Promise {}, + }, + Object { + "type": "return", + "value": Promise {}, + }, + ], + } + `); +}); + +describe('bulk helper onDocument param', () => { + it('returns index ops for each doc', async () => { + expect.assertions(testRecords.length); + + const client = new MockClient(); + client.helpers.bulk.mockImplementation(async ({ datasource, onDocument }) => { + for (const d of datasource) { + const op = onDocument(d); + expect(op).toEqual({ + index: { + _index: 'foo', + _id: expect.stringMatching(/^\d$/), + }, }); - expect(Date.now() - start).not.toBeLessThan(delayMs); - return { ok: true }; - }, - ]); + } + }); + + const stats = createStats('test', log); const progress = new Progress(); await createPromiseFromStreams([ - createListStream(records), - createIndexDocRecordsStream(client, stats, progress), + createListStream(testRecords), + createIndexDocRecordsStream(client as any, stats, progress), ]); - - client.assertNoPendingResponses(); - expect(progress.getComplete()).toBe(10); - expect(progress.getTotal()).toBe(undefined); }); - it('sends a maximum of 300 documents at a time', async () => { - const records = createPersonDocRecords(301); - const stats = createStubStats(); - const client = createStubClient([ - async (name, params) => { - expect(name).toBe('bulk'); - expect(params.body.length).toEqual(1 * 2); - return { ok: true }; - }, - async (name, params) => { - expect(name).toBe('bulk'); - expect(params.body.length).toEqual(299 * 2); - return { ok: true }; - }, - async (name, params) => { - expect(name).toBe('bulk'); - expect(params.body.length).toEqual(1 * 2); - return { ok: true }; - }, - ]); + it('returns create ops for each doc when instructed', async () => { + expect.assertions(testRecords.length); + + const client = new MockClient(); + client.helpers.bulk.mockImplementation(async ({ datasource, onDocument }) => { + for (const d of datasource) { + const op = onDocument(d); + expect(op).toEqual({ + create: { + _index: 'foo', + _id: expect.stringMatching(/^\d$/), + }, + }); + } + }); + + const stats = createStats('test', log); const progress = new Progress(); await createPromiseFromStreams([ - createListStream(records), - createIndexDocRecordsStream(client, stats, progress), + createListStream(testRecords), + createIndexDocRecordsStream(client as any, stats, progress, true), ]); - - client.assertNoPendingResponses(); - expect(progress.getComplete()).toBe(301); - expect(progress.getTotal()).toBe(undefined); }); +}); - it('emits an error if any request fails', async () => { - const records = createPersonDocRecords(2); - const stats = createStubStats(); - const client = createStubClient([ - async () => ({ ok: true }), - async () => ({ errors: true, forcedError: true }), - ]); +describe('bulk helper onDrop param', () => { + it('throws an error reporting any docs which failed all retry attempts', async () => { + const client = new MockClient(); + let counter = -1; + client.helpers.bulk.mockImplementation(async ({ datasource, onDrop }) => { + for (const d of datasource) { + counter++; + if (counter > 0) { + onDrop({ + document: d, + error: { + reason: `${counter} conflicts with something`, + }, + }); + } + } + }); + + const stats = createStats('test', log); const progress = new Progress(); - try { - await createPromiseFromStreams([ - createListStream(records), - createIndexDocRecordsStream(client, stats, progress), - ]); - throw new Error('expected stream to emit error'); - } catch (err) { - expect(err.message).toMatch(/"forcedError":\s*true/); - } + const promise = createPromiseFromStreams([ + createListStream(testRecords), + createIndexDocRecordsStream(client as any, stats, progress), + ]); - client.assertNoPendingResponses(); - expect(progress.getComplete()).toBe(1); - expect(progress.getTotal()).toBe(undefined); + await expect(promise).rejects.toThrowErrorMatchingInlineSnapshot(` + " + Error: Bulk doc failure [operation=index]: + doc: {\\"hello\\":\\"world\\"} + error: {\\"reason\\":\\"1 conflicts with something\\"} + + Error: Bulk doc failure [operation=index]: + doc: {\\"hello\\":\\"world\\"} + error: {\\"reason\\":\\"2 conflicts with something\\"} + + Error: Bulk doc failure [operation=index]: + doc: {\\"hello\\":\\"world\\"} + error: {\\"reason\\":\\"3 conflicts with something\\"} + " + `); }); }); diff --git a/packages/kbn-es-archiver/src/lib/docs/index_doc_records_stream.ts b/packages/kbn-es-archiver/src/lib/docs/index_doc_records_stream.ts index 873ea881382bc..75e96fbbc9229 100644 --- a/packages/kbn-es-archiver/src/lib/docs/index_doc_records_stream.ts +++ b/packages/kbn-es-archiver/src/lib/docs/index_doc_records_stream.ts @@ -6,7 +6,8 @@ * Public License, v 1. */ -import { Client } from 'elasticsearch'; +import { Client } from '@elastic/elasticsearch'; +import AggregateError from 'aggregate-error'; import { Writable } from 'stream'; import { Stats } from '../stats'; import { Progress } from '../progress'; @@ -18,24 +19,38 @@ export function createIndexDocRecordsStream( useCreate: boolean = false ) { async function indexDocs(docs: any[]) { - const body: any[] = []; const operation = useCreate === true ? 'create' : 'index'; - docs.forEach((doc) => { - stats.indexedDoc(doc.index); - body.push( - { + const ops = new WeakMap(); + const errors: string[] = []; + + await client.helpers.bulk({ + retries: 5, + datasource: docs.map((doc) => { + const body = doc.source; + ops.set(body, { [operation]: { _index: doc.index, _id: doc.id, }, - }, - doc.source - ); + }); + return body; + }), + onDocument(doc) { + return ops.get(doc); + }, + onDrop(dropped) { + const dj = JSON.stringify(dropped.document); + const ej = JSON.stringify(dropped.error); + errors.push(`Bulk doc failure [operation=${operation}]:\n doc: ${dj}\n error: ${ej}`); + }, }); - const resp = await client.bulk({ requestTimeout: 2 * 60 * 1000, body }); - if (resp.errors) { - throw new Error(`Failed to index all documents: ${JSON.stringify(resp, null, 2)}`); + if (errors.length) { + throw new AggregateError(errors); + } + + for (const doc of docs) { + stats.indexedDoc(doc.index); } } diff --git a/packages/kbn-es-archiver/src/lib/indices/__mocks__/stubs.ts b/packages/kbn-es-archiver/src/lib/indices/__mocks__/stubs.ts index 4d42daa71ef24..de5fbd15c1f6b 100644 --- a/packages/kbn-es-archiver/src/lib/indices/__mocks__/stubs.ts +++ b/packages/kbn-es-archiver/src/lib/indices/__mocks__/stubs.ts @@ -6,7 +6,7 @@ * Public License, v 1. */ -import { Client } from 'elasticsearch'; +import { Client } from '@elastic/elasticsearch'; import sinon from 'sinon'; import { ToolingLog } from '@kbn/dev-utils'; import { Stats } from '../../stats'; @@ -54,9 +54,11 @@ export const createStubDocRecord = (index: string, id: number) => ({ const createEsClientError = (errorType: string) => { const err = new Error(`ES Client Error Stub "${errorType}"`); - (err as any).body = { - error: { - type: errorType, + (err as any).meta = { + body: { + error: { + type: errorType, + }, }, }; return err; @@ -79,26 +81,25 @@ export const createStubClient = ( } return { - [index]: { - mappings: {}, - settings: {}, + body: { + [index]: { + mappings: {}, + settings: {}, + }, }, }; }), - existsAlias: sinon.spy(({ name }) => { - return Promise.resolve(aliases.hasOwnProperty(name)); - }), getAlias: sinon.spy(async ({ index, name }) => { if (index && existingIndices.indexOf(index) >= 0) { const result = indexAlias(aliases, index); - return { [index]: { aliases: result ? { [result]: {} } : {} } }; + return { body: { [index]: { aliases: result ? { [result]: {} } : {} } } }; } if (name && aliases[name]) { - return { [aliases[name]]: { aliases: { [name]: {} } } }; + return { body: { [aliases[name]]: { aliases: { [name]: {} } } } }; } - return { status: 404 }; + return { statusCode: 404 }; }), updateAliases: sinon.spy(async ({ body }) => { body.actions.forEach( @@ -110,14 +111,14 @@ export const createStubClient = ( } ); - return { ok: true }; + return { body: { ok: true } }; }), create: sinon.spy(async ({ index }) => { if (existingIndices.includes(index) || aliases.hasOwnProperty(index)) { throw createEsClientError('resource_already_exists_exception'); } else { existingIndices.push(index); - return { ok: true }; + return { body: { ok: true } }; } }), delete: sinon.spy(async ({ index }) => { @@ -131,7 +132,7 @@ export const createStubClient = ( } }); indices.forEach((ix) => existingIndices.splice(existingIndices.indexOf(ix), 1)); - return { ok: true }; + return { body: { ok: true } }; } else { throw createEsClientError('index_not_found_exception'); } diff --git a/packages/kbn-es-archiver/src/lib/indices/create_index_stream.test.ts b/packages/kbn-es-archiver/src/lib/indices/create_index_stream.test.ts index cf6a643068359..57c1efdbb7124 100644 --- a/packages/kbn-es-archiver/src/lib/indices/create_index_stream.test.ts +++ b/packages/kbn-es-archiver/src/lib/indices/create_index_stream.test.ts @@ -56,15 +56,35 @@ describe('esArchiver: createCreateIndexStream()', () => { createCreateIndexStream({ client, stats, log }), ]); - expect((client.indices.getAlias as sinon.SinonSpy).calledOnce).toBe(true); - expect((client.indices.getAlias as sinon.SinonSpy).args[0][0]).toEqual({ - name: 'existing-index', - ignore: [404], - }); - expect((client.indices.delete as sinon.SinonSpy).calledOnce).toBe(true); - expect((client.indices.delete as sinon.SinonSpy).args[0][0]).toEqual({ - index: ['actual-index'], - }); + expect((client.indices.getAlias as sinon.SinonSpy).args).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "name": Array [ + "existing-index", + ], + }, + Object { + "ignore": Array [ + 404, + ], + }, + ], + ] + `); + + expect((client.indices.delete as sinon.SinonSpy).args).toMatchInlineSnapshot(` + Array [ + Array [ + Object { + "index": Array [ + "actual-index", + ], + }, + ], + ] + `); + sinon.assert.callCount(client.indices.create as sinon.SinonSpy, 3); // one failed create because of existing }); diff --git a/packages/kbn-es-archiver/src/lib/indices/create_index_stream.ts b/packages/kbn-es-archiver/src/lib/indices/create_index_stream.ts index 362e92c5dd76b..ba70a8dc2dfb9 100644 --- a/packages/kbn-es-archiver/src/lib/indices/create_index_stream.ts +++ b/packages/kbn-es-archiver/src/lib/indices/create_index_stream.ts @@ -9,7 +9,7 @@ import { Transform, Readable } from 'stream'; import { inspect } from 'util'; -import { Client } from 'elasticsearch'; +import { Client } from '@elastic/elasticsearch'; import { ToolingLog } from '@kbn/dev-utils'; import { Stats } from '../stats'; @@ -88,7 +88,10 @@ export function createCreateIndexStream({ return; } - if (err?.body?.error?.type !== 'resource_already_exists_exception' || attemptNumber >= 3) { + if ( + err?.meta?.body?.error?.type !== 'resource_already_exists_exception' || + attemptNumber >= 3 + ) { throw err; } diff --git a/packages/kbn-es-archiver/src/lib/indices/delete_index.ts b/packages/kbn-es-archiver/src/lib/indices/delete_index.ts index e42928da2566f..597db5a980de4 100644 --- a/packages/kbn-es-archiver/src/lib/indices/delete_index.ts +++ b/packages/kbn-es-archiver/src/lib/indices/delete_index.ts @@ -6,8 +6,7 @@ * Public License, v 1. */ -import { get } from 'lodash'; -import { Client } from 'elasticsearch'; +import { Client } from '@elastic/elasticsearch'; import { ToolingLog } from '@kbn/dev-utils'; import { Stats } from '../stats'; @@ -17,35 +16,45 @@ const PENDING_SNAPSHOT_STATUSES = ['INIT', 'STARTED', 'WAITING']; export async function deleteIndex(options: { client: Client; stats: Stats; - index: string; + index: string | string[]; log: ToolingLog; retryIfSnapshottingCount?: number; }): Promise { - const { client, stats, index, log, retryIfSnapshottingCount = 10 } = options; + const { client, stats, log, retryIfSnapshottingCount = 10 } = options; + const indices = [options.index].flat(); const getIndicesToDelete = async () => { - const aliasInfo = await client.indices.getAlias({ name: index, ignore: [404] }); - return aliasInfo.status === 404 ? [index] : Object.keys(aliasInfo); + const resp = await client.indices.getAlias( + { + name: indices, + }, + { + ignore: [404], + } + ); + + return resp.statusCode === 404 ? indices : Object.keys(resp.body); }; try { const indicesToDelete = await getIndicesToDelete(); await client.indices.delete({ index: indicesToDelete }); - for (let i = 0; i < indicesToDelete.length; i++) { - const indexToDelete = indicesToDelete[i]; - stats.deletedIndex(indexToDelete); + for (const index of indices) { + stats.deletedIndex(index); } } catch (error) { if (retryIfSnapshottingCount > 0 && isDeleteWhileSnapshotInProgressError(error)) { - stats.waitingForInProgressSnapshot(index); - await waitForSnapshotCompletion(client, index, log); + for (const index of indices) { + stats.waitingForInProgressSnapshot(index); + } + await waitForSnapshotCompletion(client, indices, log); return await deleteIndex({ ...options, retryIfSnapshottingCount: retryIfSnapshottingCount - 1, }); } - if (get(error, 'body.error.type') !== 'index_not_found_exception') { + if (error?.meta?.body?.error?.type !== 'index_not_found_exception') { throw error; } } @@ -57,8 +66,8 @@ export async function deleteIndex(options: { * @param {Error} error * @return {Boolean} */ -export function isDeleteWhileSnapshotInProgressError(error: object) { - return get(error, 'body.error.reason', '').startsWith( +export function isDeleteWhileSnapshotInProgressError(error: any) { + return (error?.meta?.body?.error?.reason ?? '').startsWith( 'Cannot delete indices that are being snapshotted' ); } @@ -67,10 +76,16 @@ export function isDeleteWhileSnapshotInProgressError(error: object) { * Wait for the any snapshot in any repository that is * snapshotting this index to complete. */ -export async function waitForSnapshotCompletion(client: Client, index: string, log: ToolingLog) { +export async function waitForSnapshotCompletion( + client: Client, + index: string | string[], + log: ToolingLog +) { const isSnapshotPending = async (repository: string, snapshot: string) => { const { - snapshots: [status], + body: { + snapshots: [status], + }, } = await client.snapshot.status({ repository, snapshot, @@ -81,10 +96,13 @@ export async function waitForSnapshotCompletion(client: Client, index: string, l }; const getInProgressSnapshots = async (repository: string) => { - const { snapshots: inProgressSnapshots } = await client.snapshot.get({ + const { + body: { snapshots: inProgressSnapshots }, + } = await client.snapshot.get({ repository, snapshot: '_current', }); + return inProgressSnapshots; }; diff --git a/packages/kbn-es-archiver/src/lib/indices/delete_index_stream.ts b/packages/kbn-es-archiver/src/lib/indices/delete_index_stream.ts index 1b3b09afb7833..95633740032fe 100644 --- a/packages/kbn-es-archiver/src/lib/indices/delete_index_stream.ts +++ b/packages/kbn-es-archiver/src/lib/indices/delete_index_stream.ts @@ -7,7 +7,7 @@ */ import { Transform } from 'stream'; -import { Client } from 'elasticsearch'; +import { Client } from '@elastic/elasticsearch'; import { ToolingLog } from '@kbn/dev-utils'; import { Stats } from '../stats'; diff --git a/packages/kbn-es-archiver/src/lib/indices/generate_index_records_stream.test.ts b/packages/kbn-es-archiver/src/lib/indices/generate_index_records_stream.test.ts index e60e6b6d4771b..a526039df45dc 100644 --- a/packages/kbn-es-archiver/src/lib/indices/generate_index_records_stream.test.ts +++ b/packages/kbn-es-archiver/src/lib/indices/generate_index_records_stream.test.ts @@ -44,8 +44,8 @@ describe('esArchiver: createGenerateIndexRecordsStream()', () => { ]); const params = (client.indices.get as sinon.SinonSpy).args[0][0]; - expect(params).toHaveProperty('filterPath'); - const filters: string[] = params.filterPath; + expect(params).toHaveProperty('filter_path'); + const filters: string[] = params.filter_path; expect(filters.some((path) => path.includes('index.creation_date'))).toBe(true); expect(filters.some((path) => path.includes('index.uuid'))).toBe(true); expect(filters.some((path) => path.includes('index.version'))).toBe(true); diff --git a/packages/kbn-es-archiver/src/lib/indices/generate_index_records_stream.ts b/packages/kbn-es-archiver/src/lib/indices/generate_index_records_stream.ts index c1ff48e197449..2e624ef7adbad 100644 --- a/packages/kbn-es-archiver/src/lib/indices/generate_index_records_stream.ts +++ b/packages/kbn-es-archiver/src/lib/indices/generate_index_records_stream.ts @@ -7,7 +7,7 @@ */ import { Transform } from 'stream'; -import { Client } from 'elasticsearch'; +import { Client } from '@elastic/elasticsearch'; import { Stats } from '../stats'; export function createGenerateIndexRecordsStream(client: Client, stats: Stats) { @@ -16,26 +16,30 @@ export function createGenerateIndexRecordsStream(client: Client, stats: Stats) { readableObjectMode: true, async transform(indexOrAlias, enc, callback) { try { - const resp = (await client.indices.get({ - index: indexOrAlias, - filterPath: [ - '*.settings', - '*.mappings', - // remove settings that aren't really settings - '-*.settings.index.creation_date', - '-*.settings.index.uuid', - '-*.settings.index.version', - '-*.settings.index.provided_name', - '-*.settings.index.frozen', - '-*.settings.index.search.throttled', - '-*.settings.index.query', - '-*.settings.index.routing', - ], - })) as Record; + const resp = ( + await client.indices.get({ + index: indexOrAlias, + filter_path: [ + '*.settings', + '*.mappings', + // remove settings that aren't really settings + '-*.settings.index.creation_date', + '-*.settings.index.uuid', + '-*.settings.index.version', + '-*.settings.index.provided_name', + '-*.settings.index.frozen', + '-*.settings.index.search.throttled', + '-*.settings.index.query', + '-*.settings.index.routing', + ], + }) + ).body as Record; for (const [index, { settings, mappings }] of Object.entries(resp)) { const { - [index]: { aliases }, + body: { + [index]: { aliases }, + }, } = await client.indices.getAlias({ index }); stats.archivedIndex(index, { settings, mappings }); diff --git a/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts b/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts index 91c0bd8343a36..d370e49d0bca5 100644 --- a/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts +++ b/packages/kbn-es-archiver/src/lib/indices/kibana_index.ts @@ -6,7 +6,9 @@ * Public License, v 1. */ -import { Client, CreateDocumentParams } from 'elasticsearch'; +import { inspect } from 'util'; + +import { Client } from '@elastic/elasticsearch'; import { ToolingLog, KbnClient } from '@kbn/dev-utils'; import { Stats } from '../stats'; import { deleteIndex } from './delete_index'; @@ -57,13 +59,17 @@ export async function migrateKibanaIndex({ }) { // we allow dynamic mappings on the index, as some interceptors are accessing documents before // the migration is actually performed. The migrator will put the value back to `strict` after migration. - await client.indices.putMapping({ - index: '.kibana', - body: { - dynamic: true, + await client.indices.putMapping( + { + index: '.kibana', + body: { + dynamic: true, + }, }, - ignore: [404], - } as any); + { + ignore: [404], + } + ); await kbnClient.savedObjects.migrate(); } @@ -75,11 +81,14 @@ export async function migrateKibanaIndex({ * index (e.g. we don't want to remove .kibana_task_manager or the like). */ async function fetchKibanaIndices(client: Client) { - const kibanaIndices = await client.cat.indices({ index: '.kibana*', format: 'json' }); - const isKibanaIndex = (index: string) => - /^\.kibana(:?_\d*)?$/.test(index) || - /^\.kibana(_task_manager)?_(pre)?\d+\.\d+\.\d+/.test(index); - return kibanaIndices.map((x: { index: string }) => x.index).filter(isKibanaIndex); + const resp = await client.cat.indices({ index: '.kibana*', format: 'json' }); + const isKibanaIndex = (index: string) => /^\.kibana(:?_\d*)?$/.test(index); + + if (!Array.isArray(resp.body)) { + throw new Error(`expected response to be an array ${inspect(resp.body)}`); + } + + return resp.body.map((x: { index: string }) => x.index).filter(isKibanaIndex); } const delay = (delayInMs: number) => new Promise((resolve) => setTimeout(resolve, delayInMs)); @@ -104,27 +113,31 @@ export async function cleanKibanaIndices({ } while (true) { - const resp = await client.deleteByQuery({ - index: `.kibana,.kibana_task_manager`, - body: { - query: { - bool: { - must_not: { - ids: { - values: ['space:default'], + const resp = await client.deleteByQuery( + { + index: `.kibana`, + body: { + query: { + bool: { + must_not: { + ids: { + values: ['space:default'], + }, }, }, }, }, }, - ignore: [404, 409], - }); + { + ignore: [409], + } + ); - if (resp.total !== resp.deleted) { + if (resp.body.total !== resp.body.deleted) { log.warning( 'delete by query deleted %d of %d total documents, trying again', - resp.deleted, - resp.total + resp.body.deleted, + resp.body.total ); await delay(200); continue; @@ -142,19 +155,23 @@ export async function cleanKibanaIndices({ } export async function createDefaultSpace({ index, client }: { index: string; client: Client }) { - await client.create({ - index, - id: 'space:default', - ignore: 409, - body: { - type: 'space', - updated_at: new Date().toISOString(), - space: { - name: 'Default Space', - description: 'This is the default space', - disabledFeatures: [], - _reserved: true, + await client.create( + { + index, + id: 'space:default', + body: { + type: 'space', + updated_at: new Date().toISOString(), + space: { + name: 'Default Space', + description: 'This is the default space', + disabledFeatures: [], + _reserved: true, + }, }, }, - } as CreateDocumentParams); + { + ignore: [409], + } + ); } diff --git a/packages/kbn-es/src/cluster.js b/packages/kbn-es/src/cluster.js index 5948e9ecececc..7299d0aeda8dd 100644 --- a/packages/kbn-es/src/cluster.js +++ b/packages/kbn-es/src/cluster.js @@ -246,7 +246,7 @@ exports.Cluster = class Cluster { this._log.info(chalk.bold('Starting')); this._log.indent(4); - const esArgs = options.esArgs || []; + const esArgs = ['action.destructive_requires_name=true', ...(options.esArgs || [])]; // Add to esArgs if ssl is enabled if (this._ssl) { diff --git a/packages/kbn-es/src/integration_tests/cluster.test.js b/packages/kbn-es/src/integration_tests/cluster.test.js index 684667355852d..bee3a1e76d118 100644 --- a/packages/kbn-es/src/integration_tests/cluster.test.js +++ b/packages/kbn-es/src/integration_tests/cluster.test.js @@ -264,7 +264,9 @@ describe('#start(installPath)', () => { expect(extractConfigFiles.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - Array [], + Array [ + "action.destructive_requires_name=true", + ], undefined, Object { "log": , @@ -340,7 +342,9 @@ describe('#run()', () => { expect(extractConfigFiles.mock.calls).toMatchInlineSnapshot(` Array [ Array [ - Array [], + Array [ + "action.destructive_requires_name=true", + ], undefined, Object { "log": , diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index 794503656ba04..490c2ccc19d7d 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -53,7 +53,7 @@ pageLoadAssetSize: mapsLegacy: 116817 mapsLegacyLicensing: 20214 ml: 82187 - monitoring: 50000 + monitoring: 80000 navigation: 37269 newsfeed: 42228 observability: 89709 @@ -78,7 +78,7 @@ pageLoadAssetSize: tileMap: 65337 timelion: 29920 transform: 41007 - triggersActionsUi: 170001 + triggersActionsUi: 186732 uiActions: 97717 uiActionsEnhanced: 313011 upgradeAssistant: 81241 diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index df04965cd8c32..35ebe6d302b08 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -94,7 +94,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _cli__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "run", function() { return _cli__WEBPACK_IMPORTED_MODULE_0__["run"]; }); -/* harmony import */ var _production__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(512); +/* harmony import */ var _production__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(519); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _production__WEBPACK_IMPORTED_MODULE_1__["buildProductionProjects"]; }); /* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(248); @@ -106,7 +106,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_package_json__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(251); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "transformDependencies", function() { return _utils_package_json__WEBPACK_IMPORTED_MODULE_4__["transformDependencies"]; }); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(511); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(518); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "getProjectPaths", function() { return _config__WEBPACK_IMPORTED_MODULE_5__["getProjectPaths"]; }); /* @@ -139,7 +139,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(5); /* harmony import */ var _kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_3__); /* harmony import */ var _commands__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(128); -/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(505); +/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(512); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(246); /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one @@ -173,7 +173,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 + --no-cache Disable the kbn packages bootstrap cache --no-validate Disable the bootstrap yarn.lock validation --verbose Set log level to verbose --debug Set log level to debug @@ -207,7 +207,7 @@ async function run(argv) { cache: true, validate: true }, - boolean: ['prefer-offline', 'frozen-lockfile', 'cache', 'validate'] + boolean: ['cache', 'validate'] }); const args = options._; @@ -8827,9 +8827,10 @@ exports.ToolingLogCollectingWriter = ToolingLogCollectingWriter; __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__(129); -/* harmony import */ var _clean__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(373); -/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(404); -/* harmony import */ var _watch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(405); +/* harmony import */ var _clean__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(476); +/* harmony import */ var _reset__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(508); +/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(509); +/* harmony import */ var _watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(510); /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License @@ -8841,11 +8842,13 @@ __webpack_require__.r(__webpack_exports__); + const commands = { bootstrap: _bootstrap__WEBPACK_IMPORTED_MODULE_0__["BootstrapCommand"], clean: _clean__WEBPACK_IMPORTED_MODULE_1__["CleanCommand"], - run: _run__WEBPACK_IMPORTED_MODULE_2__["RunCommand"], - watch: _watch__WEBPACK_IMPORTED_MODULE_3__["WatchCommand"] + reset: _reset__WEBPACK_IMPORTED_MODULE_2__["ResetCommand"], + run: _run__WEBPACK_IMPORTED_MODULE_3__["RunCommand"], + watch: _watch__WEBPACK_IMPORTED_MODULE_4__["WatchCommand"] }; /***/ }), @@ -8895,8 +8898,7 @@ const BootstrapCommand = { var _projects$get; const batchedProjects = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_4__["topologicallyBatchProjects"])(projects, projectGraph); - const kibanaProjectPath = (_projects$get = projects.get('kibana')) === null || _projects$get === void 0 ? void 0 : _projects$get.path; - const extraArgs = [...(options['frozen-lockfile'] === true ? ['--frozen-lockfile'] : []), ...(options['prefer-offline'] === true ? ['--prefer-offline'] : [])]; // Install bazel machinery tools if needed + const kibanaProjectPath = (_projects$get = projects.get('kibana')) === null || _projects$get === void 0 ? void 0 : _projects$get.path; // Install bazel machinery tools if needed await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_9__["installBazelTools"])(rootPath); // Install monorepo npm dependencies @@ -8909,9 +8911,7 @@ const BootstrapCommand = { } if (project.isSinglePackageJsonProject || isExternalPlugin) { - await project.installDependencies({ - extraArgs - }); + await project.installDependencies(); continue; } @@ -23013,12 +23013,10 @@ class Project { return Object.values(this.allDependencies).every(dep => Object(_package_json__WEBPACK_IMPORTED_MODULE_4__["isLinkDependency"])(dep)); } - async installDependencies({ - extraArgs - }) { + async installDependencies(options = {}) { _log__WEBPACK_IMPORTED_MODULE_3__["log"].info(`[${this.name}] running yarn`); _log__WEBPACK_IMPORTED_MODULE_3__["log"].write(''); - await Object(_scripts__WEBPACK_IMPORTED_MODULE_5__["installInDir"])(this.path, extraArgs); + await Object(_scripts__WEBPACK_IMPORTED_MODULE_5__["installInDir"])(this.path, options === null || options === void 0 ? void 0 : options.extraArgs); _log__WEBPACK_IMPORTED_MODULE_3__["log"].write(''); } @@ -47942,8 +47940,16 @@ function addProjectToTree(tree, pathParts, project) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony import */ var _install_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(372); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "installBazelTools", function() { return _install_tools__WEBPACK_IMPORTED_MODULE_0__["installBazelTools"]; }); +/* harmony import */ var _get_cache_folders__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(372); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "getBazelDiskCacheFolder", function() { return _get_cache_folders__WEBPACK_IMPORTED_MODULE_0__["getBazelDiskCacheFolder"]; }); + +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "getBazelRepositoryCacheFolder", function() { return _get_cache_folders__WEBPACK_IMPORTED_MODULE_0__["getBazelRepositoryCacheFolder"]; }); + +/* harmony import */ var _install_tools__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(373); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "installBazelTools", function() { return _install_tools__WEBPACK_IMPORTED_MODULE_1__["installBazelTools"]; }); + +/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(374); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "runBazel", function() { return _run__WEBPACK_IMPORTED_MODULE_2__["runBazel"]; }); /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one @@ -47954,18 +47960,59 @@ __webpack_require__.r(__webpack_exports__); */ + + /***/ }), /* 372 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "installBazelTools", function() { return installBazelTools; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getBazelDiskCacheFolder", function() { return getBazelDiskCacheFolder; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getBazelRepositoryCacheFolder", function() { return getBazelRepositoryCacheFolder; }); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var _child_process__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(319); -/* harmony import */ var _fs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(131); -/* harmony import */ var _log__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(246); +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + + + +async function rawRunBazelInfoRepoCache() { + const { + stdout: bazelRepositoryCachePath + } = await Object(_child_process__WEBPACK_IMPORTED_MODULE_1__["spawn"])('bazel', ['info', 'repository_cache'], { + stdio: 'pipe' + }); + return bazelRepositoryCachePath; +} + +async function getBazelDiskCacheFolder() { + return Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(Object(path__WEBPACK_IMPORTED_MODULE_0__["dirname"])(await rawRunBazelInfoRepoCache()), 'disk-cache'); +} +async function getBazelRepositoryCacheFolder() { + return await rawRunBazelInfoRepoCache(); +} + +/***/ }), +/* 373 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "installBazelTools", function() { return installBazelTools; }); +/* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); +/* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(dedent__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(4); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var _child_process__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(319); +/* harmony import */ var _fs__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(131); +/* harmony import */ var _log__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(246); /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License @@ -47978,8 +48025,9 @@ __webpack_require__.r(__webpack_exports__); + async function readBazelToolsVersionFile(repoRootPath, versionFilename) { - const version = (await Object(_fs__WEBPACK_IMPORTED_MODULE_2__["readFile"])(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(repoRootPath, versionFilename))).toString().split('\n')[0]; + const version = (await Object(_fs__WEBPACK_IMPORTED_MODULE_3__["readFile"])(Object(path__WEBPACK_IMPORTED_MODULE_1__["resolve"])(repoRootPath, versionFilename))).toString().split('\n')[0]; if (!version) { throw new Error(`[bazel_tools] Failed on reading bazel tools versions\n ${versionFilename} file do not contain any version set`); @@ -47988,47 +48036,72 @@ async function readBazelToolsVersionFile(repoRootPath, versionFilename) { return version; } +async function isBazelBinAvailable() { + try { + await Object(_child_process__WEBPACK_IMPORTED_MODULE_2__["spawn"])('bazel', ['--version'], { + stdio: 'pipe' + }); + return true; + } catch { + return false; + } +} + async function installBazelTools(repoRootPath) { - _log__WEBPACK_IMPORTED_MODULE_3__["log"].debug(`[bazel_tools] reading bazel tools versions from version files`); + _log__WEBPACK_IMPORTED_MODULE_4__["log"].debug(`[bazel_tools] reading bazel tools versions from version files`); const bazeliskVersion = await readBazelToolsVersionFile(repoRootPath, '.bazeliskversion'); const bazelVersion = await readBazelToolsVersionFile(repoRootPath, '.bazelversion'); // Check what globals are installed - _log__WEBPACK_IMPORTED_MODULE_3__["log"].debug(`[bazel_tools] verify if bazelisk is installed`); + _log__WEBPACK_IMPORTED_MODULE_4__["log"].debug(`[bazel_tools] verify if bazelisk is installed`); const { - stdout - } = await Object(_child_process__WEBPACK_IMPORTED_MODULE_1__["spawn"])('yarn', ['global', 'list'], { + stdout: bazeliskPkgInstallStdout + } = await Object(_child_process__WEBPACK_IMPORTED_MODULE_2__["spawn"])('yarn', ['global', 'list'], { stdio: 'pipe' - }); // Install bazelisk if not installed + }); + const isBazelBinAlreadyAvailable = await isBazelBinAvailable(); // Install bazelisk if not installed - if (!stdout.includes(`@bazel/bazelisk@${bazeliskVersion}`)) { - _log__WEBPACK_IMPORTED_MODULE_3__["log"].info(`[bazel_tools] installing Bazel tools`); - _log__WEBPACK_IMPORTED_MODULE_3__["log"].debug(`[bazel_tools] bazelisk is not installed. Installing @bazel/bazelisk@${bazeliskVersion} and bazel@${bazelVersion}`); - await Object(_child_process__WEBPACK_IMPORTED_MODULE_1__["spawn"])('yarn', ['global', 'add', `@bazel/bazelisk@${bazeliskVersion}`], { + if (!bazeliskPkgInstallStdout.includes(`@bazel/bazelisk@${bazeliskVersion}`) || !isBazelBinAlreadyAvailable) { + _log__WEBPACK_IMPORTED_MODULE_4__["log"].info(`[bazel_tools] installing Bazel tools`); + _log__WEBPACK_IMPORTED_MODULE_4__["log"].debug(`[bazel_tools] bazelisk is not installed. Installing @bazel/bazelisk@${bazeliskVersion} and bazel@${bazelVersion}`); + await Object(_child_process__WEBPACK_IMPORTED_MODULE_2__["spawn"])('yarn', ['global', 'add', `@bazel/bazelisk@${bazeliskVersion}`], { env: { USE_BAZEL_VERSION: bazelVersion }, stdio: 'pipe' }); + const isBazelBinAvailableAfterInstall = await isBazelBinAvailable(); + + if (!isBazelBinAvailableAfterInstall) { + throw new Error(dedent__WEBPACK_IMPORTED_MODULE_0___default.a` + [bazel_tools] an error occurred when installing the Bazel tools. Please make sure 'yarn global bin' is on your $PATH, otherwise just add it there + `); + } } - _log__WEBPACK_IMPORTED_MODULE_3__["log"].success(`[bazel_tools] all bazel tools are correctly installed`); + _log__WEBPACK_IMPORTED_MODULE_4__["log"].success(`[bazel_tools] all bazel tools are correctly installed`); } /***/ }), -/* 373 */ +/* 374 */ /***/ (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 del__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(143); -/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(374); -/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(ora__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(131); -/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(246); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "runBazel", function() { return runBazel; }); +/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(113); +/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var rxjs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(8); +/* harmony import */ var rxjs_operators__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(375); +/* harmony import */ var _kbn_dev_utils_stdio__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(473); +/* harmony import */ var _kbn_dev_utils_stdio__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_kbn_dev_utils_stdio__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var _child_process__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(319); +/* harmony import */ var _log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(246); +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(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(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; } + /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License @@ -48041,10879 +48114,11176 @@ __webpack_require__.r(__webpack_exports__); -const CleanCommand = { - description: 'Remove the node_modules and target directories from all projects.', - name: 'clean', - async run(projects) { - const toDelete = []; +async function runBazel(bazelArgs, runOpts = {}) { + // Force logs to pipe in order to control the output of them + const bazelOpts = _objectSpread(_objectSpread({}, runOpts), {}, { + stdio: 'pipe' + }); - for (const project of projects.values()) { - if (await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_3__["isDirectory"])(project.nodeModulesLocation)) { - toDelete.push({ - cwd: project.path, - pattern: Object(path__WEBPACK_IMPORTED_MODULE_2__["relative"])(project.path, project.nodeModulesLocation) - }); - } + const bazelProc = Object(_child_process__WEBPACK_IMPORTED_MODULE_4__["spawn"])('bazel', bazelArgs, bazelOpts); + const bazelLogs$ = new rxjs__WEBPACK_IMPORTED_MODULE_1__["Subject"](); // Bazel outputs machine readable output into stdout and human readable output goes to stderr. + // Therefore we need to get both. In order to get errors we need to parse the actual text line - if (await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_3__["isDirectory"])(project.targetLocation)) { - toDelete.push({ - cwd: project.path, - pattern: Object(path__WEBPACK_IMPORTED_MODULE_2__["relative"])(project.path, project.targetLocation) - }); - } + const bazelLogSubscription = rxjs__WEBPACK_IMPORTED_MODULE_1__["merge"](Object(_kbn_dev_utils_stdio__WEBPACK_IMPORTED_MODULE_3__["observeLines"])(bazelProc.stdout).pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_2__["tap"])(line => _log__WEBPACK_IMPORTED_MODULE_5__["log"].info(`${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.cyan('[bazel]')} ${line}`))), Object(_kbn_dev_utils_stdio__WEBPACK_IMPORTED_MODULE_3__["observeLines"])(bazelProc.stderr).pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_2__["tap"])(line => _log__WEBPACK_IMPORTED_MODULE_5__["log"].info(`${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.cyan('[bazel]')} ${line}`)))).subscribe(bazelLogs$); // Wait for process and logs to finish, unsubscribing in the end - const { - extraPatterns - } = project.getCleanConfig(); + await bazelProc; + await bazelLogs$.toPromise(); + await bazelLogSubscription.unsubscribe(); +} - if (extraPatterns) { - toDelete.push({ - cwd: project.path, - pattern: extraPatterns - }); - } - } +/***/ }), +/* 375 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - if (toDelete.length === 0) { - _utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].success('Nothing to delete'); - } else { - /** - * 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(); +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(376); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "audit", function() { return _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__["audit"]; }); - try { - for (const { - pattern, - cwd - } of toDelete) { - process.chdir(cwd); - const promise = del__WEBPACK_IMPORTED_MODULE_0___default()(pattern); +/* harmony import */ var _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(377); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "auditTime", function() { return _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__["auditTime"]; }); - if (_utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].wouldLogLevel('info')) { - ora__WEBPACK_IMPORTED_MODULE_1___default.a.promise(promise, Object(path__WEBPACK_IMPORTED_MODULE_2__["relative"])(originalCwd, Object(path__WEBPACK_IMPORTED_MODULE_2__["join"])(cwd, String(pattern)))); - } +/* harmony import */ var _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(378); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buffer", function() { return _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__["buffer"]; }); - await promise; - } - } finally { - process.chdir(originalCwd); - } - } - } +/* harmony import */ var _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(379); +/* 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__(380); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferTime", function() { return _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__["bufferTime"]; }); -/***/ }), -/* 374 */ -/***/ (function(module, exports, __webpack_require__) { +/* harmony import */ var _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(381); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferToggle", function() { return _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__["bufferToggle"]; }); -"use strict"; +/* harmony import */ var _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(382); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferWhen", function() { return _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__["bufferWhen"]; }); -const readline = __webpack_require__(375); -const chalk = __webpack_require__(376); -const cliCursor = __webpack_require__(383); -const cliSpinners = __webpack_require__(385); -const logSymbols = __webpack_require__(387); -const stripAnsi = __webpack_require__(396); -const wcwidth = __webpack_require__(398); -const isInteractive = __webpack_require__(402); -const MuteStream = __webpack_require__(403); +/* harmony import */ var _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(383); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "catchError", function() { return _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__["catchError"]; }); -const TEXT = Symbol('text'); -const PREFIX_TEXT = Symbol('prefixText'); +/* harmony import */ var _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(384); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "combineAll", function() { return _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__["combineAll"]; }); -const ASCII_ETX_CODE = 0x03; // Ctrl+C emits this code +/* harmony import */ var _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(385); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "combineLatest", function() { return _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__["combineLatest"]; }); -class StdinDiscarder { - constructor() { - this.requests = 0; +/* harmony import */ var _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(386); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concat", function() { return _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__["concat"]; }); - this.mutedStream = new MuteStream(); - this.mutedStream.pipe(process.stdout); - this.mutedStream.mute(); +/* harmony import */ var _internal_operators_concatAll__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(80); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatAll", function() { return _internal_operators_concatAll__WEBPACK_IMPORTED_MODULE_11__["concatAll"]; }); - const self = this; - this.ourEmit = function (event, data, ...args) { - const {stdin} = process; - if (self.requests > 0 || stdin.emit === self.ourEmit) { - if (event === 'keypress') { // Fixes readline behavior - return; - } +/* harmony import */ var _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(387); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatMap", function() { return _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__["concatMap"]; }); - if (event === 'data' && data.includes(ASCII_ETX_CODE)) { - process.emit('SIGINT'); - } +/* harmony import */ var _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(388); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatMapTo", function() { return _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__["concatMapTo"]; }); - Reflect.apply(self.oldEmit, this, [event, data, ...args]); - } else { - Reflect.apply(process.stdin.emit, this, [event, data, ...args]); - } - }; - } +/* harmony import */ var _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(389); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "count", function() { return _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__["count"]; }); - start() { - this.requests++; +/* harmony import */ var _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(390); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "debounce", function() { return _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__["debounce"]; }); - if (this.requests === 1) { - this.realStart(); - } - } +/* harmony import */ var _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(391); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "debounceTime", function() { return _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__["debounceTime"]; }); - stop() { - if (this.requests <= 0) { - throw new Error('`stop` called more times than `start`'); - } +/* harmony import */ var _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(392); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "defaultIfEmpty", function() { return _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__["defaultIfEmpty"]; }); - this.requests--; +/* harmony import */ var _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(393); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "delay", function() { return _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__["delay"]; }); - if (this.requests === 0) { - this.realStop(); - } - } +/* harmony import */ var _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(395); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "delayWhen", function() { return _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__["delayWhen"]; }); - realStart() { - // No known way to make it work reliably on Windows - if (process.platform === 'win32') { - return; - } +/* harmony import */ var _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(396); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "dematerialize", function() { return _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__["dematerialize"]; }); - this.rl = readline.createInterface({ - input: process.stdin, - output: this.mutedStream - }); +/* harmony import */ var _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(397); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinct", function() { return _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__["distinct"]; }); - this.rl.on('SIGINT', () => { - if (process.listenerCount('SIGINT') === 0) { - process.emit('SIGINT'); - } else { - this.rl.close(); - process.kill(process.pid, 'SIGINT'); - } - }); - } +/* harmony import */ var _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(398); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinctUntilChanged", function() { return _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__["distinctUntilChanged"]; }); - realStop() { - if (process.platform === 'win32') { - return; - } +/* harmony import */ var _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(399); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinctUntilKeyChanged", function() { return _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__["distinctUntilKeyChanged"]; }); - this.rl.close(); - this.rl = undefined; - } -} +/* harmony import */ var _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(400); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "elementAt", function() { return _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__["elementAt"]; }); -let stdinDiscarder; +/* harmony import */ var _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(403); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "endWith", function() { return _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__["endWith"]; }); -class Ora { - constructor(options) { - if (!stdinDiscarder) { - stdinDiscarder = new StdinDiscarder(); - } +/* harmony import */ var _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(404); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "every", function() { return _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__["every"]; }); - if (typeof options === 'string') { - options = { - text: options - }; - } +/* harmony import */ var _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(405); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "exhaust", function() { return _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__["exhaust"]; }); - this.options = { - text: '', - color: 'cyan', - stream: process.stderr, - discardStdin: true, - ...options - }; +/* harmony import */ var _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(406); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "exhaustMap", function() { return _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__["exhaustMap"]; }); - this.spinner = this.options.spinner; +/* harmony import */ var _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(407); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "expand", function() { return _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__["expand"]; }); - this.color = this.options.color; - this.hideCursor = this.options.hideCursor !== false; - this.interval = this.options.interval || this.spinner.interval || 100; - this.stream = this.options.stream; - this.id = undefined; - this.isEnabled = typeof this.options.isEnabled === 'boolean' ? this.options.isEnabled : isInteractive({stream: this.stream}); +/* harmony import */ var _internal_operators_filter__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(105); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "filter", function() { return _internal_operators_filter__WEBPACK_IMPORTED_MODULE_30__["filter"]; }); - // Set *after* `this.stream` - this.text = this.options.text; - this.prefixText = this.options.prefixText; - this.linesToClear = 0; - this.indent = this.options.indent; - this.discardStdin = this.options.discardStdin; - this.isDiscardingStdin = false; - } +/* harmony import */ var _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(408); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "finalize", function() { return _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__["finalize"]; }); - get indent() { - return this._indent; - } +/* harmony import */ var _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(409); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "find", function() { return _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__["find"]; }); - set indent(indent = 0) { - if (!(indent >= 0 && Number.isInteger(indent))) { - throw new Error('The `indent` option must be an integer from 0 and up'); - } +/* harmony import */ var _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(410); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "findIndex", function() { return _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__["findIndex"]; }); - this._indent = indent; - } +/* harmony import */ var _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(411); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "first", function() { return _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__["first"]; }); - _updateInterval(interval) { - if (interval !== undefined) { - this.interval = interval; - } - } +/* harmony import */ var _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_35__ = __webpack_require__(31); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "groupBy", function() { return _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_35__["groupBy"]; }); - get spinner() { - return this._spinner; - } +/* harmony import */ var _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(412); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ignoreElements", function() { return _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__["ignoreElements"]; }); - set spinner(spinner) { - this.frameIndex = 0; +/* harmony import */ var _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(413); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "isEmpty", function() { return _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__["isEmpty"]; }); - if (typeof spinner === 'object') { - if (spinner.frames === undefined) { - throw new Error('The given spinner must have a `frames` property'); - } +/* harmony import */ var _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(414); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "last", function() { return _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__["last"]; }); - this._spinner = spinner; - } else if (process.platform === 'win32') { - this._spinner = cliSpinners.line; - } else if (spinner === undefined) { - // Set default spinner - this._spinner = cliSpinners.dots; - } else if (cliSpinners[spinner]) { - this._spinner = cliSpinners[spinner]; - } else { - throw new Error(`There is no built-in spinner named '${spinner}'. See https://github.com/sindresorhus/cli-spinners/blob/master/spinners.json for a full list.`); - } +/* harmony import */ var _internal_operators_map__WEBPACK_IMPORTED_MODULE_39__ = __webpack_require__(66); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "map", function() { return _internal_operators_map__WEBPACK_IMPORTED_MODULE_39__["map"]; }); - this._updateInterval(this._spinner.interval); - } +/* harmony import */ var _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(416); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mapTo", function() { return _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__["mapTo"]; }); - get text() { - return this[TEXT]; - } +/* harmony import */ var _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(417); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "materialize", function() { return _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__["materialize"]; }); - get prefixText() { - return this[PREFIX_TEXT]; - } +/* harmony import */ var _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(418); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "max", function() { return _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__["max"]; }); - get isSpinning() { - return this.id !== undefined; - } +/* harmony import */ var _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(421); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "merge", function() { return _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__["merge"]; }); - updateLineCount() { - const columns = this.stream.columns || 80; - const fullPrefixText = (typeof this[PREFIX_TEXT] === 'string') ? this[PREFIX_TEXT] + '-' : ''; - this.lineCount = stripAnsi(fullPrefixText + '--' + this[TEXT]).split('\n').reduce((count, line) => { - return count + Math.max(1, Math.ceil(wcwidth(line) / columns)); - }, 0); - } +/* harmony import */ var _internal_operators_mergeAll__WEBPACK_IMPORTED_MODULE_44__ = __webpack_require__(81); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeAll", function() { return _internal_operators_mergeAll__WEBPACK_IMPORTED_MODULE_44__["mergeAll"]; }); - set text(value) { - this[TEXT] = value; - this.updateLineCount(); - } +/* harmony import */ var _internal_operators_mergeMap__WEBPACK_IMPORTED_MODULE_45__ = __webpack_require__(82); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeMap", function() { return _internal_operators_mergeMap__WEBPACK_IMPORTED_MODULE_45__["mergeMap"]; }); - set prefixText(value) { - this[PREFIX_TEXT] = value; - this.updateLineCount(); - } +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "flatMap", function() { return _internal_operators_mergeMap__WEBPACK_IMPORTED_MODULE_45__["flatMap"]; }); - frame() { - const {frames} = this.spinner; - let frame = frames[this.frameIndex]; +/* harmony import */ var _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(422); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeMapTo", function() { return _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__["mergeMapTo"]; }); - if (this.color) { - frame = chalk[this.color](frame); - } +/* harmony import */ var _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(423); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeScan", function() { return _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__["mergeScan"]; }); - this.frameIndex = ++this.frameIndex % frames.length; - const fullPrefixText = (typeof this.prefixText === 'string' && this.prefixText !== '') ? this.prefixText + ' ' : ''; - const fullText = typeof this.text === 'string' ? ' ' + this.text : ''; +/* harmony import */ var _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(424); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "min", function() { return _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__["min"]; }); - return fullPrefixText + frame + fullText; - } +/* harmony import */ var _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__ = __webpack_require__(425); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "multicast", function() { return _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__["multicast"]; }); - clear() { - if (!this.isEnabled || !this.stream.isTTY) { - return this; - } +/* harmony import */ var _internal_operators_observeOn__WEBPACK_IMPORTED_MODULE_50__ = __webpack_require__(41); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "observeOn", function() { return _internal_operators_observeOn__WEBPACK_IMPORTED_MODULE_50__["observeOn"]; }); - for (let i = 0; i < this.linesToClear; i++) { - if (i > 0) { - this.stream.moveCursor(0, -1); - } +/* harmony import */ var _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__ = __webpack_require__(426); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "onErrorResumeNext", function() { return _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__["onErrorResumeNext"]; }); - this.stream.clearLine(); - this.stream.cursorTo(this.indent); - } +/* harmony import */ var _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__ = __webpack_require__(427); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pairwise", function() { return _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__["pairwise"]; }); - this.linesToClear = 0; +/* harmony import */ var _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__ = __webpack_require__(428); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "partition", function() { return _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__["partition"]; }); - return this; - } +/* harmony import */ var _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__ = __webpack_require__(429); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pluck", function() { return _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__["pluck"]; }); - render() { - this.clear(); - this.stream.write(this.frame()); - this.linesToClear = this.lineCount; +/* harmony import */ var _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__ = __webpack_require__(430); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publish", function() { return _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__["publish"]; }); - return this; - } +/* harmony import */ var _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__ = __webpack_require__(431); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishBehavior", function() { return _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__["publishBehavior"]; }); - start(text) { - if (text) { - this.text = text; - } +/* harmony import */ var _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__ = __webpack_require__(432); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishLast", function() { return _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__["publishLast"]; }); - if (!this.isEnabled) { - if (this.text) { - this.stream.write(`- ${this.text}\n`); - } +/* harmony import */ var _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__ = __webpack_require__(433); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishReplay", function() { return _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__["publishReplay"]; }); - return this; - } +/* harmony import */ var _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__ = __webpack_require__(434); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "race", function() { return _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__["race"]; }); - if (this.isSpinning) { - return this; - } +/* harmony import */ var _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__ = __webpack_require__(419); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "reduce", function() { return _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__["reduce"]; }); - if (this.hideCursor) { - cliCursor.hide(this.stream); - } +/* harmony import */ var _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__ = __webpack_require__(435); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "repeat", function() { return _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__["repeat"]; }); - if (this.discardStdin && process.stdin.isTTY) { - this.isDiscardingStdin = true; - stdinDiscarder.start(); - } +/* harmony import */ var _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__ = __webpack_require__(436); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "repeatWhen", function() { return _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__["repeatWhen"]; }); - this.render(); - this.id = setInterval(this.render.bind(this), this.interval); +/* harmony import */ var _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__ = __webpack_require__(437); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "retry", function() { return _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__["retry"]; }); - return this; - } +/* harmony import */ var _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__ = __webpack_require__(438); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "retryWhen", function() { return _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__["retryWhen"]; }); - stop() { - if (!this.isEnabled) { - return this; - } +/* harmony import */ var _internal_operators_refCount__WEBPACK_IMPORTED_MODULE_65__ = __webpack_require__(30); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "refCount", function() { return _internal_operators_refCount__WEBPACK_IMPORTED_MODULE_65__["refCount"]; }); - clearInterval(this.id); - this.id = undefined; - this.frameIndex = 0; - this.clear(); - if (this.hideCursor) { - cliCursor.show(this.stream); - } +/* harmony import */ var _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__ = __webpack_require__(439); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sample", function() { return _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__["sample"]; }); - if (this.discardStdin && process.stdin.isTTY && this.isDiscardingStdin) { - stdinDiscarder.stop(); - this.isDiscardingStdin = false; - } +/* harmony import */ var _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__ = __webpack_require__(440); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sampleTime", function() { return _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__["sampleTime"]; }); - return this; - } +/* harmony import */ var _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__ = __webpack_require__(420); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "scan", function() { return _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__["scan"]; }); - succeed(text) { - return this.stopAndPersist({symbol: logSymbols.success, text}); - } +/* harmony import */ var _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__ = __webpack_require__(441); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sequenceEqual", function() { return _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__["sequenceEqual"]; }); - fail(text) { - return this.stopAndPersist({symbol: logSymbols.error, text}); - } +/* harmony import */ var _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__ = __webpack_require__(442); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "share", function() { return _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__["share"]; }); - warn(text) { - return this.stopAndPersist({symbol: logSymbols.warning, text}); - } +/* harmony import */ var _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__ = __webpack_require__(443); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "shareReplay", function() { return _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__["shareReplay"]; }); - info(text) { - return this.stopAndPersist({symbol: logSymbols.info, text}); - } +/* harmony import */ var _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__ = __webpack_require__(444); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "single", function() { return _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__["single"]; }); - stopAndPersist(options = {}) { - const prefixText = options.prefixText || this.prefixText; - const fullPrefixText = (typeof prefixText === 'string' && prefixText !== '') ? prefixText + ' ' : ''; - const text = options.text || this.text; - const fullText = (typeof text === 'string') ? ' ' + text : ''; +/* harmony import */ var _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__ = __webpack_require__(445); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skip", function() { return _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__["skip"]; }); - this.stop(); - this.stream.write(`${fullPrefixText}${options.symbol || ' '}${fullText}\n`); +/* harmony import */ var _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__ = __webpack_require__(446); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipLast", function() { return _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__["skipLast"]; }); - return this; - } -} +/* harmony import */ var _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__ = __webpack_require__(447); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipUntil", function() { return _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__["skipUntil"]; }); -const oraFactory = function (options) { - return new Ora(options); -}; +/* harmony import */ var _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__ = __webpack_require__(448); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipWhile", function() { return _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__["skipWhile"]; }); -module.exports = oraFactory; +/* harmony import */ var _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__ = __webpack_require__(449); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "startWith", function() { return _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__["startWith"]; }); -module.exports.promise = (action, options) => { - // eslint-disable-next-line promise/prefer-await-to-then - if (typeof action.then !== 'function') { - throw new TypeError('Parameter `action` must be a Promise'); - } +/* harmony import */ var _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__ = __webpack_require__(450); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "subscribeOn", function() { return _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__["subscribeOn"]; }); - const spinner = new Ora(options); - spinner.start(); +/* harmony import */ var _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__ = __webpack_require__(452); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchAll", function() { return _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__["switchAll"]; }); - (async () => { - try { - await action; - spinner.succeed(); - } catch (_) { - spinner.fail(); - } - })(); +/* harmony import */ var _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__ = __webpack_require__(453); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchMap", function() { return _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__["switchMap"]; }); - return spinner; -}; +/* harmony import */ var _internal_operators_switchMapTo__WEBPACK_IMPORTED_MODULE_81__ = __webpack_require__(454); +/* 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__(402); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "take", function() { return _internal_operators_take__WEBPACK_IMPORTED_MODULE_82__["take"]; }); -/***/ }), -/* 375 */ -/***/ (function(module, exports) { +/* harmony import */ var _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__ = __webpack_require__(415); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeLast", function() { return _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__["takeLast"]; }); -module.exports = require("readline"); +/* harmony import */ var _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__ = __webpack_require__(455); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeUntil", function() { return _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__["takeUntil"]; }); -/***/ }), -/* 376 */ -/***/ (function(module, exports, __webpack_require__) { +/* harmony import */ var _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__ = __webpack_require__(456); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeWhile", function() { return _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__["takeWhile"]; }); -"use strict"; +/* harmony import */ var _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__ = __webpack_require__(457); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "tap", function() { return _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__["tap"]; }); -const ansiStyles = __webpack_require__(377); -const {stdout: stdoutColor, stderr: stderrColor} = __webpack_require__(120); -const { - stringReplaceAll, - stringEncaseCRLFWithFirstIndex -} = __webpack_require__(381); +/* harmony import */ var _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__ = __webpack_require__(458); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throttle", function() { return _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__["throttle"]; }); -// `supportsColor.level` → `ansiStyles.color[name]` mapping -const levelMapping = [ - 'ansi', - 'ansi', - 'ansi256', - 'ansi16m' -]; +/* harmony import */ var _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__ = __webpack_require__(459); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throttleTime", function() { return _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__["throttleTime"]; }); -const styles = Object.create(null); +/* harmony import */ var _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__ = __webpack_require__(401); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throwIfEmpty", function() { return _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__["throwIfEmpty"]; }); -const applyOptions = (object, options = {}) => { - if (options.level > 3 || options.level < 0) { - throw new Error('The `level` option should be an integer from 0 to 3'); - } +/* harmony import */ var _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__ = __webpack_require__(460); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeInterval", function() { return _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__["timeInterval"]; }); - // Detect level if not set manually - const colorLevel = stdoutColor ? stdoutColor.level : 0; - object.level = options.level === undefined ? colorLevel : options.level; -}; +/* harmony import */ var _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__ = __webpack_require__(461); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeout", function() { return _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__["timeout"]; }); -class ChalkClass { - constructor(options) { - return chalkFactory(options); - } -} +/* harmony import */ var _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__ = __webpack_require__(462); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeoutWith", function() { return _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__["timeoutWith"]; }); -const chalkFactory = options => { - const chalk = {}; - applyOptions(chalk, options); +/* harmony import */ var _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__ = __webpack_require__(463); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timestamp", function() { return _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__["timestamp"]; }); - chalk.template = (...arguments_) => chalkTag(chalk.template, ...arguments_); +/* harmony import */ var _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__ = __webpack_require__(464); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "toArray", function() { return _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__["toArray"]; }); - Object.setPrototypeOf(chalk, Chalk.prototype); - Object.setPrototypeOf(chalk.template, chalk); +/* harmony import */ var _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__ = __webpack_require__(465); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "window", function() { return _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__["window"]; }); - chalk.template.constructor = () => { - throw new Error('`chalk.constructor()` is deprecated. Use `new chalk.Instance()` instead.'); - }; +/* harmony import */ var _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__ = __webpack_require__(466); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowCount", function() { return _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__["windowCount"]; }); - chalk.template.Instance = ChalkClass; +/* harmony import */ var _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__ = __webpack_require__(467); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowTime", function() { return _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__["windowTime"]; }); - return chalk.template; -}; +/* harmony import */ var _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__ = __webpack_require__(468); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowToggle", function() { return _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__["windowToggle"]; }); -function Chalk(options) { - return chalkFactory(options); -} +/* harmony import */ var _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__ = __webpack_require__(469); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowWhen", function() { return _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__["windowWhen"]; }); -for (const [styleName, style] of Object.entries(ansiStyles)) { - styles[styleName] = { - get() { - const builder = createBuilder(this, createStyler(style.open, style.close, this._styler), this._isEmpty); - Object.defineProperty(this, styleName, {value: builder}); - return builder; - } - }; -} +/* harmony import */ var _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__ = __webpack_require__(470); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "withLatestFrom", function() { return _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__["withLatestFrom"]; }); -styles.visible = { - get() { - const builder = createBuilder(this, this._styler, true); - Object.defineProperty(this, 'visible', {value: builder}); - return builder; - } -}; +/* harmony import */ var _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__ = __webpack_require__(471); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "zip", function() { return _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__["zip"]; }); -const usedModels = ['rgb', 'hex', 'keyword', 'hsl', 'hsv', 'hwb', 'ansi', 'ansi256']; +/* harmony import */ var _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__ = __webpack_require__(472); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "zipAll", function() { return _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__["zipAll"]; }); -for (const model of usedModels) { - styles[model] = { - get() { - const {level} = this; - return function (...arguments_) { - const styler = createStyler(ansiStyles.color[levelMapping[level]][model](...arguments_), ansiStyles.color.close, this._styler); - return createBuilder(this, styler, this._isEmpty); - }; - } - }; -} +/** PURE_IMPORTS_START PURE_IMPORTS_END */ -for (const model of usedModels) { - const bgModel = 'bg' + model[0].toUpperCase() + model.slice(1); - styles[bgModel] = { - get() { - const {level} = this; - return function (...arguments_) { - const styler = createStyler(ansiStyles.bgColor[levelMapping[level]][model](...arguments_), ansiStyles.bgColor.close, this._styler); - return createBuilder(this, styler, this._isEmpty); - }; - } - }; -} -const proto = Object.defineProperties(() => {}, { - ...styles, - level: { - enumerable: true, - get() { - return this._generator.level; - }, - set(level) { - this._generator.level = level; - } - } -}); -const createStyler = (open, close, parent) => { - let openAll; - let closeAll; - if (parent === undefined) { - openAll = open; - closeAll = close; - } else { - openAll = parent.openAll + open; - closeAll = close + parent.closeAll; - } - return { - open, - close, - openAll, - closeAll, - parent - }; -}; -const createBuilder = (self, _styler, _isEmpty) => { - const builder = (...arguments_) => { - // Single argument is hot path, implicit coercion is faster than anything - // eslint-disable-next-line no-implicit-coercion - return applyStyle(builder, (arguments_.length === 1) ? ('' + arguments_[0]) : arguments_.join(' ')); - }; - // `__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 - builder._generator = self; - builder._styler = _styler; - builder._isEmpty = _isEmpty; - return builder; -}; -const applyStyle = (self, string) => { - if (self.level <= 0 || !string) { - return self._isEmpty ? '' : string; - } - let styler = self._styler; - if (styler === undefined) { - return string; - } - const {openAll, closeAll} = styler; - if (string.indexOf('\u001B') !== -1) { - while (styler !== undefined) { - // 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'. - string = stringReplaceAll(string, styler.close, styler.open); - styler = styler.parent; - } - } - // We can move both next actions out of loop, because remaining actions in loop won't have - // any/visible effect on parts we add here. 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 - const lfIndex = string.indexOf('\n'); - if (lfIndex !== -1) { - string = stringEncaseCRLFWithFirstIndex(string, closeAll, openAll, lfIndex); - } - return openAll + string + closeAll; -}; -let template; -const chalkTag = (chalk, ...strings) => { - const [firstString] = strings; - if (!Array.isArray(firstString)) { - // If chalk() was called by itself or with a string, - // return the string itself as a string. - return strings.join(' '); - } - const arguments_ = strings.slice(1); - const parts = [firstString.raw[0]]; - for (let i = 1; i < firstString.length; i++) { - parts.push( - String(arguments_[i - 1]).replace(/[{}\\]/g, '\\$&'), - String(firstString.raw[i]) - ); - } - if (template === undefined) { - template = __webpack_require__(382); - } - return template(chalk, parts.join('')); -}; -Object.defineProperties(Chalk.prototype, styles); -const chalk = Chalk(); // eslint-disable-line new-cap -chalk.supportsColor = stdoutColor; -chalk.stderr = Chalk({level: stderrColor ? stderrColor.level : 0}); // eslint-disable-line new-cap -chalk.stderr.supportsColor = stderrColor; -// For TypeScript -chalk.Level = { - None: 0, - Basic: 1, - Ansi256: 2, - TrueColor: 3, - 0: 'None', - 1: 'Basic', - 2: 'Ansi256', - 3: 'TrueColor' -}; -module.exports = chalk; -/***/ }), -/* 377 */ -/***/ (function(module, exports, __webpack_require__) { -"use strict"; -/* WEBPACK VAR INJECTION */(function(module) { -const wrapAnsi16 = (fn, offset) => (...args) => { - const code = fn(...args); - return `\u001B[${code + offset}m`; -}; -const wrapAnsi256 = (fn, offset) => (...args) => { - const code = fn(...args); - return `\u001B[${38 + offset};5;${code}m`; -}; -const wrapAnsi16m = (fn, offset) => (...args) => { - const rgb = fn(...args); - return `\u001B[${38 + offset};2;${rgb[0]};${rgb[1]};${rgb[2]}m`; -}; -const ansi2ansi = n => n; -const rgb2rgb = (r, g, b) => [r, g, b]; -const setLazyProperty = (object, property, get) => { - Object.defineProperty(object, property, { - get: () => { - const value = get(); - Object.defineProperty(object, property, { - value, - enumerable: true, - configurable: true - }); - return value; - }, - enumerable: true, - configurable: true - }); -}; -/** @type {typeof import('color-convert')} */ -let colorConvert; -const makeDynamicStyles = (wrap, targetSpace, identity, isBackground) => { - if (colorConvert === undefined) { - colorConvert = __webpack_require__(378); - } - const offset = isBackground ? 10 : 0; - const styles = {}; - for (const [sourceSpace, suite] of Object.entries(colorConvert)) { - const name = sourceSpace === 'ansi16' ? 'ansi' : sourceSpace; - if (sourceSpace === targetSpace) { - styles[name] = wrap(identity, offset); - } else if (typeof suite === 'object') { - styles[name] = wrap(suite[targetSpace], offset); - } - } - return styles; -}; -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], - // Bright color - blackBright: [90, 39], - 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] - } - }; - // Alias bright black as gray (and grey) - styles.color.gray = styles.color.blackBright; - styles.bgColor.bgGray = styles.bgColor.bgBlackBright; - styles.color.grey = styles.color.blackBright; - styles.bgColor.bgGrey = styles.bgColor.bgBlackBright; - for (const [groupName, group] of Object.entries(styles)) { - for (const [styleName, style] of Object.entries(group)) { - 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 - }); - styles.color.close = '\u001B[39m'; - styles.bgColor.close = '\u001B[49m'; - setLazyProperty(styles.color, 'ansi', () => makeDynamicStyles(wrapAnsi16, 'ansi16', ansi2ansi, false)); - setLazyProperty(styles.color, 'ansi256', () => makeDynamicStyles(wrapAnsi256, 'ansi256', ansi2ansi, false)); - setLazyProperty(styles.color, 'ansi16m', () => makeDynamicStyles(wrapAnsi16m, 'rgb', rgb2rgb, false)); - setLazyProperty(styles.bgColor, 'ansi', () => makeDynamicStyles(wrapAnsi16, 'ansi16', ansi2ansi, true)); - setLazyProperty(styles.bgColor, 'ansi256', () => makeDynamicStyles(wrapAnsi256, 'ansi256', ansi2ansi, true)); - setLazyProperty(styles.bgColor, 'ansi16m', () => makeDynamicStyles(wrapAnsi16m, 'rgb', rgb2rgb, true)); - return styles; -} -// Make the export immutable -Object.defineProperty(module, 'exports', { - enumerable: true, - get: assembleStyles -}); -/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(115)(module))) -/***/ }), -/* 378 */ -/***/ (function(module, exports, __webpack_require__) { -const conversions = __webpack_require__(379); -const route = __webpack_require__(380); -const convert = {}; -const models = Object.keys(conversions); -function wrapRaw(fn) { - const wrappedFn = function (...args) { - const arg0 = args[0]; - if (arg0 === undefined || arg0 === null) { - return arg0; - } - if (arg0.length > 1) { - args = arg0; - } - return fn(args); - }; - // Preserve .conversion property if there is one - if ('conversion' in fn) { - wrappedFn.conversion = fn.conversion; - } - return wrappedFn; -} -function wrapRounded(fn) { - const wrappedFn = function (...args) { - const arg0 = args[0]; - if (arg0 === undefined || arg0 === null) { - return arg0; - } - if (arg0.length > 1) { - args = arg0; - } - const result = fn(args); - // We're assuming the result is an array here. - // see notice in conversions.js; don't use box types - // in conversion functions. - if (typeof result === 'object') { - for (let len = result.length, i = 0; i < len; i++) { - result[i] = Math.round(result[i]); - } - } - return result; - }; - // Preserve .conversion property if there is one - if ('conversion' in fn) { - wrappedFn.conversion = fn.conversion; - } - return wrappedFn; -} -models.forEach(fromModel => { - convert[fromModel] = {}; - Object.defineProperty(convert[fromModel], 'channels', {value: conversions[fromModel].channels}); - Object.defineProperty(convert[fromModel], 'labels', {value: conversions[fromModel].labels}); - const routes = route(fromModel); - const routeModels = Object.keys(routes); - routeModels.forEach(toModel => { - const fn = routes[toModel]; - convert[fromModel][toModel] = wrapRounded(fn); - convert[fromModel][toModel].raw = wrapRaw(fn); - }); -}); -module.exports = convert; -/***/ }), -/* 379 */ -/***/ (function(module, exports, __webpack_require__) { -/* MIT license */ -/* eslint-disable no-mixed-operators */ -const cssKeywords = __webpack_require__(118); -// NOTE: conversions should only return primitive values (i.e. arrays, or -// values that give correct `typeof` results). -// do not use box values types (i.e. Number(), String(), etc.) -const reverseKeywords = {}; -for (const key of Object.keys(cssKeywords)) { - reverseKeywords[cssKeywords[key]] = key; -} -const convert = { - rgb: {channels: 3, labels: 'rgb'}, - hsl: {channels: 3, labels: 'hsl'}, - hsv: {channels: 3, labels: 'hsv'}, - hwb: {channels: 3, labels: 'hwb'}, - cmyk: {channels: 4, labels: 'cmyk'}, - xyz: {channels: 3, labels: 'xyz'}, - lab: {channels: 3, labels: 'lab'}, - lch: {channels: 3, labels: 'lch'}, - hex: {channels: 1, labels: ['hex']}, - keyword: {channels: 1, labels: ['keyword']}, - ansi16: {channels: 1, labels: ['ansi16']}, - ansi256: {channels: 1, labels: ['ansi256']}, - hcg: {channels: 3, labels: ['h', 'c', 'g']}, - apple: {channels: 3, labels: ['r16', 'g16', 'b16']}, - gray: {channels: 1, labels: ['gray']} -}; -module.exports = convert; -// Hide .channels and .labels properties -for (const model of Object.keys(convert)) { - if (!('channels' in convert[model])) { - throw new Error('missing channels property: ' + model); - } - if (!('labels' in convert[model])) { - throw new Error('missing channel labels property: ' + model); - } - if (convert[model].labels.length !== convert[model].channels) { - throw new Error('channel and label counts mismatch: ' + model); - } - const {channels, labels} = convert[model]; - delete convert[model].channels; - delete convert[model].labels; - Object.defineProperty(convert[model], 'channels', {value: channels}); - Object.defineProperty(convert[model], 'labels', {value: labels}); -} -convert.rgb.hsl = function (rgb) { - const r = rgb[0] / 255; - const g = rgb[1] / 255; - const b = rgb[2] / 255; - const min = Math.min(r, g, b); - const max = Math.max(r, g, b); - const delta = max - min; - let h; - let s; - if (max === min) { - h = 0; - } else if (r === max) { - h = (g - b) / delta; - } else if (g === max) { - h = 2 + (b - r) / delta; - } else if (b === max) { - h = 4 + (r - g) / delta; - } - h = Math.min(h * 60, 360); - if (h < 0) { - h += 360; - } - const l = (min + max) / 2; - if (max === min) { - s = 0; - } else if (l <= 0.5) { - s = delta / (max + min); - } else { - s = delta / (2 - max - min); - } - return [h, s * 100, l * 100]; -}; -convert.rgb.hsv = function (rgb) { - let rdif; - let gdif; - let bdif; - let h; - let s; - const r = rgb[0] / 255; - const g = rgb[1] / 255; - const b = rgb[2] / 255; - const v = Math.max(r, g, b); - const diff = v - Math.min(r, g, b); - const diffc = function (c) { - return (v - c) / 6 / diff + 1 / 2; - }; - if (diff === 0) { - h = 0; - s = 0; - } else { - s = diff / v; - rdif = diffc(r); - gdif = diffc(g); - bdif = diffc(b); - if (r === v) { - h = bdif - gdif; - } else if (g === v) { - h = (1 / 3) + rdif - bdif; - } else if (b === v) { - h = (2 / 3) + gdif - rdif; - } - if (h < 0) { - h += 1; - } else if (h > 1) { - h -= 1; - } - } - return [ - h * 360, - s * 100, - v * 100 - ]; -}; -convert.rgb.hwb = function (rgb) { - const r = rgb[0]; - const g = rgb[1]; - let b = rgb[2]; - const h = convert.rgb.hsl(rgb)[0]; - const w = 1 / 255 * Math.min(r, Math.min(g, b)); - b = 1 - 1 / 255 * Math.max(r, Math.max(g, b)); - return [h, w * 100, b * 100]; -}; +//# sourceMappingURL=index.js.map -convert.rgb.cmyk = function (rgb) { - const r = rgb[0] / 255; - const g = rgb[1] / 255; - const b = rgb[2] / 255; - const k = Math.min(1 - r, 1 - g, 1 - b); - const c = (1 - r - k) / (1 - k) || 0; - const m = (1 - g - k) / (1 - k) || 0; - const y = (1 - b - k) / (1 - k) || 0; +/***/ }), +/* 376 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - return [c * 100, m * 100, y * 100, k * 100]; -}; +"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__(12); +/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(90); +/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */ -function comparativeDistance(x, y) { - /* - See https://en.m.wikipedia.org/wiki/Euclidean_distance#Squared_Euclidean_distance - */ - return ( - ((x[0] - y[0]) ** 2) + - ((x[1] - y[1]) ** 2) + - ((x[2] - y[2]) ** 2) - ); + +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(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["innerSubscribe"])(duration, new _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleInnerSubscriber"](this)); + 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 = undefined; + throttled.unsubscribe(); + } + if (hasValue) { + this.value = undefined; + this.hasValue = false; + this.destination.next(value); + } + }; + AuditSubscriber.prototype.notifyNext = function () { + this.clearThrottle(); + }; + AuditSubscriber.prototype.notifyComplete = function () { + this.clearThrottle(); + }; + return AuditSubscriber; +}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleOuterSubscriber"])); +//# sourceMappingURL=audit.js.map -convert.rgb.keyword = function (rgb) { - const reversed = reverseKeywords[rgb]; - if (reversed) { - return reversed; - } - let currentClosestDistance = Infinity; - let currentClosestKeyword; +/***/ }), +/* 377 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - for (const keyword of Object.keys(cssKeywords)) { - const value = cssKeywords[keyword]; +"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__(55); +/* harmony import */ var _audit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(376); +/* harmony import */ var _observable_timer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(108); +/** PURE_IMPORTS_START _scheduler_async,_audit,_observable_timer PURE_IMPORTS_END */ - // Compute comparative distance - const distance = comparativeDistance(rgb, value); - // Check if its less, if so set as closest - if (distance < currentClosestDistance) { - currentClosestDistance = distance; - currentClosestKeyword = keyword; - } - } - return currentClosestKeyword; -}; +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 -convert.keyword.rgb = function (keyword) { - return cssKeywords[keyword]; -}; -convert.rgb.xyz = function (rgb) { - let r = rgb[0] / 255; - let g = rgb[1] / 255; - let b = rgb[2] / 255; +/***/ }), +/* 378 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - // Assume sRGB - r = r > 0.04045 ? (((r + 0.055) / 1.055) ** 2.4) : (r / 12.92); - g = g > 0.04045 ? (((g + 0.055) / 1.055) ** 2.4) : (g / 12.92); - b = b > 0.04045 ? (((b + 0.055) / 1.055) ** 2.4) : (b / 12.92); +"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__(12); +/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(90); +/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */ - const x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805); - const y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722); - const z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505); - return [x * 100, y * 100, z * 100]; -}; +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(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["innerSubscribe"])(closingNotifier, new _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleInnerSubscriber"](_this))); + return _this; + } + BufferSubscriber.prototype._next = function (value) { + this.buffer.push(value); + }; + BufferSubscriber.prototype.notifyNext = function () { + var buffer = this.buffer; + this.buffer = []; + this.destination.next(buffer); + }; + return BufferSubscriber; +}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleOuterSubscriber"])); +//# sourceMappingURL=buffer.js.map -convert.rgb.lab = function (rgb) { - const xyz = convert.rgb.xyz(rgb); - let x = xyz[0]; - let y = xyz[1]; - let z = xyz[2]; - x /= 95.047; - y /= 100; - z /= 108.883; +/***/ }), +/* 379 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - x = x > 0.008856 ? (x ** (1 / 3)) : (7.787 * x) + (16 / 116); - y = y > 0.008856 ? (y ** (1 / 3)) : (7.787 * y) + (16 / 116); - z = z > 0.008856 ? (z ** (1 / 3)) : (7.787 * z) + (16 / 116); +"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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ - const l = (116 * y) - 16; - const a = 500 * (x - y); - const b = 200 * (y - z); - return [l, a, b]; -}; +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 -convert.hsl.rgb = function (hsl) { - const h = hsl[0] / 360; - const s = hsl[1] / 100; - const l = hsl[2] / 100; - let t2; - let t3; - let val; - if (s === 0) { - val = l * 255; - return [val, val, val]; - } +/***/ }), +/* 380 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - if (l < 0.5) { - t2 = l * (1 + s); - } else { - t2 = l + s - l * s; - } +"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__(12); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(55); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(11); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(45); +/** PURE_IMPORTS_START tslib,_scheduler_async,_Subscriber,_util_isScheduler PURE_IMPORTS_END */ - const t1 = 2 * l - t2; - const rgb = [0, 0, 0]; - for (let i = 0; i < 3; i++) { - t3 = h + 1 / 3 * -(i - 1); - if (t3 < 0) { - t3++; - } - if (t3 > 1) { - t3--; - } - if (6 * t3 < 1) { - val = t1 + (t2 - t1) * 6 * t3; - } else if (2 * t3 < 1) { - val = t2; - } else if (3 * t3 < 2) { - val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; - } else { - val = t1; - } +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 - rgb[i] = val * 255; - } - return rgb; -}; - -convert.hsl.hsv = function (hsl) { - const h = hsl[0]; - let s = hsl[1] / 100; - let l = hsl[2] / 100; - let smin = s; - const lmin = Math.max(l, 0.01); +/***/ }), +/* 381 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - l *= 2; - s *= (l <= 1) ? l : 2 - l; - smin *= lmin <= 1 ? lmin : 2 - lmin; - const v = (l + s) / 2; - const sv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s); +"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__(12); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(17); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(70); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(69); +/** PURE_IMPORTS_START tslib,_Subscription,_util_subscribeToResult,_OuterSubscriber PURE_IMPORTS_END */ - return [h, sv * 100, v * 100]; -}; -convert.hsv.rgb = function (hsv) { - const h = hsv[0] / 60; - const s = hsv[1] / 100; - let v = hsv[2] / 100; - const hi = Math.floor(h) % 6; - const f = h - Math.floor(h); - const p = 255 * v * (1 - s); - const q = 255 * v * (1 - (s * f)); - const t = 255 * v * (1 - (s * (1 - f))); - v *= 255; - switch (hi) { - case 0: - return [v, t, p]; - case 1: - return [q, v, p]; - case 2: - return [p, v, t]; - case 3: - return [p, q, v]; - case 4: - return [t, p, v]; - case 5: - return [v, p, q]; - } -}; +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.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) { + 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 -convert.hsv.hsl = function (hsv) { - const h = hsv[0]; - const s = hsv[1] / 100; - const v = hsv[2] / 100; - const vmin = Math.max(v, 0.01); - let sl; - let l; - l = (2 - s) * v; - const lmin = (2 - s) * vmin; - sl = s * vmin; - sl /= (lmin <= 1) ? lmin : 2 - lmin; - sl = sl || 0; - l /= 2; +/***/ }), +/* 382 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - return [h, sl * 100, l * 100]; -}; +"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__(12); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(17); +/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(90); +/** PURE_IMPORTS_START tslib,_Subscription,_innerSubscribe PURE_IMPORTS_END */ -// http://dev.w3.org/csswg/css-color/#hwb-to-rgb -convert.hwb.rgb = function (hwb) { - const h = hwb[0] / 360; - let wh = hwb[1] / 100; - let bl = hwb[2] / 100; - const ratio = wh + bl; - let f; - // Wh + bl cant be > 1 - if (ratio > 1) { - wh /= ratio; - bl /= ratio; - } - const i = Math.floor(6 * h); - const v = 1 - bl; - f = 6 * h - i; +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 = undefined; + this.subscribing = false; + }; + BufferWhenSubscriber.prototype.notifyNext = function () { + 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(_innerSubscribe__WEBPACK_IMPORTED_MODULE_2__["innerSubscribe"])(closingNotifier, new _innerSubscribe__WEBPACK_IMPORTED_MODULE_2__["SimpleInnerSubscriber"](this))); + this.subscribing = false; + }; + return BufferWhenSubscriber; +}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_2__["SimpleOuterSubscriber"])); +//# sourceMappingURL=bufferWhen.js.map - if ((i & 0x01) !== 0) { - f = 1 - f; - } - const n = wh + f * (v - wh); // Linear interpolation +/***/ }), +/* 383 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - let r; - let g; - let b; - /* eslint-disable max-statements-per-line,no-multi-spaces */ - switch (i) { - default: - case 6: - case 0: r = v; g = n; b = wh; break; - case 1: r = n; g = v; b = wh; break; - case 2: r = wh; g = v; b = n; break; - case 3: r = wh; g = n; b = v; break; - case 4: r = n; g = wh; b = v; break; - case 5: r = v; g = wh; b = n; break; - } - /* eslint-enable max-statements-per-line,no-multi-spaces */ +"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__(12); +/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(90); +/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */ - return [r * 255, g * 255, b * 255]; -}; -convert.cmyk.rgb = function (cmyk) { - const c = cmyk[0] / 100; - const m = cmyk[1] / 100; - const y = cmyk[2] / 100; - const k = cmyk[3] / 100; +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 _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleInnerSubscriber"](this); + this.add(innerSubscriber); + var innerSubscription = Object(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["innerSubscribe"])(result, innerSubscriber); + if (innerSubscription !== innerSubscriber) { + this.add(innerSubscription); + } + } + }; + return CatchSubscriber; +}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleOuterSubscriber"])); +//# sourceMappingURL=catchError.js.map - const r = 1 - Math.min(1, c * (1 - k) + k); - const g = 1 - Math.min(1, m * (1 - k) + k); - const b = 1 - Math.min(1, y * (1 - k) + k); - return [r * 255, g * 255, b * 255]; -}; +/***/ }), +/* 384 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -convert.xyz.rgb = function (xyz) { - const x = xyz[0] / 100; - const y = xyz[1] / 100; - const z = xyz[2] / 100; - let r; - let g; - let b; +"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__(68); +/** PURE_IMPORTS_START _observable_combineLatest PURE_IMPORTS_END */ - r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986); - g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415); - b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570); +function combineAll(project) { + return function (source) { return source.lift(new _observable_combineLatest__WEBPACK_IMPORTED_MODULE_0__["CombineLatestOperator"](project)); }; +} +//# sourceMappingURL=combineAll.js.map - // Assume sRGB - r = r > 0.0031308 - ? ((1.055 * (r ** (1.0 / 2.4))) - 0.055) - : r * 12.92; - g = g > 0.0031308 - ? ((1.055 * (g ** (1.0 / 2.4))) - 0.055) - : g * 12.92; +/***/ }), +/* 385 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - b = b > 0.0031308 - ? ((1.055 * (b ** (1.0 / 2.4))) - 0.055) - : b * 12.92; +"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__(18); +/* harmony import */ var _observable_combineLatest__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(68); +/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(83); +/** PURE_IMPORTS_START _util_isArray,_observable_combineLatest,_observable_from PURE_IMPORTS_END */ - r = Math.min(Math.max(0, r), 1); - g = Math.min(Math.max(0, g), 1); - b = Math.min(Math.max(0, b), 1); - return [r * 255, g * 255, b * 255]; -}; -convert.xyz.lab = function (xyz) { - let x = xyz[0]; - let y = xyz[1]; - let z = xyz[2]; +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 - x /= 95.047; - y /= 100; - z /= 108.883; - x = x > 0.008856 ? (x ** (1 / 3)) : (7.787 * x) + (16 / 116); - y = y > 0.008856 ? (y ** (1 / 3)) : (7.787 * y) + (16 / 116); - z = z > 0.008856 ? (z ** (1 / 3)) : (7.787 * z) + (16 / 116); +/***/ }), +/* 386 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - const l = (116 * y) - 16; - const a = 500 * (x - y); - const b = 200 * (y - z); +"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__(79); +/** PURE_IMPORTS_START _observable_concat PURE_IMPORTS_END */ - return [l, a, b]; -}; +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 -convert.lab.xyz = function (lab) { - const l = lab[0]; - const a = lab[1]; - const b = lab[2]; - let x; - let y; - let z; - y = (l + 16) / 116; - x = a / 500 + y; - z = y - b / 200; +/***/ }), +/* 387 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - const y2 = y ** 3; - const x2 = x ** 3; - const z2 = z ** 3; - y = y2 > 0.008856 ? y2 : (y - 16 / 116) / 7.787; - x = x2 > 0.008856 ? x2 : (x - 16 / 116) / 7.787; - z = z2 > 0.008856 ? z2 : (z - 16 / 116) / 7.787; +"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__(82); +/** PURE_IMPORTS_START _mergeMap PURE_IMPORTS_END */ - x *= 95.047; - y *= 100; - z *= 108.883; +function concatMap(project, resultSelector) { + return Object(_mergeMap__WEBPACK_IMPORTED_MODULE_0__["mergeMap"])(project, resultSelector, 1); +} +//# sourceMappingURL=concatMap.js.map - return [x, y, z]; -}; -convert.lab.lch = function (lab) { - const l = lab[0]; - const a = lab[1]; - const b = lab[2]; - let h; +/***/ }), +/* 388 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - const hr = Math.atan2(b, a); - h = hr * 360 / 2 / Math.PI; +"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__(387); +/** PURE_IMPORTS_START _concatMap PURE_IMPORTS_END */ - if (h < 0) { - h += 360; - } +function concatMapTo(innerObservable, resultSelector) { + return Object(_concatMap__WEBPACK_IMPORTED_MODULE_0__["concatMap"])(function () { return innerObservable; }, resultSelector); +} +//# sourceMappingURL=concatMapTo.js.map - const c = Math.sqrt(a * a + b * b); - return [l, c, h]; -}; +/***/ }), +/* 389 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -convert.lch.lab = function (lch) { - const l = lch[0]; - const c = lch[1]; - const h = lch[2]; - - const hr = h / 360 * 2 * Math.PI; - const a = c * Math.cos(hr); - const b = c * Math.sin(hr); - - return [l, a, b]; -}; - -convert.rgb.ansi16 = function (args, saturation = null) { - const [r, g, b] = args; - let value = saturation === null ? convert.rgb.hsv(args)[2] : saturation; // Hsv -> ansi16 optimization +"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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ - value = Math.round(value / 50); - if (value === 0) { - return 30; - } +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 - let ansi = 30 - + ((Math.round(b / 255) << 2) - | (Math.round(g / 255) << 1) - | Math.round(r / 255)); - if (value === 2) { - ansi += 60; - } +/***/ }), +/* 390 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - return ansi; -}; +"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__(12); +/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(90); +/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */ -convert.hsv.ansi16 = function (args) { - // Optimization here; we already know the value and don't need to get - // it converted for us. - return convert.rgb.ansi16(convert.hsv.rgb(args), args[2]); -}; -convert.rgb.ansi256 = function (args) { - const r = args[0]; - const g = args[1]; - const b = args[2]; +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; + 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(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["innerSubscribe"])(duration, new _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleInnerSubscriber"](this)); + if (subscription && !subscription.closed) { + this.add(this.durationSubscription = subscription); + } + }; + DebounceSubscriber.prototype.notifyNext = function () { + 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 = undefined; + subscription.unsubscribe(); + this.remove(subscription); + } + this.value = undefined; + this.hasValue = false; + _super.prototype._next.call(this, value); + } + }; + return DebounceSubscriber; +}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleOuterSubscriber"])); +//# sourceMappingURL=debounce.js.map - // We use the extended greyscale palette here, with the exception of - // black and white. normal palette only has 4 greyscale shades. - if (r === g && g === b) { - if (r < 8) { - return 16; - } - if (r > 248) { - return 231; - } +/***/ }), +/* 391 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - return Math.round(((r - 8) / 247) * 24) + 232; - } +"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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(55); +/** PURE_IMPORTS_START tslib,_Subscriber,_scheduler_async PURE_IMPORTS_END */ - const ansi = 16 - + (36 * Math.round(r / 255 * 5)) - + (6 * Math.round(g / 255 * 5)) - + Math.round(b / 255 * 5); - return ansi; -}; -convert.ansi16.rgb = function (args) { - let color = args % 10; +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 - // Handle greyscale - if (color === 0 || color === 7) { - if (args > 50) { - color += 3.5; - } - color = color / 10.5 * 255; +/***/ }), +/* 392 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - return [color, color, color]; - } +"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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ - const mult = (~~(args > 50) + 1) * 0.5; - const r = ((color & 1) * mult) * 255; - const g = (((color >> 1) & 1) * mult) * 255; - const b = (((color >> 2) & 1) * mult) * 255; - return [r, g, b]; -}; +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 -convert.ansi256.rgb = function (args) { - // Handle greyscale - if (args >= 232) { - const c = (args - 232) * 10 + 8; - return [c, c, c]; - } - args -= 16; +/***/ }), +/* 393 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - let rem; - const r = Math.floor(args / 36) / 5 * 255; - const g = Math.floor((rem = args % 36) / 6) / 5 * 255; - const b = (rem % 6) / 5 * 255; +"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__(12); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(55); +/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(394); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(11); +/* harmony import */ var _Notification__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(42); +/** PURE_IMPORTS_START tslib,_scheduler_async,_util_isDate,_Subscriber,_Notification PURE_IMPORTS_END */ - return [r, g, b]; -}; -convert.rgb.hex = function (args) { - const integer = ((Math.round(args[0]) & 0xFF) << 16) - + ((Math.round(args[1]) & 0xFF) << 8) - + (Math.round(args[2]) & 0xFF); - const string = integer.toString(16).toUpperCase(); - return '000000'.substring(string.length) + string; -}; -convert.hex.rgb = function (args) { - const match = args.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i); - if (!match) { - return [0, 0, 0]; - } - let colorString = match[0]; +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 - if (match[0].length === 3) { - colorString = colorString.split('').map(char => { - return char + char; - }).join(''); - } - const integer = parseInt(colorString, 16); - const r = (integer >> 16) & 0xFF; - const g = (integer >> 8) & 0xFF; - const b = integer & 0xFF; +/***/ }), +/* 394 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - return [r, g, b]; -}; +"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 -convert.rgb.hcg = function (rgb) { - const r = rgb[0] / 255; - const g = rgb[1] / 255; - const b = rgb[2] / 255; - const max = Math.max(Math.max(r, g), b); - const min = Math.min(Math.min(r, g), b); - const chroma = (max - min); - let grayscale; - let hue; - if (chroma < 1) { - grayscale = min / (1 - chroma); - } else { - grayscale = 0; - } +/***/ }), +/* 395 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - if (chroma <= 0) { - hue = 0; - } else - if (max === r) { - hue = ((g - b) / chroma) % 6; - } else - if (max === g) { - hue = 2 + (b - r) / chroma; - } else { - hue = 4 + (r - g) / chroma; - } +"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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(69); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(70); +/** PURE_IMPORTS_START tslib,_Subscriber,_Observable,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ - hue /= 6; - hue %= 1; - return [hue * 360, chroma * 100, grayscale * 100]; -}; -convert.hsl.hcg = function (hsl) { - const s = hsl[1] / 100; - const l = hsl[2] / 100; - const c = l < 0.5 ? (2.0 * s * l) : (2.0 * s * (1.0 - l)); - let f = 0; - if (c < 1.0) { - f = (l - 0.5 * c) / (1.0 - c); - } +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 - return [hsl[0], c * 100, f * 100]; -}; -convert.hsv.hcg = function (hsv) { - const s = hsv[1] / 100; - const v = hsv[2] / 100; +/***/ }), +/* 396 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - const c = s * v; - let f = 0; +"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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ - if (c < 1.0) { - f = (v - c) / (1 - c); - } - return [hsv[0], c * 100, f * 100]; -}; +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 -convert.hcg.rgb = function (hcg) { - const h = hcg[0] / 360; - const c = hcg[1] / 100; - const g = hcg[2] / 100; - if (c === 0.0) { - return [g * 255, g * 255, g * 255]; - } +/***/ }), +/* 397 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - const pure = [0, 0, 0]; - const hi = (h % 1) * 6; - const v = hi % 1; - const w = 1 - v; - let mg = 0; +"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__(12); +/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(90); +/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */ - /* eslint-disable max-statements-per-line */ - switch (Math.floor(hi)) { - case 0: - pure[0] = 1; pure[1] = v; pure[2] = 0; break; - case 1: - pure[0] = w; pure[1] = 1; pure[2] = 0; break; - case 2: - pure[0] = 0; pure[1] = 1; pure[2] = v; break; - case 3: - pure[0] = 0; pure[1] = w; pure[2] = 1; break; - case 4: - pure[0] = v; pure[1] = 0; pure[2] = 1; break; - default: - pure[0] = 1; pure[1] = 0; pure[2] = w; - } - /* eslint-enable max-statements-per-line */ - mg = (1.0 - c) * g; +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(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["innerSubscribe"])(flushes, new _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleInnerSubscriber"](_this))); + } + return _this; + } + DistinctSubscriber.prototype.notifyNext = function () { + this.values.clear(); + }; + DistinctSubscriber.prototype.notifyError = function (error) { + 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; +}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleOuterSubscriber"])); - return [ - (c * pure[0] + mg) * 255, - (c * pure[1] + mg) * 255, - (c * pure[2] + mg) * 255 - ]; -}; +//# sourceMappingURL=distinct.js.map -convert.hcg.hsv = function (hcg) { - const c = hcg[1] / 100; - const g = hcg[2] / 100; - const v = c + g * (1.0 - c); - let f = 0; +/***/ }), +/* 398 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - if (v > 0.0) { - f = c / v; - } +"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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ - return [hcg[0], f * 100, v * 100]; -}; -convert.hcg.hsl = function (hcg) { - const c = hcg[1] / 100; - const g = hcg[2] / 100; +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 - const l = g * (1.0 - c) + 0.5 * c; - let s = 0; - if (l > 0.0 && l < 0.5) { - s = c / (2 * l); - } else - if (l >= 0.5 && l < 1.0) { - s = c / (2 * (1 - l)); - } +/***/ }), +/* 399 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - return [hcg[0], s * 100, l * 100]; -}; +"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__(398); +/** PURE_IMPORTS_START _distinctUntilChanged PURE_IMPORTS_END */ -convert.hcg.hwb = function (hcg) { - const c = hcg[1] / 100; - const g = hcg[2] / 100; - const v = c + g * (1.0 - c); - return [hcg[0], (v - c) * 100, (1 - v) * 100]; -}; +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 -convert.hwb.hcg = function (hwb) { - const w = hwb[1] / 100; - const b = hwb[2] / 100; - const v = 1 - b; - const c = v - w; - let g = 0; - if (c < 1) { - g = (v - c) / (1 - c); - } +/***/ }), +/* 400 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - return [hwb[0], c * 100, g * 100]; -}; +"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__(62); +/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(105); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(401); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(392); +/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(402); +/** PURE_IMPORTS_START _util_ArgumentOutOfRangeError,_filter,_throwIfEmpty,_defaultIfEmpty,_take PURE_IMPORTS_END */ -convert.apple.rgb = function (apple) { - return [(apple[0] / 65535) * 255, (apple[1] / 65535) * 255, (apple[2] / 65535) * 255]; -}; -convert.rgb.apple = function (rgb) { - return [(rgb[0] / 255) * 65535, (rgb[1] / 255) * 65535, (rgb[2] / 255) * 65535]; -}; -convert.gray.rgb = function (args) { - return [args[0] / 100 * 255, args[0] / 100 * 255, args[0] / 100 * 255]; -}; -convert.gray.hsl = function (args) { - return [0, 0, args[0]]; -}; -convert.gray.hsv = convert.gray.hsl; +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 -convert.gray.hwb = function (gray) { - return [0, 100, gray[0]]; -}; -convert.gray.cmyk = function (gray) { - return [0, 0, 0, gray[0]]; -}; +/***/ }), +/* 401 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -convert.gray.lab = function (gray) { - return [gray[0], 0, 0]; -}; +"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__(12); +/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(63); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(11); +/** PURE_IMPORTS_START tslib,_util_EmptyError,_Subscriber PURE_IMPORTS_END */ -convert.gray.hex = function (gray) { - const val = Math.round(gray[0] / 100 * 255) & 0xFF; - const integer = (val << 16) + (val << 8) + val; - const string = integer.toString(16).toUpperCase(); - return '000000'.substring(string.length) + string; -}; -convert.rgb.gray = function (rgb) { - const val = (rgb[0] + rgb[1] + rgb[2]) / 3; - return [val / 255 * 100]; -}; +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 /***/ }), -/* 380 */ -/***/ (function(module, exports, __webpack_require__) { - -const conversions = __webpack_require__(379); - -/* - This function routes a model to all other models. +/* 402 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - all functions that are routed have a property `.conversion` attached - to the returned synthetic function. This property is an array - of strings, each with the steps in between the 'from' and 'to' - color models (inclusive). +"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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(62); +/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(43); +/** PURE_IMPORTS_START tslib,_Subscriber,_util_ArgumentOutOfRangeError,_observable_empty PURE_IMPORTS_END */ - conversions that are not possible simply are not included. -*/ -function buildGraph() { - const graph = {}; - // https://jsperf.com/object-keys-vs-for-in-with-closure/3 - const models = Object.keys(conversions); - for (let len = models.length, i = 0; i < len; i++) { - graph[models[i]] = { - // http://jsperf.com/1-vs-infinity - // micro-opt, but this is simple. - distance: -1, - parent: null - }; - } - return graph; +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 -// https://en.wikipedia.org/wiki/Breadth-first_search -function deriveBFS(fromModel) { - const graph = buildGraph(); - const queue = [fromModel]; // Unshift -> queue -> pop - - graph[fromModel].distance = 0; - while (queue.length) { - const current = queue.pop(); - const adjacents = Object.keys(conversions[current]); +/***/ }), +/* 403 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - for (let len = adjacents.length, i = 0; i < len; i++) { - const adjacent = adjacents[i]; - const node = graph[adjacent]; +"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__(79); +/* harmony import */ var _observable_of__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(44); +/** PURE_IMPORTS_START _observable_concat,_observable_of PURE_IMPORTS_END */ - if (node.distance === -1) { - node.distance = graph[current].distance + 1; - node.parent = current; - queue.unshift(adjacent); - } - } - } - return graph; +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 -function link(from, to) { - return function (args) { - return to(from(args)); - }; -} -function wrapConversion(toModel, graph) { - const path = [graph[toModel].parent, toModel]; - let fn = conversions[graph[toModel].parent][toModel]; +/***/ }), +/* 404 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - let cur = graph[toModel].parent; - while (graph[cur].parent) { - path.unshift(graph[cur].parent); - fn = link(conversions[graph[cur].parent][cur], fn); - cur = graph[cur].parent; - } +"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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ - fn.conversion = path; - return fn; -} -module.exports = function (fromModel) { - const graph = deriveBFS(fromModel); - const conversion = {}; +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 - const models = Object.keys(graph); - for (let len = models.length, i = 0; i < len; i++) { - const toModel = models[i]; - const node = graph[toModel]; - if (node.parent === null) { - // No possible conversion, or this node is the source model. - continue; - } +/***/ }), +/* 405 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - conversion[toModel] = wrapConversion(toModel, graph); - } +"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__(12); +/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(90); +/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */ - return conversion; -}; +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(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["innerSubscribe"])(value, new _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleInnerSubscriber"](this))); + } + }; + SwitchFirstSubscriber.prototype._complete = function () { + this.hasCompleted = true; + if (!this.hasSubscription) { + this.destination.complete(); + } + }; + SwitchFirstSubscriber.prototype.notifyComplete = function () { + this.hasSubscription = false; + if (this.hasCompleted) { + this.destination.complete(); + } + }; + return SwitchFirstSubscriber; +}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleOuterSubscriber"])); +//# sourceMappingURL=exhaust.js.map /***/ }), -/* 381 */ -/***/ (function(module, exports, __webpack_require__) { +/* 406 */ +/***/ (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__(12); +/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(66); +/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(83); +/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(90); +/** PURE_IMPORTS_START tslib,_map,_observable_from,_innerSubscribe PURE_IMPORTS_END */ -const stringReplaceAll = (string, substring, replacer) => { - let index = string.indexOf(substring); - if (index === -1) { - return string; - } - - const substringLength = substring.length; - let endIndex = 0; - let returnValue = ''; - do { - returnValue += string.substr(endIndex, index - endIndex) + substring + replacer; - endIndex = index + substringLength; - index = string.indexOf(substring, endIndex); - } while (index !== -1); - - returnValue += string.substr(endIndex); - return returnValue; -}; - -const stringEncaseCRLFWithFirstIndex = (string, prefix, postfix, index) => { - let endIndex = 0; - let returnValue = ''; - do { - const gotCR = string[index - 1] === '\r'; - returnValue += string.substr(endIndex, (gotCR ? index - 1 : index) - endIndex) + prefix + (gotCR ? '\r\n' : '\n') + postfix; - endIndex = index + 1; - index = string.indexOf('\n', endIndex); - } while (index !== -1); - returnValue += string.substr(endIndex); - return returnValue; -}; -module.exports = { - stringReplaceAll, - stringEncaseCRLFWithFirstIndex -}; +function exhaustMap(project, resultSelector) { + if (resultSelector) { + return function (source) { return source.pipe(exhaustMap(function (a, i) { return Object(_observable_from__WEBPACK_IMPORTED_MODULE_2__["from"])(project(a, i)).pipe(Object(_map__WEBPACK_IMPORTED_MODULE_1__["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); + }; + ExhaustMapSubscriber.prototype._innerSub = function (result) { + var innerSubscriber = new _innerSubscribe__WEBPACK_IMPORTED_MODULE_3__["SimpleInnerSubscriber"](this); + var destination = this.destination; + destination.add(innerSubscriber); + var innerSubscription = Object(_innerSubscribe__WEBPACK_IMPORTED_MODULE_3__["innerSubscribe"])(result, innerSubscriber); + if (innerSubscription !== innerSubscriber) { + destination.add(innerSubscription); + } + }; + ExhaustMapSubscriber.prototype._complete = function () { + this.hasCompleted = true; + if (!this.hasSubscription) { + this.destination.complete(); + } + this.unsubscribe(); + }; + ExhaustMapSubscriber.prototype.notifyNext = function (innerValue) { + this.destination.next(innerValue); + }; + ExhaustMapSubscriber.prototype.notifyError = function (err) { + this.destination.error(err); + }; + ExhaustMapSubscriber.prototype.notifyComplete = function () { + this.hasSubscription = false; + if (this.hasCompleted) { + this.destination.complete(); + } + }; + return ExhaustMapSubscriber; +}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_3__["SimpleOuterSubscriber"])); +//# sourceMappingURL=exhaustMap.js.map /***/ }), -/* 382 */ -/***/ (function(module, exports, __webpack_require__) { +/* 407 */ +/***/ (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__(12); +/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(90); +/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */ -const TEMPLATE_REGEX = /(?:\\(u(?:[a-f\d]{4}|\{[a-f\d]{1,6}\})|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}|\{[a-f\d]{1,6}\})|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) { - const u = c[0] === 'u'; - const bracket = c[1] === '{'; +function expand(project, concurrent, scheduler) { + if (concurrent === void 0) { + concurrent = Number.POSITIVE_INFINITY; + } + 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; +}()); - if ((u && !bracket && c.length === 5) || (c[0] === 'x' && c.length === 3)) { - return String.fromCharCode(parseInt(c.slice(1), 16)); - } +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(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["innerSubscribe"])(result, new _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleInnerSubscriber"](this))); + }; + ExpandSubscriber.prototype._complete = function () { + this.hasCompleted = true; + if (this.hasCompleted && this.active === 0) { + this.destination.complete(); + } + this.unsubscribe(); + }; + ExpandSubscriber.prototype.notifyNext = function (innerValue) { + this._next(innerValue); + }; + ExpandSubscriber.prototype.notifyComplete = function () { + var buffer = this.buffer; + this.active--; + if (buffer && buffer.length > 0) { + this._next(buffer.shift()); + } + if (this.hasCompleted && this.active === 0) { + this.destination.complete(); + } + }; + return ExpandSubscriber; +}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleOuterSubscriber"])); - if (u && bracket) { - return String.fromCodePoint(parseInt(c.slice(2, -1), 16)); - } +//# sourceMappingURL=expand.js.map - return ESCAPES.get(c) || c; -} -function parseArguments(name, arguments_) { - const results = []; - const chunks = arguments_.trim().split(/\s*,\s*/g); - let matches; +/***/ }), +/* 408 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - for (const chunk of chunks) { - const number = Number(chunk); - if (!Number.isNaN(number)) { - results.push(number); - } else if ((matches = chunk.match(STRING_REGEX))) { - results.push(matches[2].replace(ESCAPE_REGEX, (m, escape, character) => escape ? unescape(escape) : character)); - } 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; +"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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(17); +/** PURE_IMPORTS_START tslib,_Subscriber,_Subscription PURE_IMPORTS_END */ - 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 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 -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, styles] of Object.entries(enabled)) { - if (!Array.isArray(styles)) { - continue; - } +/***/ }), +/* 409 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - if (!(styleName in current)) { - throw new Error(`Unknown Chalk style: ${styleName}`); - } +"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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ - current = styles.length > 0 ? current[styleName](...styles) : current[styleName]; - } - return current; +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; +}()); -module.exports = (chalk, temporary) => { - const styles = []; - const chunks = []; - let chunk = []; +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"])); - // eslint-disable-next-line max-params - temporary.replace(TEMPLATE_REGEX, (m, escapeCharacter, inverse, style, close, character) => { - if (escapeCharacter) { - chunk.push(unescape(escapeCharacter)); - } else if (style) { - const string = chunk.join(''); - chunk = []; - chunks.push(styles.length === 0 ? string : buildStyle(chalk, styles)(string)); - styles.push({inverse, styles: parseStyle(style)}); - } else if (close) { - if (styles.length === 0) { - throw new Error('Found extraneous } in Chalk template literal'); - } +//# sourceMappingURL=find.js.map - chunks.push(buildStyle(chalk, styles)(chunk.join(''))); - chunk = []; - styles.pop(); - } else { - chunk.push(character); - } - }); - chunks.push(chunk.join('')); +/***/ }), +/* 410 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - if (styles.length > 0) { - const errMsg = `Chalk template literal is missing ${styles.length} closing bracket${styles.length === 1 ? '' : 's'} (\`}\`)`; - throw new Error(errMsg); - } +"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__(409); +/** PURE_IMPORTS_START _operators_find PURE_IMPORTS_END */ - return chunks.join(''); -}; +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 /***/ }), -/* 383 */ -/***/ (function(module, exports, __webpack_require__) { +/* 411 */ +/***/ (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__(63); +/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(105); +/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(402); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(392); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(401); +/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(25); +/** PURE_IMPORTS_START _util_EmptyError,_filter,_take,_defaultIfEmpty,_throwIfEmpty,_util_identity PURE_IMPORTS_END */ -const restoreCursor = __webpack_require__(384); - -let isHidden = false; - -exports.show = (writableStream = process.stderr) => { - if (!writableStream.isTTY) { - return; - } - isHidden = false; - writableStream.write('\u001B[?25h'); -}; -exports.hide = (writableStream = process.stderr) => { - if (!writableStream.isTTY) { - return; - } - restoreCursor(); - isHidden = true; - writableStream.write('\u001B[?25l'); -}; -exports.toggle = (force, writableStream) => { - if (force !== undefined) { - isHidden = force; - } - if (isHidden) { - exports.show(writableStream); - } else { - exports.hide(writableStream); - } -}; +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 /***/ }), -/* 384 */ -/***/ (function(module, exports, __webpack_require__) { +/* 412 */ +/***/ (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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ -const onetime = __webpack_require__(337); -const signalExit = __webpack_require__(309); -module.exports = onetime(() => { - signalExit(() => { - process.stderr.write('\u001B[?25h'); - }, {alwaysLast: true}); -}); +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 /***/ }), -/* 385 */ -/***/ (function(module, exports, __webpack_require__) { +/* 413 */ +/***/ (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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ -const spinners = Object.assign({}, __webpack_require__(386)); - -const spinnersList = Object.keys(spinners); - -Object.defineProperty(spinners, 'random', { - get() { - const randomIndex = Math.floor(Math.random() * spinnersList.length); - const spinnerName = spinnersList[randomIndex]; - return spinners[spinnerName]; - } -}); - -module.exports = spinners; -// TODO: Remove this for the next major release -module.exports.default = spinners; - - -/***/ }), -/* 386 */ -/***/ (function(module) { +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 -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\":[\"⢀⠀\",\"⡀⠀\",\"⠄⠀\",\"⢂⠀\",\"⡂⠀\",\"⠅⠀\",\"⢃⠀\",\"⡃⠀\",\"⠍⠀\",\"⢋⠀\",\"⡋⠀\",\"⠍⠁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⢈⠩\",\"⡀⢙\",\"⠄⡙\",\"⢂⠩\",\"⡂⢘\",\"⠅⡘\",\"⢃⠨\",\"⡃⢐\",\"⠍⡐\",\"⢋⠠\",\"⡋⢀\",\"⠍⡁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⠈⠩\",\"⠀⢙\",\"⠀⡙\",\"⠀⠩\",\"⠀⢘\",\"⠀⡘\",\"⠀⠨\",\"⠀⢐\",\"⠀⡐\",\"⠀⠠\",\"⠀⢀\",\"⠀⡀\"]},\"dots8Bit\":{\"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\":[\"🌍 \",\"🌎 \",\"🌏 \"]},\"material\":{\"interval\":17,\"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\":[\"🌲\",\"🎄\"]},\"grenade\":{\"interval\":80,\"frames\":[\"، \",\"′ \",\" ´ \",\" ‾ \",\" ⸌\",\" ⸊\",\" |\",\" ⁎\",\" ⁕\",\" ෴ \",\" ⁓\",\" \",\" \",\" \"]},\"point\":{\"interval\":125,\"frames\":[\"∙∙∙\",\"●∙∙\",\"∙●∙\",\"∙∙●\",\"∙∙∙\"]},\"layer\":{\"interval\":150,\"frames\":[\"-\",\"=\",\"≡\"]},\"betaWave\":{\"interval\":80,\"frames\":[\"ρββββββ\",\"βρβββββ\",\"ββρββββ\",\"βββρβββ\",\"ββββρββ\",\"βββββρβ\",\"ββββββρ\"]},\"aesthetic\":{\"interval\":80,\"frames\":[\"▰▱▱▱▱▱▱\",\"▰▰▱▱▱▱▱\",\"▰▰▰▱▱▱▱\",\"▰▰▰▰▱▱▱\",\"▰▰▰▰▰▱▱\",\"▰▰▰▰▰▰▱\",\"▰▰▰▰▰▰▰\",\"▰▱▱▱▱▱▱\"]}}"); /***/ }), -/* 387 */ -/***/ (function(module, exports, __webpack_require__) { +/* 414 */ +/***/ (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__(63); +/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(105); +/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(415); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(401); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(392); +/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(25); +/** PURE_IMPORTS_START _util_EmptyError,_filter,_takeLast,_throwIfEmpty,_defaultIfEmpty,_util_identity PURE_IMPORTS_END */ -const chalk = __webpack_require__(388); -const isSupported = process.platform !== 'win32' || process.env.CI || process.env.TERM === 'xterm-256color'; -const main = { - info: chalk.blue('ℹ'), - success: chalk.green('✔'), - warning: chalk.yellow('⚠'), - error: chalk.red('✖') -}; -const fallbacks = { - info: chalk.blue('i'), - success: chalk.green('√'), - warning: chalk.yellow('‼'), - error: chalk.red('×') -}; -module.exports = isSupported ? main : fallbacks; + +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 /***/ }), -/* 388 */ -/***/ (function(module, exports, __webpack_require__) { +/* 415 */ +/***/ (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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(62); +/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(43); +/** PURE_IMPORTS_START tslib,_Subscriber,_util_ArgumentOutOfRangeError,_observable_empty PURE_IMPORTS_END */ -const escapeStringRegexp = __webpack_require__(265); -const ansiStyles = __webpack_require__(389); -const stdoutColor = __webpack_require__(394).stdout; -const template = __webpack_require__(395); -const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); -// `supportsColor.level` → `ansiStyles.color[name]` mapping -const levelMapping = ['ansi', 'ansi', 'ansi256', 'ansi16m']; +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 -// `color-convert` models to exclude from the Chalk API due to conflicts and such -const skipModels = new Set(['gray']); -const styles = Object.create(null); +/***/ }), +/* 416 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -function applyOptions(obj, options) { - options = options || {}; +"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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ - // 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); +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 - 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); +/***/ }), +/* 417 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - chalk.template.constructor = Chalk; +"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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/* harmony import */ var _Notification__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(42); +/** PURE_IMPORTS_START tslib,_Subscriber,_Notification PURE_IMPORTS_END */ - 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'; +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 -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); - }; - } - }; -} +/***/ }), +/* 418 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -ansiStyles.bgColor.closeRe = new RegExp(escapeStringRegexp(ansiStyles.bgColor.close), 'g'); -for (const model of Object.keys(ansiStyles.bgColor.ansi)) { - if (skipModels.has(model)) { - continue; - } +"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__(419); +/** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ - 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); - }; - } - }; +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 -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; +/***/ }), +/* 419 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - Object.defineProperty(builder, 'level', { - enumerable: true, - get() { - return self.level; - }, - set(level) { - self.level = level; - } - }); +"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__(420); +/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(415); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(392); +/* harmony import */ var _util_pipe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(24); +/** PURE_IMPORTS_START _scan,_takeLast,_defaultIfEmpty,_util_pipe PURE_IMPORTS_END */ - 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 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 -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 ''; - } +/***/ }), +/* 420 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - if (argsLen > 1) { - // Don't slice `arguments`, it prevents V8 optimizations - for (let a = 1; a < argsLen; a++) { - str += ' ' + args[a]; - } - } +"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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ - 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 = ''; - } +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 - 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}`); - } +/***/ }), +/* 421 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - // Reset the original `dim` if we changed it to work around the Windows dimmed gray issue - ansiStyles.dim.open = originalDim; +"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__(99); +/** PURE_IMPORTS_START _observable_merge PURE_IMPORTS_END */ - return str; +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 -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]]; +/***/ }), +/* 422 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - for (let i = 1; i < strings.length; i++) { - parts.push(String(args[i - 1]).replace(/[{}\\]/g, '\\$&')); - parts.push(String(strings.raw[i])); - } +"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__(82); +/** PURE_IMPORTS_START _mergeMap PURE_IMPORTS_END */ - return template(chalk, parts.join('')); +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); } - -Object.defineProperties(Chalk.prototype, styles); - -module.exports = Chalk(); // eslint-disable-line new-cap -module.exports.supportsColor = stdoutColor; -module.exports.default = module.exports; // For TypeScript +//# sourceMappingURL=mergeMapTo.js.map /***/ }), -/* 389 */ -/***/ (function(module, exports, __webpack_require__) { +/* 423 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -/* WEBPACK VAR INJECTION */(function(module) { -const colorConvert = __webpack_require__(390); - -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`; -}; +__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__(12); +/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(90); +/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */ -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], +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; +}()); - // 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], +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); + } + else { + this.buffer.push(value); + } + }; + MergeScanSubscriber.prototype._innerSub = function (ish) { + var innerSubscriber = new _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleInnerSubscriber"](this); + var destination = this.destination; + destination.add(innerSubscriber); + var innerSubscription = Object(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["innerSubscribe"])(ish, innerSubscriber); + if (innerSubscription !== innerSubscriber) { + destination.add(innerSubscription); + } + }; + 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 (innerValue) { + var destination = this.destination; + this.acc = innerValue; + this.hasValue = true; + destination.next(innerValue); + }; + MergeScanSubscriber.prototype.notifyComplete = function () { + var buffer = this.buffer; + 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; +}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleOuterSubscriber"])); - // 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] - } - }; +//# sourceMappingURL=mergeScan.js.map - // Fix humans - styles.color.grey = styles.color.gray; - for (const groupName of Object.keys(styles)) { - const group = styles[groupName]; +/***/ }), +/* 424 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - for (const styleName of Object.keys(group)) { - const style = group[styleName]; +"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__(419); +/** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ - styles[styleName] = { - open: `\u001B[${style[0]}m`, - close: `\u001B[${style[1]}m` - }; +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 - group[styleName] = styles[styleName]; - codes.set(style[0], style[1]); - } +/***/ }), +/* 425 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - 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'; +"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__(26); +/** PURE_IMPORTS_START _observable_ConnectableObservable PURE_IMPORTS_END */ - styles.color.ansi = { - ansi: wrapAnsi16(ansi2ansi, 0) - }; - styles.color.ansi256 = { - ansi256: wrapAnsi256(ansi2ansi, 0) - }; - styles.color.ansi16m = { - rgb: wrapAnsi16m(rgb2rgb, 0) - }; +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; +}()); - styles.bgColor.ansi = { - ansi: wrapAnsi16(ansi2ansi, 10) - }; - styles.bgColor.ansi256 = { - ansi256: wrapAnsi256(ansi2ansi, 10) - }; - styles.bgColor.ansi16m = { - rgb: wrapAnsi16m(rgb2rgb, 10) - }; +//# sourceMappingURL=multicast.js.map - for (let key of Object.keys(colorConvert)) { - if (typeof colorConvert[key] !== 'object') { - continue; - } - const suite = colorConvert[key]; +/***/ }), +/* 426 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - if (key === 'ansi16') { - key = 'ansi'; - } +"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__(12); +/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(83); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(18); +/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(90); +/** PURE_IMPORTS_START tslib,_observable_from,_util_isArray,_innerSubscribe PURE_IMPORTS_END */ - 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; +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 = undefined; + 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).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 () { + this.subscribeToNextSource(); + }; + OnErrorResumeNextSubscriber.prototype.notifyComplete = function () { + 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 _innerSubscribe__WEBPACK_IMPORTED_MODULE_3__["SimpleInnerSubscriber"](this); + var destination = this.destination; + destination.add(innerSubscriber); + var innerSubscription = Object(_innerSubscribe__WEBPACK_IMPORTED_MODULE_3__["innerSubscribe"])(next, innerSubscriber); + if (innerSubscription !== innerSubscriber) { + destination.add(innerSubscription); + } + } + else { + this.destination.complete(); + } + }; + return OnErrorResumeNextSubscriber; +}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_3__["SimpleOuterSubscriber"])); +//# sourceMappingURL=onErrorResumeNext.js.map -// Make the export immutable -Object.defineProperty(module, 'exports', { - enumerable: true, - get: assembleStyles -}); - -/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(115)(module))) /***/ }), -/* 390 */ -/***/ (function(module, exports, __webpack_require__) { +/* 427 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -var conversions = __webpack_require__(391); -var route = __webpack_require__(393); +"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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ -var convert = {}; -var models = Object.keys(conversions); +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 -function wrapRaw(fn) { - var wrappedFn = function (args) { - if (args === undefined || args === null) { - return args; - } - if (arguments.length > 1) { - args = Array.prototype.slice.call(arguments); - } +/***/ }), +/* 428 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - return fn(args); - }; +"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__(104); +/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(105); +/** PURE_IMPORTS_START _util_not,_filter PURE_IMPORTS_END */ - // preserve .conversion property if there is one - if ('conversion' in fn) { - wrappedFn.conversion = fn.conversion; - } - return wrappedFn; +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 -function wrapRounded(fn) { - var wrappedFn = function (args) { - if (args === undefined || args === null) { - return args; - } - if (arguments.length > 1) { - args = Array.prototype.slice.call(arguments); - } +/***/ }), +/* 429 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - var result = fn(args); +"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__(66); +/** PURE_IMPORTS_START _map PURE_IMPORTS_END */ - // we're assuming the result is an array here. - // see notice in conversions.js; don't use box types - // in conversion functions. - if (typeof result === 'object') { - for (var len = result.length, i = 0; i < len; i++) { - result[i] = Math.round(result[i]); - } - } +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 != null ? currentProp[props[i]] : undefined; + if (p !== void 0) { + currentProp = p; + } + else { + return undefined; + } + } + return currentProp; + }; + return mapper; +} +//# sourceMappingURL=pluck.js.map - return result; - }; - // preserve .conversion property if there is one - if ('conversion' in fn) { - wrappedFn.conversion = fn.conversion; - } +/***/ }), +/* 430 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - return wrappedFn; -} +"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__(27); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(425); +/** PURE_IMPORTS_START _Subject,_multicast PURE_IMPORTS_END */ -models.forEach(function (fromModel) { - convert[fromModel] = {}; - Object.defineProperty(convert[fromModel], 'channels', {value: conversions[fromModel].channels}); - Object.defineProperty(convert[fromModel], 'labels', {value: conversions[fromModel].labels}); +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 - var routes = route(fromModel); - var routeModels = Object.keys(routes); - routeModels.forEach(function (toModel) { - var fn = routes[toModel]; +/***/ }), +/* 431 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - convert[fromModel][toModel] = wrapRounded(fn); - convert[fromModel][toModel].raw = wrapRaw(fn); - }); -}); +"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__(32); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(425); +/** PURE_IMPORTS_START _BehaviorSubject,_multicast PURE_IMPORTS_END */ -module.exports = convert; + +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 /***/ }), -/* 391 */ -/***/ (function(module, exports, __webpack_require__) { +/* 432 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -/* MIT license */ -var cssKeywords = __webpack_require__(392); +"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__(50); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(425); +/** PURE_IMPORTS_START _AsyncSubject,_multicast PURE_IMPORTS_END */ -// NOTE: conversions should only return primitive values (i.e. arrays, or -// values that give correct `typeof` results). -// do not use box values types (i.e. Number(), String(), etc.) -var reverseKeywords = {}; -for (var key in cssKeywords) { - if (cssKeywords.hasOwnProperty(key)) { - reverseKeywords[cssKeywords[key]] = key; - } +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 -var convert = module.exports = { - rgb: {channels: 3, labels: 'rgb'}, - hsl: {channels: 3, labels: 'hsl'}, - hsv: {channels: 3, labels: 'hsv'}, - hwb: {channels: 3, labels: 'hwb'}, - cmyk: {channels: 4, labels: 'cmyk'}, - xyz: {channels: 3, labels: 'xyz'}, - lab: {channels: 3, labels: 'lab'}, - lch: {channels: 3, labels: 'lch'}, - hex: {channels: 1, labels: ['hex']}, - keyword: {channels: 1, labels: ['keyword']}, - ansi16: {channels: 1, labels: ['ansi16']}, - ansi256: {channels: 1, labels: ['ansi256']}, - hcg: {channels: 3, labels: ['h', 'c', 'g']}, - apple: {channels: 3, labels: ['r16', 'g16', 'b16']}, - gray: {channels: 1, labels: ['gray']} -}; -// hide .channels and .labels properties -for (var model in convert) { - if (convert.hasOwnProperty(model)) { - if (!('channels' in convert[model])) { - throw new Error('missing channels property: ' + model); - } +/***/ }), +/* 433 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - if (!('labels' in convert[model])) { - throw new Error('missing channel labels property: ' + model); - } +"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__(33); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(425); +/** PURE_IMPORTS_START _ReplaySubject,_multicast PURE_IMPORTS_END */ - if (convert[model].labels.length !== convert[model].channels) { - throw new Error('channel and label counts mismatch: ' + model); - } - var channels = convert[model].channels; - var labels = convert[model].labels; - delete convert[model].channels; - delete convert[model].labels; - Object.defineProperty(convert[model], 'channels', {value: channels}); - Object.defineProperty(convert[model], 'labels', {value: labels}); - } +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 -convert.rgb.hsl = function (rgb) { - var r = rgb[0] / 255; - var g = rgb[1] / 255; - var b = rgb[2] / 255; - var min = Math.min(r, g, b); - var max = Math.max(r, g, b); - var delta = max - min; - var h; - var s; - var l; - if (max === min) { - h = 0; - } else if (r === max) { - h = (g - b) / delta; - } else if (g === max) { - h = 2 + (b - r) / delta; - } else if (b === max) { - h = 4 + (r - g) / delta; - } +/***/ }), +/* 434 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - h = Math.min(h * 60, 360); +"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__(18); +/* harmony import */ var _observable_race__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(106); +/** PURE_IMPORTS_START _util_isArray,_observable_race PURE_IMPORTS_END */ - if (h < 0) { - h += 360; - } - l = (min + max) / 2; +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 - if (max === min) { - s = 0; - } else if (l <= 0.5) { - s = delta / (max + min); - } else { - s = delta / (2 - max - min); - } - return [h, s * 100, l * 100]; -}; +/***/ }), +/* 435 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -convert.rgb.hsv = function (rgb) { - var rdif; - var gdif; - var bdif; - var h; - var s; +"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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(43); +/** PURE_IMPORTS_START tslib,_Subscriber,_observable_empty PURE_IMPORTS_END */ - var r = rgb[0] / 255; - var g = rgb[1] / 255; - var b = rgb[2] / 255; - var v = Math.max(r, g, b); - var diff = v - Math.min(r, g, b); - var diffc = function (c) { - return (v - c) / 6 / diff + 1 / 2; - }; - - if (diff === 0) { - h = s = 0; - } else { - s = diff / v; - rdif = diffc(r); - gdif = diffc(g); - bdif = diffc(b); - - if (r === v) { - h = bdif - gdif; - } else if (g === v) { - h = (1 / 3) + rdif - bdif; - } else if (b === v) { - h = (2 / 3) + gdif - rdif; - } - if (h < 0) { - h += 1; - } else if (h > 1) { - h -= 1; - } - } - - return [ - h * 360, - s * 100, - v * 100 - ]; -}; - -convert.rgb.hwb = function (rgb) { - var r = rgb[0]; - var g = rgb[1]; - var b = rgb[2]; - var h = convert.rgb.hsl(rgb)[0]; - var w = 1 / 255 * Math.min(r, Math.min(g, b)); - - b = 1 - 1 / 255 * Math.max(r, Math.max(g, b)); - - return [h, w * 100, b * 100]; -}; - -convert.rgb.cmyk = function (rgb) { - var r = rgb[0] / 255; - var g = rgb[1] / 255; - var b = rgb[2] / 255; - var c; - var m; - var y; - var k; - - k = Math.min(1 - r, 1 - g, 1 - b); - c = (1 - r - k) / (1 - k) || 0; - m = (1 - g - k) / (1 - k) || 0; - y = (1 - b - k) / (1 - k) || 0; - return [c * 100, m * 100, y * 100, k * 100]; -}; -/** - * See https://en.m.wikipedia.org/wiki/Euclidean_distance#Squared_Euclidean_distance - * */ -function comparativeDistance(x, y) { - return ( - Math.pow(x[0] - y[0], 2) + - Math.pow(x[1] - y[1], 2) + - Math.pow(x[2] - y[2], 2) - ); +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 -convert.rgb.keyword = function (rgb) { - var reversed = reverseKeywords[rgb]; - if (reversed) { - return reversed; - } - - var currentClosestDistance = Infinity; - var currentClosestKeyword; - for (var keyword in cssKeywords) { - if (cssKeywords.hasOwnProperty(keyword)) { - var value = cssKeywords[keyword]; +/***/ }), +/* 436 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - // Compute comparative distance - var distance = comparativeDistance(rgb, value); +"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__(12); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(27); +/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(90); +/** PURE_IMPORTS_START tslib,_Subject,_innerSubscribe PURE_IMPORTS_END */ - // Check if its less, if so set as closest - if (distance < currentClosestDistance) { - currentClosestDistance = distance; - currentClosestKeyword = keyword; - } - } - } - return currentClosestKeyword; -}; -convert.keyword.rgb = function (keyword) { - return cssKeywords[keyword]; -}; +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 () { + this.sourceIsBeingSubscribedTo = true; + this.source.subscribe(this); + }; + RepeatWhenSubscriber.prototype.notifyComplete = function () { + 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(undefined); + } + }; + RepeatWhenSubscriber.prototype._unsubscribe = function () { + var _a = this, notifications = _a.notifications, retriesSubscription = _a.retriesSubscription; + if (notifications) { + notifications.unsubscribe(); + this.notifications = undefined; + } + if (retriesSubscription) { + retriesSubscription.unsubscribe(); + this.retriesSubscription = undefined; + } + this.retries = undefined; + }; + 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(_innerSubscribe__WEBPACK_IMPORTED_MODULE_2__["innerSubscribe"])(retries, new _innerSubscribe__WEBPACK_IMPORTED_MODULE_2__["SimpleInnerSubscriber"](this)); + }; + return RepeatWhenSubscriber; +}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_2__["SimpleOuterSubscriber"])); +//# sourceMappingURL=repeatWhen.js.map -convert.rgb.xyz = function (rgb) { - var r = rgb[0] / 255; - var g = rgb[1] / 255; - var b = rgb[2] / 255; - // assume sRGB - r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92); - g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92); - b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92); +/***/ }), +/* 437 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805); - var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722); - var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505); +"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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ - return [x * 100, y * 100, z * 100]; -}; -convert.rgb.lab = function (rgb) { - var xyz = convert.rgb.xyz(rgb); - var x = xyz[0]; - var y = xyz[1]; - var z = xyz[2]; - var l; - var a; - var b; +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 - x /= 95.047; - y /= 100; - z /= 108.883; - x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116); - y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116); - z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116); +/***/ }), +/* 438 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - l = (116 * y) - 16; - a = 500 * (x - y); - b = 200 * (y - z); +"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__(12); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(27); +/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(90); +/** PURE_IMPORTS_START tslib,_Subject,_innerSubscribe PURE_IMPORTS_END */ - return [l, a, b]; -}; -convert.hsl.rgb = function (hsl) { - var h = hsl[0] / 360; - var s = hsl[1] / 100; - var l = hsl[2] / 100; - var t1; - var t2; - var t3; - var rgb; - var val; - if (s === 0) { - val = l * 255; - return [val, val, val]; - } +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(_innerSubscribe__WEBPACK_IMPORTED_MODULE_2__["innerSubscribe"])(retries, new _innerSubscribe__WEBPACK_IMPORTED_MODULE_2__["SimpleInnerSubscriber"](this)); + } + else { + this.errors = undefined; + this.retriesSubscription = undefined; + } + 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 = undefined; + } + if (retriesSubscription) { + retriesSubscription.unsubscribe(); + this.retriesSubscription = undefined; + } + this.retries = undefined; + }; + RetryWhenSubscriber.prototype.notifyNext = function () { + var _unsubscribe = this._unsubscribe; + this._unsubscribe = null; + this._unsubscribeAndRecycle(); + this._unsubscribe = _unsubscribe; + this.source.subscribe(this); + }; + return RetryWhenSubscriber; +}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_2__["SimpleOuterSubscriber"])); +//# sourceMappingURL=retryWhen.js.map - if (l < 0.5) { - t2 = l * (1 + s); - } else { - t2 = l + s - l * s; - } - t1 = 2 * l - t2; +/***/ }), +/* 439 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - rgb = [0, 0, 0]; - for (var i = 0; i < 3; i++) { - t3 = h + 1 / 3 * -(i - 1); - if (t3 < 0) { - t3++; - } - if (t3 > 1) { - t3--; - } +"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__(12); +/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(90); +/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */ - if (6 * t3 < 1) { - val = t1 + (t2 - t1) * 6 * t3; - } else if (2 * t3 < 1) { - val = t2; - } else if (3 * t3 < 2) { - val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; - } else { - val = t1; - } - rgb[i] = val * 255; - } +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(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["innerSubscribe"])(this.notifier, new _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleInnerSubscriber"](sampleSubscriber))); + 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 () { + 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; +}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleOuterSubscriber"])); +//# sourceMappingURL=sample.js.map - return rgb; -}; -convert.hsl.hsv = function (hsl) { - var h = hsl[0]; - var s = hsl[1] / 100; - var l = hsl[2] / 100; - var smin = s; - var lmin = Math.max(l, 0.01); - var sv; - var v; +/***/ }), +/* 440 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - l *= 2; - s *= (l <= 1) ? l : 2 - l; - smin *= lmin <= 1 ? lmin : 2 - lmin; - v = (l + s) / 2; - sv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s); +"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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(55); +/** PURE_IMPORTS_START tslib,_Subscriber,_scheduler_async PURE_IMPORTS_END */ - return [h, sv * 100, v * 100]; -}; -convert.hsv.rgb = function (hsv) { - var h = hsv[0] / 60; - var s = hsv[1] / 100; - var v = hsv[2] / 100; - var hi = Math.floor(h) % 6; - var f = h - Math.floor(h); - var p = 255 * v * (1 - s); - var q = 255 * v * (1 - (s * f)); - var t = 255 * v * (1 - (s * (1 - f))); - v *= 255; +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 - switch (hi) { - case 0: - return [v, t, p]; - case 1: - return [q, v, p]; - case 2: - return [p, v, t]; - case 3: - return [p, q, v]; - case 4: - return [t, p, v]; - case 5: - return [v, p, q]; - } -}; -convert.hsv.hsl = function (hsv) { - var h = hsv[0]; - var s = hsv[1] / 100; - var v = hsv[2] / 100; - var vmin = Math.max(v, 0.01); - var lmin; - var sl; - var l; +/***/ }), +/* 441 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - l = (2 - s) * v; - lmin = (2 - s) * vmin; - sl = s * vmin; - sl /= (lmin <= 1) ? lmin : 2 - lmin; - sl = sl || 0; - l /= 2; +"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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ - return [h, sl * 100, l * 100]; -}; -// http://dev.w3.org/csswg/css-color/#hwb-to-rgb -convert.hwb.rgb = function (hwb) { - var h = hwb[0] / 360; - var wh = hwb[1] / 100; - var bl = hwb[2] / 100; - var ratio = wh + bl; - var i; - var v; - var f; - var n; +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; +}()); - // wh + bl cant be > 1 - if (ratio > 1) { - wh /= ratio; - bl /= ratio; - } +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"])); - i = Math.floor(6 * h); - v = 1 - bl; - f = 6 * h - i; +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 - if ((i & 0x01) !== 0) { - f = 1 - f; - } - n = wh + f * (v - wh); // linear interpolation +/***/ }), +/* 442 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - var r; - var g; - var b; - switch (i) { - default: - case 6: - case 0: r = v; g = n; b = wh; break; - case 1: r = n; g = v; b = wh; break; - case 2: r = wh; g = v; b = n; break; - case 3: r = wh; g = n; b = v; break; - case 4: r = n; g = wh; b = v; break; - case 5: r = v; g = wh; b = n; break; - } +"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__(425); +/* harmony import */ var _refCount__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(30); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(27); +/** PURE_IMPORTS_START _multicast,_refCount,_Subject PURE_IMPORTS_END */ - return [r * 255, g * 255, b * 255]; -}; -convert.cmyk.rgb = function (cmyk) { - var c = cmyk[0] / 100; - var m = cmyk[1] / 100; - var y = cmyk[2] / 100; - var k = cmyk[3] / 100; - var r; - var g; - var b; - r = 1 - Math.min(1, c * (1 - k) + k); - g = 1 - Math.min(1, m * (1 - k) + k); - b = 1 - Math.min(1, y * (1 - k) + k); +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 - return [r * 255, g * 255, b * 255]; -}; -convert.xyz.rgb = function (xyz) { - var x = xyz[0] / 100; - var y = xyz[1] / 100; - var z = xyz[2] / 100; - var r; - var g; - var b; +/***/ }), +/* 443 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986); - g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415); - b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570); +"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__(33); +/** PURE_IMPORTS_START _ReplaySubject PURE_IMPORTS_END */ - // assume sRGB - r = r > 0.0031308 - ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055) - : r * 12.92; +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++; + var innerSub; + if (!subject || hasError) { + hasError = false; + subject = new _ReplaySubject__WEBPACK_IMPORTED_MODULE_0__["ReplaySubject"](bufferSize, windowTime, scheduler); + innerSub = subject.subscribe(this); + subscription = source.subscribe({ + next: function (value) { subject.next(value); }, + error: function (err) { + hasError = true; + subject.error(err); + }, + complete: function () { + isComplete = true; + subscription = undefined; + subject.complete(); + }, + }); + } + else { + 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 - g = g > 0.0031308 - ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055) - : g * 12.92; - b = b > 0.0031308 - ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055) - : b * 12.92; +/***/ }), +/* 444 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - r = Math.min(Math.max(0, r), 1); - g = Math.min(Math.max(0, g), 1); - b = Math.min(Math.max(0, b), 1); +"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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(63); +/** PURE_IMPORTS_START tslib,_Subscriber,_util_EmptyError PURE_IMPORTS_END */ - return [r * 255, g * 255, b * 255]; -}; -convert.xyz.lab = function (xyz) { - var x = xyz[0]; - var y = xyz[1]; - var z = xyz[2]; - var l; - var a; - var b; - x /= 95.047; - y /= 100; - z /= 108.883; +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 - x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116); - y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116); - z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116); - l = (116 * y) - 16; - a = 500 * (x - y); - b = 200 * (y - z); +/***/ }), +/* 445 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - return [l, a, b]; -}; +"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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ -convert.lab.xyz = function (lab) { - var l = lab[0]; - var a = lab[1]; - var b = lab[2]; - var x; - var y; - var z; - y = (l + 16) / 116; - x = a / 500 + y; - z = y - b / 200; +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 - var y2 = Math.pow(y, 3); - var x2 = Math.pow(x, 3); - var z2 = Math.pow(z, 3); - y = y2 > 0.008856 ? y2 : (y - 16 / 116) / 7.787; - x = x2 > 0.008856 ? x2 : (x - 16 / 116) / 7.787; - z = z2 > 0.008856 ? z2 : (z - 16 / 116) / 7.787; - x *= 95.047; - y *= 100; - z *= 108.883; +/***/ }), +/* 446 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - return [x, y, z]; -}; +"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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(62); +/** PURE_IMPORTS_START tslib,_Subscriber,_util_ArgumentOutOfRangeError PURE_IMPORTS_END */ -convert.lab.lch = function (lab) { - var l = lab[0]; - var a = lab[1]; - var b = lab[2]; - var hr; - var h; - var c; - hr = Math.atan2(b, a); - h = hr * 360 / 2 / Math.PI; - if (h < 0) { - h += 360; - } +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 - c = Math.sqrt(a * a + b * b); - return [l, c, h]; -}; +/***/ }), +/* 447 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -convert.lch.lab = function (lch) { - var l = lch[0]; - var c = lch[1]; - var h = lch[2]; - var a; - var b; - var hr; +"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__(12); +/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(90); +/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */ - hr = h / 360 * 2 * Math.PI; - a = c * Math.cos(hr); - b = c * Math.sin(hr); - return [l, a, b]; -}; +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 _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleInnerSubscriber"](_this); + _this.add(innerSubscriber); + _this.innerSubscription = innerSubscriber; + var innerSubscription = Object(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["innerSubscribe"])(notifier, innerSubscriber); + if (innerSubscription !== innerSubscriber) { + _this.add(innerSubscription); + _this.innerSubscription = innerSubscription; + } + return _this; + } + SkipUntilSubscriber.prototype._next = function (value) { + if (this.hasValue) { + _super.prototype._next.call(this, value); + } + }; + SkipUntilSubscriber.prototype.notifyNext = function () { + this.hasValue = true; + if (this.innerSubscription) { + this.innerSubscription.unsubscribe(); + } + }; + SkipUntilSubscriber.prototype.notifyComplete = function () { + }; + return SkipUntilSubscriber; +}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleOuterSubscriber"])); +//# sourceMappingURL=skipUntil.js.map -convert.rgb.ansi16 = function (args) { - var r = args[0]; - var g = args[1]; - var b = args[2]; - var value = 1 in arguments ? arguments[1] : convert.rgb.hsv(args)[2]; // hsv -> ansi16 optimization - value = Math.round(value / 50); +/***/ }), +/* 448 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - if (value === 0) { - return 30; - } +"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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ - var ansi = 30 - + ((Math.round(b / 255) << 2) - | (Math.round(g / 255) << 1) - | Math.round(r / 255)); - if (value === 2) { - ansi += 60; - } +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 - return ansi; -}; -convert.hsv.ansi16 = function (args) { - // optimization here; we already know the value and don't need to get - // it converted for us. - return convert.rgb.ansi16(convert.hsv.rgb(args), args[2]); -}; +/***/ }), +/* 449 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -convert.rgb.ansi256 = function (args) { - var r = args[0]; - var g = args[1]; - var b = args[2]; +"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__(79); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(45); +/** PURE_IMPORTS_START _observable_concat,_util_isScheduler PURE_IMPORTS_END */ - // we use the extended greyscale palette here, with the exception of - // black and white. normal palette only has 4 greyscale shades. - if (r === g && g === b) { - if (r < 8) { - return 16; - } - if (r > 248) { - return 231; - } +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 - return Math.round(((r - 8) / 247) * 24) + 232; - } - var ansi = 16 - + (36 * Math.round(r / 255 * 5)) - + (6 * Math.round(g / 255 * 5)) - + Math.round(b / 255 * 5); +/***/ }), +/* 450 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - return ansi; -}; +"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__(451); +/** PURE_IMPORTS_START _observable_SubscribeOnObservable PURE_IMPORTS_END */ -convert.ansi16.rgb = function (args) { - var color = args % 10; +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 - // handle greyscale - if (color === 0 || color === 7) { - if (args > 50) { - color += 3.5; - } - color = color / 10.5 * 255; +/***/ }), +/* 451 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - return [color, color, color]; - } +"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__(12); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9); +/* harmony import */ var _scheduler_asap__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(51); +/* harmony import */ var _util_isNumeric__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(98); +/** PURE_IMPORTS_START tslib,_Observable,_scheduler_asap,_util_isNumeric PURE_IMPORTS_END */ - var mult = (~~(args > 50) + 1) * 0.5; - var r = ((color & 1) * mult) * 255; - var g = (((color >> 1) & 1) * mult) * 255; - var b = (((color >> 2) & 1) * mult) * 255; - return [r, g, b]; -}; -convert.ansi256.rgb = function (args) { - // handle greyscale - if (args >= 232) { - var c = (args - 232) * 10 + 8; - return [c, c, c]; - } - args -= 16; +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"])); - var rem; - var r = Math.floor(args / 36) / 5 * 255; - var g = Math.floor((rem = args % 36) / 6) / 5 * 255; - var b = (rem % 6) / 5 * 255; +//# sourceMappingURL=SubscribeOnObservable.js.map - return [r, g, b]; -}; -convert.rgb.hex = function (args) { - var integer = ((Math.round(args[0]) & 0xFF) << 16) - + ((Math.round(args[1]) & 0xFF) << 8) - + (Math.round(args[2]) & 0xFF); +/***/ }), +/* 452 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - var string = integer.toString(16).toUpperCase(); - return '000000'.substring(string.length) + string; -}; +"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__(453); +/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(25); +/** PURE_IMPORTS_START _switchMap,_util_identity PURE_IMPORTS_END */ -convert.hex.rgb = function (args) { - var match = args.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i); - if (!match) { - return [0, 0, 0]; - } - var colorString = match[0]; +function switchAll() { + return Object(_switchMap__WEBPACK_IMPORTED_MODULE_0__["switchMap"])(_util_identity__WEBPACK_IMPORTED_MODULE_1__["identity"]); +} +//# sourceMappingURL=switchAll.js.map - if (match[0].length === 3) { - colorString = colorString.split('').map(function (char) { - return char + char; - }).join(''); - } - var integer = parseInt(colorString, 16); - var r = (integer >> 16) & 0xFF; - var g = (integer >> 8) & 0xFF; - var b = integer & 0xFF; +/***/ }), +/* 453 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - return [r, g, b]; -}; +"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__(12); +/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(66); +/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(83); +/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(90); +/** PURE_IMPORTS_START tslib,_map,_observable_from,_innerSubscribe PURE_IMPORTS_END */ -convert.rgb.hcg = function (rgb) { - var r = rgb[0] / 255; - var g = rgb[1] / 255; - var b = rgb[2] / 255; - var max = Math.max(Math.max(r, g), b); - var min = Math.min(Math.min(r, g), b); - var chroma = (max - min); - var grayscale; - var hue; - if (chroma < 1) { - grayscale = min / (1 - chroma); - } else { - grayscale = 0; - } - if (chroma <= 0) { - hue = 0; - } else - if (max === r) { - hue = ((g - b) / chroma) % 6; - } else - if (max === g) { - hue = 2 + (b - r) / chroma; - } else { - hue = 4 + (r - g) / chroma + 4; - } - hue /= 6; - hue %= 1; +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_2__["from"])(project(a, i)).pipe(Object(_map__WEBPACK_IMPORTED_MODULE_1__["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); + }; + SwitchMapSubscriber.prototype._innerSub = function (result) { + var innerSubscription = this.innerSubscription; + if (innerSubscription) { + innerSubscription.unsubscribe(); + } + var innerSubscriber = new _innerSubscribe__WEBPACK_IMPORTED_MODULE_3__["SimpleInnerSubscriber"](this); + var destination = this.destination; + destination.add(innerSubscriber); + this.innerSubscription = Object(_innerSubscribe__WEBPACK_IMPORTED_MODULE_3__["innerSubscribe"])(result, innerSubscriber); + if (this.innerSubscription !== innerSubscriber) { + destination.add(this.innerSubscription); + } + }; + 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 = undefined; + }; + SwitchMapSubscriber.prototype.notifyComplete = function () { + this.innerSubscription = undefined; + if (this.isStopped) { + _super.prototype._complete.call(this); + } + }; + SwitchMapSubscriber.prototype.notifyNext = function (innerValue) { + this.destination.next(innerValue); + }; + return SwitchMapSubscriber; +}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_3__["SimpleOuterSubscriber"])); +//# sourceMappingURL=switchMap.js.map - return [hue * 360, chroma * 100, grayscale * 100]; -}; -convert.hsl.hcg = function (hsl) { - var s = hsl[1] / 100; - var l = hsl[2] / 100; - var c = 1; - var f = 0; +/***/ }), +/* 454 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - if (l < 0.5) { - c = 2.0 * s * l; - } else { - c = 2.0 * s * (1.0 - l); - } - - if (c < 1.0) { - f = (l - 0.5 * c) / (1.0 - c); - } - - return [hsl[0], c * 100, f * 100]; -}; - -convert.hsv.hcg = function (hsv) { - var s = hsv[1] / 100; - var v = hsv[2] / 100; - - var c = s * v; - var f = 0; +"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__(453); +/** PURE_IMPORTS_START _switchMap PURE_IMPORTS_END */ - if (c < 1.0) { - f = (v - c) / (1 - c); - } +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 - return [hsv[0], c * 100, f * 100]; -}; -convert.hcg.rgb = function (hcg) { - var h = hcg[0] / 360; - var c = hcg[1] / 100; - var g = hcg[2] / 100; +/***/ }), +/* 455 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - if (c === 0.0) { - return [g * 255, g * 255, g * 255]; - } +"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__(12); +/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(90); +/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */ - var pure = [0, 0, 0]; - var hi = (h % 1) * 6; - var v = hi % 1; - var w = 1 - v; - var mg = 0; - switch (Math.floor(hi)) { - case 0: - pure[0] = 1; pure[1] = v; pure[2] = 0; break; - case 1: - pure[0] = w; pure[1] = 1; pure[2] = 0; break; - case 2: - pure[0] = 0; pure[1] = 1; pure[2] = v; break; - case 3: - pure[0] = 0; pure[1] = w; pure[2] = 1; break; - case 4: - pure[0] = v; pure[1] = 0; pure[2] = 1; break; - default: - pure[0] = 1; pure[1] = 0; pure[2] = w; - } +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(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["innerSubscribe"])(this.notifier, new _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleInnerSubscriber"](takeUntilSubscriber)); + 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 () { + this.seenValue = true; + this.complete(); + }; + TakeUntilSubscriber.prototype.notifyComplete = function () { + }; + return TakeUntilSubscriber; +}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleOuterSubscriber"])); +//# sourceMappingURL=takeUntil.js.map - mg = (1.0 - c) * g; - return [ - (c * pure[0] + mg) * 255, - (c * pure[1] + mg) * 255, - (c * pure[2] + mg) * 255 - ]; -}; +/***/ }), +/* 456 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -convert.hcg.hsv = function (hcg) { - var c = hcg[1] / 100; - var g = hcg[2] / 100; +"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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ - var v = c + g * (1.0 - c); - var f = 0; - if (v > 0.0) { - f = c / v; - } +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 - return [hcg[0], f * 100, v * 100]; -}; -convert.hcg.hsl = function (hcg) { - var c = hcg[1] / 100; - var g = hcg[2] / 100; +/***/ }), +/* 457 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - var l = g * (1.0 - c) + 0.5 * c; - var s = 0; +"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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/* harmony import */ var _util_noop__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(60); +/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(13); +/** PURE_IMPORTS_START tslib,_Subscriber,_util_noop,_util_isFunction PURE_IMPORTS_END */ - if (l > 0.0 && l < 0.5) { - s = c / (2 * l); - } else - if (l >= 0.5 && l < 1.0) { - s = c / (2 * (1 - l)); - } - return [hcg[0], s * 100, l * 100]; -}; -convert.hcg.hwb = function (hcg) { - var c = hcg[1] / 100; - var g = hcg[2] / 100; - var v = c + g * (1.0 - c); - return [hcg[0], (v - c) * 100, (1 - v) * 100]; -}; -convert.hwb.hcg = function (hwb) { - var w = hwb[1] / 100; - var b = hwb[2] / 100; - var v = 1 - b; - var c = v - w; - var g = 0; +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 - if (c < 1) { - g = (v - c) / (1 - c); - } - return [hwb[0], c * 100, g * 100]; -}; +/***/ }), +/* 458 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -convert.apple.rgb = function (apple) { - return [(apple[0] / 65535) * 255, (apple[1] / 65535) * 255, (apple[2] / 65535) * 255]; -}; +"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__(12); +/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(90); +/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */ -convert.rgb.apple = function (rgb) { - return [(rgb[0] / 255) * 65535, (rgb[1] / 255) * 65535, (rgb[2] / 255) * 65535]; -}; -convert.gray.rgb = function (args) { - return [args[0] / 100 * 255, args[0] / 100 * 255, args[0] / 100 * 255]; +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 = undefined; + }; + ThrottleSubscriber.prototype.throttle = function (value) { + var duration = this.tryDurationSelector(value); + if (!!duration) { + this.add(this._throttled = Object(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["innerSubscribe"])(duration, new _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleInnerSubscriber"](this))); + } + }; + 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 = undefined; + if (_trailing) { + this.send(); + } + }; + ThrottleSubscriber.prototype.notifyNext = function () { + this.throttlingDone(); + }; + ThrottleSubscriber.prototype.notifyComplete = function () { + this.throttlingDone(); + }; + return ThrottleSubscriber; +}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleOuterSubscriber"])); +//# sourceMappingURL=throttle.js.map -convert.gray.hsl = convert.gray.hsv = function (args) { - return [0, 0, args[0]]; -}; -convert.gray.hwb = function (gray) { - return [0, 100, gray[0]]; -}; +/***/ }), +/* 459 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -convert.gray.cmyk = function (gray) { - return [0, 0, 0, gray[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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(55); +/* harmony import */ var _throttle__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(458); +/** PURE_IMPORTS_START tslib,_Subscriber,_scheduler_async,_throttle PURE_IMPORTS_END */ -convert.gray.lab = function (gray) { - return [gray[0], 0, 0]; -}; -convert.gray.hex = function (gray) { - var val = Math.round(gray[0] / 100 * 255) & 0xFF; - var integer = (val << 16) + (val << 8) + val; - var string = integer.toString(16).toUpperCase(); - return '000000'.substring(string.length) + string; -}; -convert.rgb.gray = function (rgb) { - var val = (rgb[0] + rgb[1] + rgb[2]) / 3; - return [val / 255 * 100]; -}; +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 /***/ }), -/* 392 */ -/***/ (function(module, exports, __webpack_require__) { +/* 460 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; - - -module.exports = { - "aliceblue": [240, 248, 255], - "antiquewhite": [250, 235, 215], - "aqua": [0, 255, 255], - "aquamarine": [127, 255, 212], - "azure": [240, 255, 255], - "beige": [245, 245, 220], - "bisque": [255, 228, 196], - "black": [0, 0, 0], - "blanchedalmond": [255, 235, 205], - "blue": [0, 0, 255], - "blueviolet": [138, 43, 226], - "brown": [165, 42, 42], - "burlywood": [222, 184, 135], - "cadetblue": [95, 158, 160], - "chartreuse": [127, 255, 0], - "chocolate": [210, 105, 30], - "coral": [255, 127, 80], - "cornflowerblue": [100, 149, 237], - "cornsilk": [255, 248, 220], - "crimson": [220, 20, 60], - "cyan": [0, 255, 255], - "darkblue": [0, 0, 139], - "darkcyan": [0, 139, 139], - "darkgoldenrod": [184, 134, 11], - "darkgray": [169, 169, 169], - "darkgreen": [0, 100, 0], - "darkgrey": [169, 169, 169], - "darkkhaki": [189, 183, 107], - "darkmagenta": [139, 0, 139], - "darkolivegreen": [85, 107, 47], - "darkorange": [255, 140, 0], - "darkorchid": [153, 50, 204], - "darkred": [139, 0, 0], - "darksalmon": [233, 150, 122], - "darkseagreen": [143, 188, 143], - "darkslateblue": [72, 61, 139], - "darkslategray": [47, 79, 79], - "darkslategrey": [47, 79, 79], - "darkturquoise": [0, 206, 209], - "darkviolet": [148, 0, 211], - "deeppink": [255, 20, 147], - "deepskyblue": [0, 191, 255], - "dimgray": [105, 105, 105], - "dimgrey": [105, 105, 105], - "dodgerblue": [30, 144, 255], - "firebrick": [178, 34, 34], - "floralwhite": [255, 250, 240], - "forestgreen": [34, 139, 34], - "fuchsia": [255, 0, 255], - "gainsboro": [220, 220, 220], - "ghostwhite": [248, 248, 255], - "gold": [255, 215, 0], - "goldenrod": [218, 165, 32], - "gray": [128, 128, 128], - "green": [0, 128, 0], - "greenyellow": [173, 255, 47], - "grey": [128, 128, 128], - "honeydew": [240, 255, 240], - "hotpink": [255, 105, 180], - "indianred": [205, 92, 92], - "indigo": [75, 0, 130], - "ivory": [255, 255, 240], - "khaki": [240, 230, 140], - "lavender": [230, 230, 250], - "lavenderblush": [255, 240, 245], - "lawngreen": [124, 252, 0], - "lemonchiffon": [255, 250, 205], - "lightblue": [173, 216, 230], - "lightcoral": [240, 128, 128], - "lightcyan": [224, 255, 255], - "lightgoldenrodyellow": [250, 250, 210], - "lightgray": [211, 211, 211], - "lightgreen": [144, 238, 144], - "lightgrey": [211, 211, 211], - "lightpink": [255, 182, 193], - "lightsalmon": [255, 160, 122], - "lightseagreen": [32, 178, 170], - "lightskyblue": [135, 206, 250], - "lightslategray": [119, 136, 153], - "lightslategrey": [119, 136, 153], - "lightsteelblue": [176, 196, 222], - "lightyellow": [255, 255, 224], - "lime": [0, 255, 0], - "limegreen": [50, 205, 50], - "linen": [250, 240, 230], - "magenta": [255, 0, 255], - "maroon": [128, 0, 0], - "mediumaquamarine": [102, 205, 170], - "mediumblue": [0, 0, 205], - "mediumorchid": [186, 85, 211], - "mediumpurple": [147, 112, 219], - "mediumseagreen": [60, 179, 113], - "mediumslateblue": [123, 104, 238], - "mediumspringgreen": [0, 250, 154], - "mediumturquoise": [72, 209, 204], - "mediumvioletred": [199, 21, 133], - "midnightblue": [25, 25, 112], - "mintcream": [245, 255, 250], - "mistyrose": [255, 228, 225], - "moccasin": [255, 228, 181], - "navajowhite": [255, 222, 173], - "navy": [0, 0, 128], - "oldlace": [253, 245, 230], - "olive": [128, 128, 0], - "olivedrab": [107, 142, 35], - "orange": [255, 165, 0], - "orangered": [255, 69, 0], - "orchid": [218, 112, 214], - "palegoldenrod": [238, 232, 170], - "palegreen": [152, 251, 152], - "paleturquoise": [175, 238, 238], - "palevioletred": [219, 112, 147], - "papayawhip": [255, 239, 213], - "peachpuff": [255, 218, 185], - "peru": [205, 133, 63], - "pink": [255, 192, 203], - "plum": [221, 160, 221], - "powderblue": [176, 224, 230], - "purple": [128, 0, 128], - "rebeccapurple": [102, 51, 153], - "red": [255, 0, 0], - "rosybrown": [188, 143, 143], - "royalblue": [65, 105, 225], - "saddlebrown": [139, 69, 19], - "salmon": [250, 128, 114], - "sandybrown": [244, 164, 96], - "seagreen": [46, 139, 87], - "seashell": [255, 245, 238], - "sienna": [160, 82, 45], - "silver": [192, 192, 192], - "skyblue": [135, 206, 235], - "slateblue": [106, 90, 205], - "slategray": [112, 128, 144], - "slategrey": [112, 128, 144], - "snow": [255, 250, 250], - "springgreen": [0, 255, 127], - "steelblue": [70, 130, 180], - "tan": [210, 180, 140], - "teal": [0, 128, 128], - "thistle": [216, 191, 216], - "tomato": [255, 99, 71], - "turquoise": [64, 224, 208], - "violet": [238, 130, 238], - "wheat": [245, 222, 179], - "white": [255, 255, 255], - "whitesmoke": [245, 245, 245], - "yellow": [255, 255, 0], - "yellowgreen": [154, 205, 50] -}; +__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__(55); +/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(420); +/* harmony import */ var _observable_defer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(91); +/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(66); +/** PURE_IMPORTS_START _scheduler_async,_scan,_observable_defer,_map PURE_IMPORTS_END */ -/***/ }), -/* 393 */ -/***/ (function(module, exports, __webpack_require__) { -var conversions = __webpack_require__(391); -/* - this function routes a model to all other models. +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; +}()); - all functions that are routed have a property `.conversion` attached - to the returned synthetic function. This property is an array - of strings, each with the steps in between the 'from' and 'to' - color models (inclusive). +//# sourceMappingURL=timeInterval.js.map - conversions that are not possible simply are not included. -*/ -function buildGraph() { - var graph = {}; - // https://jsperf.com/object-keys-vs-for-in-with-closure/3 - var models = Object.keys(conversions); +/***/ }), +/* 461 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - for (var len = models.length, i = 0; i < len; i++) { - graph[models[i]] = { - // http://jsperf.com/1-vs-infinity - // micro-opt, but this is simple. - distance: -1, - parent: null - }; - } +"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__(55); +/* harmony import */ var _util_TimeoutError__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(64); +/* harmony import */ var _timeoutWith__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(462); +/* harmony import */ var _observable_throwError__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(49); +/** PURE_IMPORTS_START _scheduler_async,_util_TimeoutError,_timeoutWith,_observable_throwError PURE_IMPORTS_END */ - return graph; -} -// https://en.wikipedia.org/wiki/Breadth-first_search -function deriveBFS(fromModel) { - var graph = buildGraph(); - var queue = [fromModel]; // unshift -> queue -> pop - graph[fromModel].distance = 0; - while (queue.length) { - var current = queue.pop(); - var adjacents = Object.keys(conversions[current]); +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 - for (var len = adjacents.length, i = 0; i < len; i++) { - var adjacent = adjacents[i]; - var node = graph[adjacent]; - if (node.distance === -1) { - node.distance = graph[current].distance + 1; - node.parent = current; - queue.unshift(adjacent); - } - } - } +/***/ }), +/* 462 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - return graph; -} +"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__(12); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(55); +/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(394); +/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(90); +/** PURE_IMPORTS_START tslib,_scheduler_async,_util_isDate,_innerSubscribe PURE_IMPORTS_END */ -function link(from, to) { - return function (args) { - return to(from(args)); - }; -} -function wrapConversion(toModel, graph) { - var path = [graph[toModel].parent, toModel]; - var fn = conversions[graph[toModel].parent][toModel]; - var cur = graph[toModel].parent; - while (graph[cur].parent) { - path.unshift(graph[cur].parent); - fn = link(conversions[graph[cur].parent][cur], fn); - cur = graph[cur].parent; - } - fn.conversion = path; - return fn; +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.scheduleTimeout(); + return _this; + } + TimeoutWithSubscriber.dispatchTimeout = function (subscriber) { + var withObservable = subscriber.withObservable; + subscriber._unsubscribeAndRecycle(); + subscriber.add(Object(_innerSubscribe__WEBPACK_IMPORTED_MODULE_3__["innerSubscribe"])(withObservable, new _innerSubscribe__WEBPACK_IMPORTED_MODULE_3__["SimpleInnerSubscriber"](subscriber))); + }; + 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 = undefined; + this.scheduler = null; + this.withObservable = null; + }; + return TimeoutWithSubscriber; +}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_3__["SimpleOuterSubscriber"])); +//# sourceMappingURL=timeoutWith.js.map -module.exports = function (fromModel) { - var graph = deriveBFS(fromModel); - var conversion = {}; - var models = Object.keys(graph); - for (var len = models.length, i = 0; i < len; i++) { - var toModel = models[i]; - var node = graph[toModel]; +/***/ }), +/* 463 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - if (node.parent === null) { - // no possible conversion, or this node is the source model. - continue; - } +"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__(55); +/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(66); +/** PURE_IMPORTS_START _scheduler_async,_map PURE_IMPORTS_END */ - conversion[toModel] = wrapConversion(toModel, graph); - } - return conversion; -}; +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 /***/ }), -/* 394 */ -/***/ (function(module, exports, __webpack_require__) { +/* 464 */ +/***/ (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__(419); +/** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ -const os = __webpack_require__(121); -const hasFlag = __webpack_require__(272); - -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; +function toArrayReducer(arr, item, index) { + if (index === 0) { + return [item]; + } + arr.push(item); + return arr; } -if ('FORCE_COLOR' in env) { - forceColor = env.FORCE_COLOR.length === 0 || parseInt(env.FORCE_COLOR, 10) !== 0; +function toArray() { + return Object(_reduce__WEBPACK_IMPORTED_MODULE_0__["reduce"])(toArrayReducer, []); } +//# sourceMappingURL=toArray.js.map -function translateLevel(level) { - if (level === 0) { - return false; - } - return { - level, - hasBasic: true, - has256: level >= 2, - has16m: level >= 3 - }; +/***/ }), +/* 465 */ +/***/ (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__(12); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(27); +/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(90); +/** PURE_IMPORTS_START tslib,_Subject,_innerSubscribe 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(_innerSubscribe__WEBPACK_IMPORTED_MODULE_2__["innerSubscribe"])(this.windowBoundaries, new _innerSubscribe__WEBPACK_IMPORTED_MODULE_2__["SimpleInnerSubscriber"](windowSubscriber))); + } + 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 () { + this.openWindow(); + }; + WindowSubscriber.prototype.notifyError = function (error) { + this._error(error); + }; + WindowSubscriber.prototype.notifyComplete = function () { + 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; +}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_2__["SimpleOuterSubscriber"])); +//# sourceMappingURL=window.js.map -function supportsColor(stream) { - if (forceColor === false) { - return 0; - } - if (hasFlag('color=16m') || - hasFlag('color=full') || - hasFlag('color=truecolor')) { - return 3; - } +/***/ }), +/* 466 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - if (hasFlag('color=256')) { - return 2; - } +"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__(12); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(27); +/** PURE_IMPORTS_START tslib,_Subscriber,_Subject PURE_IMPORTS_END */ - if (stream && !stream.isTTY && forceColor !== true) { - 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; - } +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 - 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; - } +/***/ }), +/* 467 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - if ('TERM_PROGRAM' in env) { - const version = parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10); +"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__(12); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(27); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(55); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(11); +/* harmony import */ var _util_isNumeric__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(98); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(45); +/** PURE_IMPORTS_START tslib,_Subject,_scheduler_async,_Subscriber,_util_isNumeric,_util_isScheduler PURE_IMPORTS_END */ - 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; - } - if (env.TERM === 'dumb') { - return min; - } - return min; +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 = Number(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 = Number(arguments[1]); + } + return function windowTimeOperatorFunction(source) { + return source.lift(new WindowTimeOperator(windowTimeSpan, windowCreationInterval, maxWindowSize, scheduler)); + }; } - -function getSupportLevel(stream) { - const level = supportsColor(stream); - return translateLevel(level); +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); } - -module.exports = { - supportsColor: getSupportLevel, - stdout: getSupportLevel(process.stdout), - stderr: getSupportLevel(process.stderr) -}; +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 /***/ }), -/* 395 */ -/***/ (function(module, exports, __webpack_require__) { +/* 468 */ +/***/ (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__(12); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(27); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(17); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(69); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(70); +/** PURE_IMPORTS_START tslib,_Subject,_Subscription,_OuterSubscriber,_util_subscribeToResult 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 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 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 -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]); - } - } +/***/ }), +/* 469 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - return results; -} +"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__(12); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(27); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(69); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(70); +/** PURE_IMPORTS_START tslib,_Subject,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ -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; +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) { + 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 -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'); - } +/***/ }), +/* 470 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - chunks.push(buildStyle(chalk, styles)(chunk.join(''))); - chunk = []; - styles.pop(); - } else { - chunk.push(chr); - } - }); +"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__(12); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(69); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(70); +/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ - 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(''); -}; +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, undefined, i)); + } + return _this; + } + WithLatestFromSubscriber.prototype.notifyNext = function (_outerValue, innerValue, outerIndex) { + 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 /***/ }), -/* 396 */ -/***/ (function(module, exports, __webpack_require__) { +/* 471 */ +/***/ (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__(110); +/** PURE_IMPORTS_START _observable_zip PURE_IMPORTS_END */ -const ansiRegex = __webpack_require__(397); - -module.exports = string => typeof string === 'string' ? string.replace(ansiRegex(), '') : string; +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 /***/ }), -/* 397 */ -/***/ (function(module, exports, __webpack_require__) { +/* 472 */ +/***/ (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__(110); +/** 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 -module.exports = ({onlyFirst = false} = {}) => { - const pattern = [ - '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)', - '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))' - ].join('|'); - return new RegExp(pattern, onlyFirst ? undefined : 'g'); -}; +/***/ }), +/* 473 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const tslib_1 = __webpack_require__(7); +tslib_1.__exportStar(__webpack_require__(474), exports); +tslib_1.__exportStar(__webpack_require__(475), exports); /***/ }), -/* 398 */ +/* 474 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.observeLines = void 0; +const tslib_1 = __webpack_require__(7); +const Rx = tslib_1.__importStar(__webpack_require__(8)); +const operators_1 = __webpack_require__(375); +const SEP = /\r?\n/; +const observe_readable_1 = __webpack_require__(475); +/** + * 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; -var defaults = __webpack_require__(399) -var combining = __webpack_require__(401) -var DEFAULTS = { - nul: 0, - control: 0 -} +/***/ }), +/* 475 */ +/***/ (function(module, exports, __webpack_require__) { -module.exports = function wcwidth(str) { - return wcswidth(str, DEFAULTS) -} +"use strict"; -module.exports.config = function(opts) { - opts = defaults(opts || {}, DEFAULTS) - return function wcwidth(str) { - return wcswidth(str, opts) - } +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.observeReadable = void 0; +const tslib_1 = __webpack_require__(7); +const Rx = tslib_1.__importStar(__webpack_require__(8)); +const operators_1 = __webpack_require__(375); +/** + * 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; + +/***/ }), +/* 476 */ +/***/ (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 dedent__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); +/* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(dedent__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(143); +/* 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__(477); +/* 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__(4); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var _utils_bazel__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(371); +/* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(131); +/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(246); /* - * The following functions define the column width of an ISO 10646 - * character as follows: - * - The null character (U+0000) has a column width of 0. - * - Other C0/C1 control characters and DEL will lead to a return value - * of -1. - * - Non-spacing and enclosing combining characters (general category - * code Mn or Me in the - * Unicode database) have a column width of 0. - * - SOFT HYPHEN (U+00AD) has a column width of 1. - * - Other format characters (general category code Cf in the Unicode - * database) and ZERO WIDTH - * SPACE (U+200B) have a column width of 0. - * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) - * have a column width of 0. - * - Spacing characters in the East Asian Wide (W) or East Asian - * Full-width (F) category as - * defined in Unicode Technical Report #11 have a column width of 2. - * - All remaining characters (including all printable ISO 8859-1 and - * WGL4 characters, Unicode control characters, etc.) have a column - * width of 1. - * This implementation assumes that characters are encoded in ISO 10646. -*/ + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ -function wcswidth(str, opts) { - if (typeof str !== 'string') return wcwidth(str, opts) - var s = 0 - for (var i = 0; i < str.length; i++) { - var n = wcwidth(str.charCodeAt(i), opts) - if (n < 0) return -1 - s += n - } - return s -} -function wcwidth(ucs, opts) { - // test for 8-bit control characters - if (ucs === 0) return opts.nul - if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) return opts.control - // binary search in table of non-spacing characters - if (bisearch(ucs)) return 0 - // if we arrive here, ucs is not a combining or C0/C1 control character - return 1 + - (ucs >= 0x1100 && - (ucs <= 0x115f || // Hangul Jamo init. consonants - ucs == 0x2329 || ucs == 0x232a || - (ucs >= 0x2e80 && ucs <= 0xa4cf && - ucs != 0x303f) || // CJK ... Yi - (ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables - (ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compatibility Ideographs - (ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms - (ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compatibility Forms - (ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms - (ucs >= 0xffe0 && ucs <= 0xffe6) || - (ucs >= 0x20000 && ucs <= 0x2fffd) || - (ucs >= 0x30000 && ucs <= 0x3fffd))); -} -function bisearch(ucs) { - var min = 0 - var max = combining.length - 1 - var mid +const CleanCommand = { + description: 'Deletes output directories, node_modules and resets internal caches.', + name: 'clean', - if (ucs < combining[0][0] || ucs > combining[max][1]) return false + async run(projects) { + _utils_log__WEBPACK_IMPORTED_MODULE_6__["log"].warning(dedent__WEBPACK_IMPORTED_MODULE_0___default.a` + This command is only necessary for the rare circumstance where you need to recover a consistent + state when problems arise. If you need to run this command often, please let us know by + filling out this form: https://ela.st/yarn-kbn-clean + `); + const toDelete = []; - while (max >= min) { - mid = Math.floor((min + max) / 2) - if (ucs > combining[mid][1]) min = mid + 1 - else if (ucs < combining[mid][0]) max = mid - 1 - else return true - } + for (const project of projects.values()) { + if (await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_5__["isDirectory"])(project.nodeModulesLocation)) { + toDelete.push({ + cwd: project.path, + pattern: Object(path__WEBPACK_IMPORTED_MODULE_3__["relative"])(project.path, project.nodeModulesLocation) + }); + } - return false -} + if (await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_5__["isDirectory"])(project.targetLocation)) { + toDelete.push({ + cwd: project.path, + pattern: Object(path__WEBPACK_IMPORTED_MODULE_3__["relative"])(project.path, project.targetLocation) + }); + } + const { + extraPatterns + } = project.getCleanConfig(); -/***/ }), -/* 399 */ -/***/ (function(module, exports, __webpack_require__) { + if (extraPatterns) { + toDelete.push({ + cwd: project.path, + pattern: extraPatterns + }); + } + } // Runs Bazel soft clean -var clone = __webpack_require__(400); -module.exports = function(options, defaults) { - options = options || {}; + await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_4__["runBazel"])(['clean']); + _utils_log__WEBPACK_IMPORTED_MODULE_6__["log"].success('Soft cleaned bazel'); - Object.keys(defaults).forEach(function(key) { - if (typeof options[key] === 'undefined') { - options[key] = clone(defaults[key]); + if (toDelete.length === 0) { + _utils_log__WEBPACK_IMPORTED_MODULE_6__["log"].success('Nothing to delete'); + } else { + /** + * 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 { + pattern, + cwd + } of toDelete) { + process.chdir(cwd); + const promise = del__WEBPACK_IMPORTED_MODULE_1___default()(pattern); + + if (_utils_log__WEBPACK_IMPORTED_MODULE_6__["log"].wouldLogLevel('info')) { + 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); + } } - }); + } - return options; }; /***/ }), -/* 400 */ +/* 477 */ /***/ (function(module, exports, __webpack_require__) { -var clone = (function() { -'use strict'; - -/** - * Clones (copies) an Object using deep copying. - * - * This function supports circular references by default, but if you are certain - * there are no circular references in your object, you can save some CPU time - * by calling clone(obj, false). - * - * Caution: if `circular` is false and `parent` contains circular references, - * your program may enter an infinite loop and crash. - * - * @param `parent` - the object to be cloned - * @param `circular` - set to true if the object to be cloned may contain - * circular references. (optional - true by default) - * @param `depth` - set to a number if the object is only to be cloned to - * a particular depth. (optional - defaults to Infinity) - * @param `prototype` - sets the prototype to be used when cloning an object. - * (optional - defaults to parent prototype). -*/ -function clone(parent, circular, depth, prototype) { - var filter; - if (typeof circular === 'object') { - depth = circular.depth; - prototype = circular.prototype; - filter = circular.filter; - circular = circular.circular - } - // maintain two arrays for circular references, where corresponding parents - // and children have the same index - var allParents = []; - var allChildren = []; +"use strict"; - var useBuffer = typeof Buffer != 'undefined'; +const readline = __webpack_require__(478); +const chalk = __webpack_require__(479); +const cliCursor = __webpack_require__(486); +const cliSpinners = __webpack_require__(488); +const logSymbols = __webpack_require__(490); +const stripAnsi = __webpack_require__(500); +const wcwidth = __webpack_require__(502); +const isInteractive = __webpack_require__(506); +const MuteStream = __webpack_require__(507); - if (typeof circular == 'undefined') - circular = true; +const TEXT = Symbol('text'); +const PREFIX_TEXT = Symbol('prefixText'); - if (typeof depth == 'undefined') - depth = Infinity; +const ASCII_ETX_CODE = 0x03; // Ctrl+C emits this code - // recurse this function so we don't reset allParents and allChildren - function _clone(parent, depth) { - // cloning null always returns null - if (parent === null) - return null; +class StdinDiscarder { + constructor() { + this.requests = 0; - if (depth == 0) - return parent; + this.mutedStream = new MuteStream(); + this.mutedStream.pipe(process.stdout); + this.mutedStream.mute(); - var child; - var proto; - if (typeof parent != 'object') { - return parent; - } - - if (clone.__isArray(parent)) { - child = []; - } else if (clone.__isRegExp(parent)) { - child = new RegExp(parent.source, __getRegExpFlags(parent)); - if (parent.lastIndex) child.lastIndex = parent.lastIndex; - } else if (clone.__isDate(parent)) { - child = new Date(parent.getTime()); - } else if (useBuffer && Buffer.isBuffer(parent)) { - if (Buffer.allocUnsafe) { - // Node.js >= 4.5.0 - child = Buffer.allocUnsafe(parent.length); - } else { - // Older Node.js versions - child = new Buffer(parent.length); - } - parent.copy(child); - return child; - } else { - if (typeof prototype == 'undefined') { - proto = Object.getPrototypeOf(parent); - child = Object.create(proto); - } - else { - child = Object.create(prototype); - proto = prototype; - } - } + const self = this; + this.ourEmit = function (event, data, ...args) { + const {stdin} = process; + if (self.requests > 0 || stdin.emit === self.ourEmit) { + if (event === 'keypress') { // Fixes readline behavior + return; + } - if (circular) { - var index = allParents.indexOf(parent); + if (event === 'data' && data.includes(ASCII_ETX_CODE)) { + process.emit('SIGINT'); + } - if (index != -1) { - return allChildren[index]; - } - allParents.push(parent); - allChildren.push(child); - } + Reflect.apply(self.oldEmit, this, [event, data, ...args]); + } else { + Reflect.apply(process.stdin.emit, this, [event, data, ...args]); + } + }; + } - for (var i in parent) { - var attrs; - if (proto) { - attrs = Object.getOwnPropertyDescriptor(proto, i); - } + start() { + this.requests++; - if (attrs && attrs.set == null) { - continue; - } - child[i] = _clone(parent[i], depth - 1); - } + if (this.requests === 1) { + this.realStart(); + } + } - return child; - } + stop() { + if (this.requests <= 0) { + throw new Error('`stop` called more times than `start`'); + } - return _clone(parent, depth); -} + this.requests--; -/** - * Simple flat clone using prototype, accepts only objects, usefull for property - * override on FLAT configuration object (no nested props). - * - * USE WITH CAUTION! This may not behave as you wish if you do not know how this - * works. - */ -clone.clonePrototype = function clonePrototype(parent) { - if (parent === null) - return null; + if (this.requests === 0) { + this.realStop(); + } + } - var c = function () {}; - c.prototype = parent; - return new c(); -}; + realStart() { + // No known way to make it work reliably on Windows + if (process.platform === 'win32') { + return; + } -// private utility functions + this.rl = readline.createInterface({ + input: process.stdin, + output: this.mutedStream + }); -function __objToStr(o) { - return Object.prototype.toString.call(o); -}; -clone.__objToStr = __objToStr; + this.rl.on('SIGINT', () => { + if (process.listenerCount('SIGINT') === 0) { + process.emit('SIGINT'); + } else { + this.rl.close(); + process.kill(process.pid, 'SIGINT'); + } + }); + } -function __isDate(o) { - return typeof o === 'object' && __objToStr(o) === '[object Date]'; -}; -clone.__isDate = __isDate; + realStop() { + if (process.platform === 'win32') { + return; + } -function __isArray(o) { - return typeof o === 'object' && __objToStr(o) === '[object Array]'; -}; -clone.__isArray = __isArray; + this.rl.close(); + this.rl = undefined; + } +} -function __isRegExp(o) { - return typeof o === 'object' && __objToStr(o) === '[object RegExp]'; -}; -clone.__isRegExp = __isRegExp; +let stdinDiscarder; -function __getRegExpFlags(re) { - var flags = ''; - if (re.global) flags += 'g'; - if (re.ignoreCase) flags += 'i'; - if (re.multiline) flags += 'm'; - return flags; -}; -clone.__getRegExpFlags = __getRegExpFlags; +class Ora { + constructor(options) { + if (!stdinDiscarder) { + stdinDiscarder = new StdinDiscarder(); + } -return clone; -})(); + if (typeof options === 'string') { + options = { + text: options + }; + } -if ( true && module.exports) { - module.exports = clone; -} + this.options = { + text: '', + color: 'cyan', + stream: process.stderr, + discardStdin: true, + ...options + }; + this.spinner = this.options.spinner; -/***/ }), -/* 401 */ -/***/ (function(module, exports) { + this.color = this.options.color; + this.hideCursor = this.options.hideCursor !== false; + this.interval = this.options.interval || this.spinner.interval || 100; + this.stream = this.options.stream; + this.id = undefined; + this.isEnabled = typeof this.options.isEnabled === 'boolean' ? this.options.isEnabled : isInteractive({stream: this.stream}); -module.exports = [ - [ 0x0300, 0x036F ], [ 0x0483, 0x0486 ], [ 0x0488, 0x0489 ], - [ 0x0591, 0x05BD ], [ 0x05BF, 0x05BF ], [ 0x05C1, 0x05C2 ], - [ 0x05C4, 0x05C5 ], [ 0x05C7, 0x05C7 ], [ 0x0600, 0x0603 ], - [ 0x0610, 0x0615 ], [ 0x064B, 0x065E ], [ 0x0670, 0x0670 ], - [ 0x06D6, 0x06E4 ], [ 0x06E7, 0x06E8 ], [ 0x06EA, 0x06ED ], - [ 0x070F, 0x070F ], [ 0x0711, 0x0711 ], [ 0x0730, 0x074A ], - [ 0x07A6, 0x07B0 ], [ 0x07EB, 0x07F3 ], [ 0x0901, 0x0902 ], - [ 0x093C, 0x093C ], [ 0x0941, 0x0948 ], [ 0x094D, 0x094D ], - [ 0x0951, 0x0954 ], [ 0x0962, 0x0963 ], [ 0x0981, 0x0981 ], - [ 0x09BC, 0x09BC ], [ 0x09C1, 0x09C4 ], [ 0x09CD, 0x09CD ], - [ 0x09E2, 0x09E3 ], [ 0x0A01, 0x0A02 ], [ 0x0A3C, 0x0A3C ], - [ 0x0A41, 0x0A42 ], [ 0x0A47, 0x0A48 ], [ 0x0A4B, 0x0A4D ], - [ 0x0A70, 0x0A71 ], [ 0x0A81, 0x0A82 ], [ 0x0ABC, 0x0ABC ], - [ 0x0AC1, 0x0AC5 ], [ 0x0AC7, 0x0AC8 ], [ 0x0ACD, 0x0ACD ], - [ 0x0AE2, 0x0AE3 ], [ 0x0B01, 0x0B01 ], [ 0x0B3C, 0x0B3C ], - [ 0x0B3F, 0x0B3F ], [ 0x0B41, 0x0B43 ], [ 0x0B4D, 0x0B4D ], - [ 0x0B56, 0x0B56 ], [ 0x0B82, 0x0B82 ], [ 0x0BC0, 0x0BC0 ], - [ 0x0BCD, 0x0BCD ], [ 0x0C3E, 0x0C40 ], [ 0x0C46, 0x0C48 ], - [ 0x0C4A, 0x0C4D ], [ 0x0C55, 0x0C56 ], [ 0x0CBC, 0x0CBC ], - [ 0x0CBF, 0x0CBF ], [ 0x0CC6, 0x0CC6 ], [ 0x0CCC, 0x0CCD ], - [ 0x0CE2, 0x0CE3 ], [ 0x0D41, 0x0D43 ], [ 0x0D4D, 0x0D4D ], - [ 0x0DCA, 0x0DCA ], [ 0x0DD2, 0x0DD4 ], [ 0x0DD6, 0x0DD6 ], - [ 0x0E31, 0x0E31 ], [ 0x0E34, 0x0E3A ], [ 0x0E47, 0x0E4E ], - [ 0x0EB1, 0x0EB1 ], [ 0x0EB4, 0x0EB9 ], [ 0x0EBB, 0x0EBC ], - [ 0x0EC8, 0x0ECD ], [ 0x0F18, 0x0F19 ], [ 0x0F35, 0x0F35 ], - [ 0x0F37, 0x0F37 ], [ 0x0F39, 0x0F39 ], [ 0x0F71, 0x0F7E ], - [ 0x0F80, 0x0F84 ], [ 0x0F86, 0x0F87 ], [ 0x0F90, 0x0F97 ], - [ 0x0F99, 0x0FBC ], [ 0x0FC6, 0x0FC6 ], [ 0x102D, 0x1030 ], - [ 0x1032, 0x1032 ], [ 0x1036, 0x1037 ], [ 0x1039, 0x1039 ], - [ 0x1058, 0x1059 ], [ 0x1160, 0x11FF ], [ 0x135F, 0x135F ], - [ 0x1712, 0x1714 ], [ 0x1732, 0x1734 ], [ 0x1752, 0x1753 ], - [ 0x1772, 0x1773 ], [ 0x17B4, 0x17B5 ], [ 0x17B7, 0x17BD ], - [ 0x17C6, 0x17C6 ], [ 0x17C9, 0x17D3 ], [ 0x17DD, 0x17DD ], - [ 0x180B, 0x180D ], [ 0x18A9, 0x18A9 ], [ 0x1920, 0x1922 ], - [ 0x1927, 0x1928 ], [ 0x1932, 0x1932 ], [ 0x1939, 0x193B ], - [ 0x1A17, 0x1A18 ], [ 0x1B00, 0x1B03 ], [ 0x1B34, 0x1B34 ], - [ 0x1B36, 0x1B3A ], [ 0x1B3C, 0x1B3C ], [ 0x1B42, 0x1B42 ], - [ 0x1B6B, 0x1B73 ], [ 0x1DC0, 0x1DCA ], [ 0x1DFE, 0x1DFF ], - [ 0x200B, 0x200F ], [ 0x202A, 0x202E ], [ 0x2060, 0x2063 ], - [ 0x206A, 0x206F ], [ 0x20D0, 0x20EF ], [ 0x302A, 0x302F ], - [ 0x3099, 0x309A ], [ 0xA806, 0xA806 ], [ 0xA80B, 0xA80B ], - [ 0xA825, 0xA826 ], [ 0xFB1E, 0xFB1E ], [ 0xFE00, 0xFE0F ], - [ 0xFE20, 0xFE23 ], [ 0xFEFF, 0xFEFF ], [ 0xFFF9, 0xFFFB ], - [ 0x10A01, 0x10A03 ], [ 0x10A05, 0x10A06 ], [ 0x10A0C, 0x10A0F ], - [ 0x10A38, 0x10A3A ], [ 0x10A3F, 0x10A3F ], [ 0x1D167, 0x1D169 ], - [ 0x1D173, 0x1D182 ], [ 0x1D185, 0x1D18B ], [ 0x1D1AA, 0x1D1AD ], - [ 0x1D242, 0x1D244 ], [ 0xE0001, 0xE0001 ], [ 0xE0020, 0xE007F ], - [ 0xE0100, 0xE01EF ] -] + // Set *after* `this.stream` + this.text = this.options.text; + this.prefixText = this.options.prefixText; + this.linesToClear = 0; + this.indent = this.options.indent; + this.discardStdin = this.options.discardStdin; + this.isDiscardingStdin = false; + } + get indent() { + return this._indent; + } -/***/ }), -/* 402 */ -/***/ (function(module, exports, __webpack_require__) { + set indent(indent = 0) { + if (!(indent >= 0 && Number.isInteger(indent))) { + throw new Error('The `indent` option must be an integer from 0 and up'); + } -"use strict"; + this._indent = indent; + } + _updateInterval(interval) { + if (interval !== undefined) { + this.interval = interval; + } + } -module.exports = ({stream = process.stdout} = {}) => { - return Boolean( - stream && stream.isTTY && - process.env.TERM !== 'dumb' && - !('CI' in process.env) - ); -}; + get spinner() { + return this._spinner; + } + set spinner(spinner) { + this.frameIndex = 0; -/***/ }), -/* 403 */ -/***/ (function(module, exports, __webpack_require__) { + if (typeof spinner === 'object') { + if (spinner.frames === undefined) { + throw new Error('The given spinner must have a `frames` property'); + } -var Stream = __webpack_require__(138) + this._spinner = spinner; + } else if (process.platform === 'win32') { + this._spinner = cliSpinners.line; + } else if (spinner === undefined) { + // Set default spinner + this._spinner = cliSpinners.dots; + } else if (cliSpinners[spinner]) { + this._spinner = cliSpinners[spinner]; + } else { + throw new Error(`There is no built-in spinner named '${spinner}'. See https://github.com/sindresorhus/cli-spinners/blob/master/spinners.json for a full list.`); + } -module.exports = MuteStream + this._updateInterval(this._spinner.interval); + } -// var out = new MuteStream(process.stdout) -// argument auto-pipes -function MuteStream (opts) { - Stream.apply(this) - opts = opts || {} - this.writable = this.readable = true - this.muted = false - this.on('pipe', this._onpipe) - this.replace = opts.replace + get text() { + return this[TEXT]; + } - // For readline-type situations - // This much at the start of a line being redrawn after a ctrl char - // is seen (such as backspace) won't be redrawn as the replacement - this._prompt = opts.prompt || null - this._hadControl = false -} + get prefixText() { + return this[PREFIX_TEXT]; + } -MuteStream.prototype = Object.create(Stream.prototype) + get isSpinning() { + return this.id !== undefined; + } -Object.defineProperty(MuteStream.prototype, 'constructor', { - value: MuteStream, - enumerable: false -}) + updateLineCount() { + const columns = this.stream.columns || 80; + const fullPrefixText = (typeof this[PREFIX_TEXT] === 'string') ? this[PREFIX_TEXT] + '-' : ''; + this.lineCount = stripAnsi(fullPrefixText + '--' + this[TEXT]).split('\n').reduce((count, line) => { + return count + Math.max(1, Math.ceil(wcwidth(line) / columns)); + }, 0); + } -MuteStream.prototype.mute = function () { - this.muted = true -} + set text(value) { + this[TEXT] = value; + this.updateLineCount(); + } -MuteStream.prototype.unmute = function () { - this.muted = false -} + set prefixText(value) { + this[PREFIX_TEXT] = value; + this.updateLineCount(); + } -Object.defineProperty(MuteStream.prototype, '_onpipe', { - value: onPipe, - enumerable: false, - writable: true, - configurable: true -}) + frame() { + const {frames} = this.spinner; + let frame = frames[this.frameIndex]; -function onPipe (src) { - this._src = src -} + if (this.color) { + frame = chalk[this.color](frame); + } -Object.defineProperty(MuteStream.prototype, 'isTTY', { - get: getIsTTY, - set: setIsTTY, - enumerable: true, - configurable: true -}) + this.frameIndex = ++this.frameIndex % frames.length; + const fullPrefixText = (typeof this.prefixText === 'string' && this.prefixText !== '') ? this.prefixText + ' ' : ''; + const fullText = typeof this.text === 'string' ? ' ' + this.text : ''; -function getIsTTY () { - return( (this._dest) ? this._dest.isTTY - : (this._src) ? this._src.isTTY - : false - ) -} + return fullPrefixText + frame + fullText; + } -// basically just get replace the getter/setter with a regular value -function setIsTTY (isTTY) { - Object.defineProperty(this, 'isTTY', { - value: isTTY, - enumerable: true, - writable: true, - configurable: true - }) -} + clear() { + if (!this.isEnabled || !this.stream.isTTY) { + return this; + } -Object.defineProperty(MuteStream.prototype, 'rows', { - get: function () { - return( this._dest ? this._dest.rows - : this._src ? this._src.rows - : undefined ) - }, enumerable: true, configurable: true }) + for (let i = 0; i < this.linesToClear; i++) { + if (i > 0) { + this.stream.moveCursor(0, -1); + } -Object.defineProperty(MuteStream.prototype, 'columns', { - get: function () { - return( this._dest ? this._dest.columns - : this._src ? this._src.columns - : undefined ) - }, enumerable: true, configurable: true }) + this.stream.clearLine(); + this.stream.cursorTo(this.indent); + } + this.linesToClear = 0; -MuteStream.prototype.pipe = function (dest, options) { - this._dest = dest - return Stream.prototype.pipe.call(this, dest, options) -} + return this; + } -MuteStream.prototype.pause = function () { - if (this._src) return this._src.pause() -} + render() { + this.clear(); + this.stream.write(this.frame()); + this.linesToClear = this.lineCount; -MuteStream.prototype.resume = function () { - if (this._src) return this._src.resume() -} + return this; + } -MuteStream.prototype.write = function (c) { - if (this.muted) { - if (!this.replace) return true - if (c.match(/^\u001b/)) { - if(c.indexOf(this._prompt) === 0) { - c = c.substr(this._prompt.length); - c = c.replace(/./g, this.replace); - c = this._prompt + c; - } - this._hadControl = true - return this.emit('data', c) - } else { - if (this._prompt && this._hadControl && - c.indexOf(this._prompt) === 0) { - this._hadControl = false - this.emit('data', this._prompt) - c = c.substr(this._prompt.length) - } - c = c.toString().replace(/./g, this.replace) - } - } - this.emit('data', c) -} + start(text) { + if (text) { + this.text = text; + } -MuteStream.prototype.end = function (c) { - if (this.muted) { - if (c && this.replace) { - c = c.toString().replace(/./g, this.replace) - } else { - c = null - } - } - if (c) this.emit('data', c) - this.emit('end') -} + if (!this.isEnabled) { + if (this.text) { + this.stream.write(`- ${this.text}\n`); + } -function proxy (fn) { return function () { - var d = this._dest - var s = this._src - if (d && d[fn]) d[fn].apply(d, arguments) - if (s && s[fn]) s[fn].apply(s, arguments) -}} + return this; + } -MuteStream.prototype.destroy = proxy('destroy') -MuteStream.prototype.destroySoon = proxy('destroySoon') -MuteStream.prototype.close = proxy('close') + if (this.isSpinning) { + return this; + } + if (this.hideCursor) { + cliCursor.hide(this.stream); + } -/***/ }), -/* 404 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (this.discardStdin && process.stdin.isTTY) { + this.isDiscardingStdin = true; + stdinDiscarder.start(); + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "RunCommand", function() { return RunCommand; }); -/* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(249); -/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(246); -/* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(247); -/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(248); -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ + this.render(); + this.id = setInterval(this.render.bind(this), this.interval); + return this; + } + stop() { + if (!this.isEnabled) { + return this; + } + clearInterval(this.id); + this.id = undefined; + this.frameIndex = 0; + this.clear(); + if (this.hideCursor) { + cliCursor.show(this.stream); + } -const RunCommand = { - description: 'Run script defined in package.json in each package that contains that script.', - name: 'run', - - async run(projects, projectGraph, { - extraArgs, - options - }) { - const batchedProjects = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_3__["topologicallyBatchProjects"])(projects, projectGraph); - - if (extraArgs.length === 0) { - throw new _utils_errors__WEBPACK_IMPORTED_MODULE_0__["CliError"]('No script specified'); - } - - const scriptName = extraArgs[0]; - const scriptArgs = extraArgs.slice(1); - await Object(_utils_parallelize__WEBPACK_IMPORTED_MODULE_2__["parallelizeBatches"])(batchedProjects, async project => { - if (!project.hasScript(scriptName)) { - if (!!options['skip-missing']) { - return; - } - - throw new _utils_errors__WEBPACK_IMPORTED_MODULE_0__["CliError"](`[${project.name}] no "${scriptName}" script defined. To skip packages without the "${scriptName}" script pass --skip-missing`); - } - - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].info(`[${project.name}] running "${scriptName}" script`); - await project.runScriptStreaming(scriptName, { - args: scriptArgs - }); - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].success(`[${project.name}] complete`); - }); - } - -}; - -/***/ }), -/* 405 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (this.discardStdin && process.stdin.isTTY && this.isDiscardingStdin) { + stdinDiscarder.stop(); + this.isDiscardingStdin = false; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "WatchCommand", function() { return WatchCommand; }); -/* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(249); -/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(246); -/* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(247); -/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(248); -/* harmony import */ var _utils_watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(406); -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ + 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 = {}) { + const prefixText = options.prefixText || this.prefixText; + const fullPrefixText = (typeof prefixText === 'string' && prefixText !== '') ? prefixText + ' ' : ''; + const text = options.text || this.text; + const fullText = (typeof text === 'string') ? ' ' + text : ''; -/** - * 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.stop(); + this.stream.write(`${fullPrefixText}${options.symbol || ' '}${fullText}\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. - */ + return this; + } +} -const WatchCommand = { - description: 'Runs `kbn:watch` script for every project.', - name: 'watch', +const oraFactory = function (options) { + return new Ora(options); +}; - async run(projects, projectGraph) { - const projectsToWatch = new Map(); +module.exports = oraFactory; - 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); - } - } +module.exports.promise = (action, options) => { + // eslint-disable-next-line promise/prefer-await-to-then + if (typeof action.then !== 'function') { + throw new TypeError('Parameter `action` must be a Promise'); + } - if (projectsToWatch.size === 0) { - throw new _utils_errors__WEBPACK_IMPORTED_MODULE_0__["CliError"](`There are no projects to watch found. Make sure that projects define 'kbn:watch' script in 'package.json'.`); - } + const spinner = new Ora(options); + spinner.start(); - const projectNames = Array.from(projectsToWatch.keys()); - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].info(`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. + (async () => { + try { + await action; + spinner.succeed(); + } catch (_) { + spinner.fail(); + } + })(); - const shouldWatchKibanaProject = projectsToWatch.delete(kibanaProjectName); - const batchedProjects = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_3__["topologicallyBatchProjects"])(projectsToWatch, projectGraph); + return spinner; +}; - 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, { - debug: false - }).stdout // TypeScript note: As long as the proc stdio[1] is 'pipe', then stdout will not be null - ); - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].success(`[${pkg.name}] Initial build completed (${completionHint}).`); - }); - } +/***/ }), +/* 478 */ +/***/ (function(module, exports) { -}; +module.exports = require("readline"); /***/ }), -/* 406 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 479 */ +/***/ (function(module, 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__(8); -/* harmony import */ var rxjs_operators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(407); -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -/** - * 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]; -} +const ansiStyles = __webpack_require__(480); +const {stdout: stdoutColor, stderr: stderrColor} = __webpack_require__(120); +const { + stringReplaceAll, + stringEncaseCRLFWithFirstIndex +} = __webpack_require__(484); -function waitUntilWatchIsReady(stream, opts = {}) { - const buildOutput$ = new rxjs__WEBPACK_IMPORTED_MODULE_0__["Subject"](); +// `supportsColor.level` → `ansiStyles.color[name]` mapping +const levelMapping = [ + 'ansi', + 'ansi', + 'ansi256', + 'ansi16m' +]; - const onDataListener = data => buildOutput$.next(data.toString('utf-8')); +const styles = Object.create(null); - const onEndListener = () => buildOutput$.complete(); +const applyOptions = (object, options = {}) => { + if (options.level > 3 || options.level < 0) { + throw new Error('The `level` option should be an integer from 0 to 3'); + } - const onErrorListener = e => buildOutput$.error(e); + // Detect level if not set manually + const colorLevel = stdoutColor ? stdoutColor.level : 0; + object.level = options.level === undefined ? colorLevel : options.level; +}; - 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(); +class ChalkClass { + constructor(options) { + return chalkFactory(options); + } } -/***/ }), -/* 407 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +const chalkFactory = options => { + const chalk = {}; + applyOptions(chalk, options); -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony import */ var _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(408); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "audit", function() { return _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__["audit"]; }); + chalk.template = (...arguments_) => chalkTag(chalk.template, ...arguments_); -/* harmony import */ var _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(409); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "auditTime", function() { return _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__["auditTime"]; }); + Object.setPrototypeOf(chalk, Chalk.prototype); + Object.setPrototypeOf(chalk.template, chalk); -/* harmony import */ var _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(410); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buffer", function() { return _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__["buffer"]; }); + chalk.template.constructor = () => { + throw new Error('`chalk.constructor()` is deprecated. Use `new chalk.Instance()` instead.'); + }; -/* harmony import */ var _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(411); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferCount", function() { return _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__["bufferCount"]; }); + chalk.template.Instance = ChalkClass; -/* harmony import */ var _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(412); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferTime", function() { return _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__["bufferTime"]; }); + return chalk.template; +}; -/* harmony import */ var _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(413); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferToggle", function() { return _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__["bufferToggle"]; }); +function Chalk(options) { + return chalkFactory(options); +} -/* harmony import */ var _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(414); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferWhen", function() { return _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__["bufferWhen"]; }); +for (const [styleName, style] of Object.entries(ansiStyles)) { + styles[styleName] = { + get() { + const builder = createBuilder(this, createStyler(style.open, style.close, this._styler), this._isEmpty); + Object.defineProperty(this, styleName, {value: builder}); + return builder; + } + }; +} -/* harmony import */ var _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(415); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "catchError", function() { return _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__["catchError"]; }); +styles.visible = { + get() { + const builder = createBuilder(this, this._styler, true); + Object.defineProperty(this, 'visible', {value: builder}); + return builder; + } +}; -/* harmony import */ var _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(416); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "combineAll", function() { return _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__["combineAll"]; }); +const usedModels = ['rgb', 'hex', 'keyword', 'hsl', 'hsv', 'hwb', 'ansi', 'ansi256']; -/* harmony import */ var _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(417); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "combineLatest", function() { return _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__["combineLatest"]; }); +for (const model of usedModels) { + styles[model] = { + get() { + const {level} = this; + return function (...arguments_) { + const styler = createStyler(ansiStyles.color[levelMapping[level]][model](...arguments_), ansiStyles.color.close, this._styler); + return createBuilder(this, styler, this._isEmpty); + }; + } + }; +} -/* harmony import */ var _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(418); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concat", function() { return _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__["concat"]; }); +for (const model of usedModels) { + const bgModel = 'bg' + model[0].toUpperCase() + model.slice(1); + styles[bgModel] = { + get() { + const {level} = this; + return function (...arguments_) { + const styler = createStyler(ansiStyles.bgColor[levelMapping[level]][model](...arguments_), ansiStyles.bgColor.close, this._styler); + return createBuilder(this, styler, this._isEmpty); + }; + } + }; +} -/* harmony import */ var _internal_operators_concatAll__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(80); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatAll", function() { return _internal_operators_concatAll__WEBPACK_IMPORTED_MODULE_11__["concatAll"]; }); +const proto = Object.defineProperties(() => {}, { + ...styles, + level: { + enumerable: true, + get() { + return this._generator.level; + }, + set(level) { + this._generator.level = level; + } + } +}); -/* harmony import */ var _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(419); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatMap", function() { return _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__["concatMap"]; }); +const createStyler = (open, close, parent) => { + let openAll; + let closeAll; + if (parent === undefined) { + openAll = open; + closeAll = close; + } else { + openAll = parent.openAll + open; + closeAll = close + parent.closeAll; + } -/* harmony import */ var _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(420); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatMapTo", function() { return _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__["concatMapTo"]; }); + return { + open, + close, + openAll, + closeAll, + parent + }; +}; -/* harmony import */ var _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(421); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "count", function() { return _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__["count"]; }); +const createBuilder = (self, _styler, _isEmpty) => { + const builder = (...arguments_) => { + // Single argument is hot path, implicit coercion is faster than anything + // eslint-disable-next-line no-implicit-coercion + return applyStyle(builder, (arguments_.length === 1) ? ('' + arguments_[0]) : arguments_.join(' ')); + }; -/* harmony import */ var _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(422); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "debounce", function() { return _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__["debounce"]; }); + // `__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 -/* harmony import */ var _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(423); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "debounceTime", function() { return _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__["debounceTime"]; }); + builder._generator = self; + builder._styler = _styler; + builder._isEmpty = _isEmpty; -/* harmony import */ var _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(424); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "defaultIfEmpty", function() { return _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__["defaultIfEmpty"]; }); + return builder; +}; -/* harmony import */ var _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(425); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "delay", function() { return _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__["delay"]; }); +const applyStyle = (self, string) => { + if (self.level <= 0 || !string) { + return self._isEmpty ? '' : string; + } -/* harmony import */ var _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(427); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "delayWhen", function() { return _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__["delayWhen"]; }); + let styler = self._styler; -/* harmony import */ var _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(428); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "dematerialize", function() { return _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__["dematerialize"]; }); + if (styler === undefined) { + return string; + } -/* harmony import */ var _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(429); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinct", function() { return _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__["distinct"]; }); + const {openAll, closeAll} = styler; + if (string.indexOf('\u001B') !== -1) { + while (styler !== undefined) { + // 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'. + string = stringReplaceAll(string, styler.close, styler.open); -/* harmony import */ var _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(430); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinctUntilChanged", function() { return _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__["distinctUntilChanged"]; }); + styler = styler.parent; + } + } -/* harmony import */ var _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(431); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinctUntilKeyChanged", function() { return _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__["distinctUntilKeyChanged"]; }); + // We can move both next actions out of loop, because remaining actions in loop won't have + // any/visible effect on parts we add here. 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 + const lfIndex = string.indexOf('\n'); + if (lfIndex !== -1) { + string = stringEncaseCRLFWithFirstIndex(string, closeAll, openAll, lfIndex); + } -/* harmony import */ var _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(432); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "elementAt", function() { return _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__["elementAt"]; }); + return openAll + string + closeAll; +}; -/* harmony import */ var _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(435); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "endWith", function() { return _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__["endWith"]; }); +let template; +const chalkTag = (chalk, ...strings) => { + const [firstString] = strings; -/* harmony import */ var _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(436); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "every", function() { return _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__["every"]; }); + if (!Array.isArray(firstString)) { + // If chalk() was called by itself or with a string, + // return the string itself as a string. + return strings.join(' '); + } -/* harmony import */ var _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(437); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "exhaust", function() { return _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__["exhaust"]; }); + const arguments_ = strings.slice(1); + const parts = [firstString.raw[0]]; -/* harmony import */ var _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(438); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "exhaustMap", function() { return _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__["exhaustMap"]; }); + for (let i = 1; i < firstString.length; i++) { + parts.push( + String(arguments_[i - 1]).replace(/[{}\\]/g, '\\$&'), + String(firstString.raw[i]) + ); + } -/* harmony import */ var _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(439); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "expand", function() { return _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__["expand"]; }); + if (template === undefined) { + template = __webpack_require__(485); + } -/* harmony import */ var _internal_operators_filter__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(105); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "filter", function() { return _internal_operators_filter__WEBPACK_IMPORTED_MODULE_30__["filter"]; }); + return template(chalk, parts.join('')); +}; -/* harmony import */ var _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(440); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "finalize", function() { return _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__["finalize"]; }); +Object.defineProperties(Chalk.prototype, styles); -/* harmony import */ var _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(441); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "find", function() { return _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__["find"]; }); +const chalk = Chalk(); // eslint-disable-line new-cap +chalk.supportsColor = stdoutColor; +chalk.stderr = Chalk({level: stderrColor ? stderrColor.level : 0}); // eslint-disable-line new-cap +chalk.stderr.supportsColor = stderrColor; -/* harmony import */ var _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(442); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "findIndex", function() { return _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__["findIndex"]; }); +// For TypeScript +chalk.Level = { + None: 0, + Basic: 1, + Ansi256: 2, + TrueColor: 3, + 0: 'None', + 1: 'Basic', + 2: 'Ansi256', + 3: 'TrueColor' +}; -/* harmony import */ var _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(443); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "first", function() { return _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__["first"]; }); +module.exports = chalk; -/* harmony import */ var _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_35__ = __webpack_require__(31); -/* 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__(444); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ignoreElements", function() { return _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__["ignoreElements"]; }); +/***/ }), +/* 480 */ +/***/ (function(module, exports, __webpack_require__) { -/* harmony import */ var _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(445); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "isEmpty", function() { return _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__["isEmpty"]; }); +"use strict"; +/* WEBPACK VAR INJECTION */(function(module) { -/* harmony import */ var _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(446); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "last", function() { return _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__["last"]; }); +const wrapAnsi16 = (fn, offset) => (...args) => { + const code = fn(...args); + return `\u001B[${code + offset}m`; +}; -/* harmony import */ var _internal_operators_map__WEBPACK_IMPORTED_MODULE_39__ = __webpack_require__(66); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "map", function() { return _internal_operators_map__WEBPACK_IMPORTED_MODULE_39__["map"]; }); +const wrapAnsi256 = (fn, offset) => (...args) => { + const code = fn(...args); + return `\u001B[${38 + offset};5;${code}m`; +}; -/* harmony import */ var _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(448); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mapTo", function() { return _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__["mapTo"]; }); +const wrapAnsi16m = (fn, offset) => (...args) => { + const rgb = fn(...args); + return `\u001B[${38 + offset};2;${rgb[0]};${rgb[1]};${rgb[2]}m`; +}; -/* harmony import */ var _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(449); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "materialize", function() { return _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__["materialize"]; }); +const ansi2ansi = n => n; +const rgb2rgb = (r, g, b) => [r, g, b]; -/* harmony import */ var _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(450); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "max", function() { return _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__["max"]; }); +const setLazyProperty = (object, property, get) => { + Object.defineProperty(object, property, { + get: () => { + const value = get(); -/* harmony import */ var _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(453); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "merge", function() { return _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__["merge"]; }); + Object.defineProperty(object, property, { + value, + enumerable: true, + configurable: true + }); -/* harmony import */ var _internal_operators_mergeAll__WEBPACK_IMPORTED_MODULE_44__ = __webpack_require__(81); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeAll", function() { return _internal_operators_mergeAll__WEBPACK_IMPORTED_MODULE_44__["mergeAll"]; }); + return value; + }, + enumerable: true, + configurable: true + }); +}; -/* harmony import */ var _internal_operators_mergeMap__WEBPACK_IMPORTED_MODULE_45__ = __webpack_require__(82); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeMap", function() { return _internal_operators_mergeMap__WEBPACK_IMPORTED_MODULE_45__["mergeMap"]; }); +/** @type {typeof import('color-convert')} */ +let colorConvert; +const makeDynamicStyles = (wrap, targetSpace, identity, isBackground) => { + if (colorConvert === undefined) { + colorConvert = __webpack_require__(481); + } -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "flatMap", function() { return _internal_operators_mergeMap__WEBPACK_IMPORTED_MODULE_45__["flatMap"]; }); + const offset = isBackground ? 10 : 0; + const styles = {}; -/* harmony import */ var _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(454); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeMapTo", function() { return _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__["mergeMapTo"]; }); + for (const [sourceSpace, suite] of Object.entries(colorConvert)) { + const name = sourceSpace === 'ansi16' ? 'ansi' : sourceSpace; + if (sourceSpace === targetSpace) { + styles[name] = wrap(identity, offset); + } else if (typeof suite === 'object') { + styles[name] = wrap(suite[targetSpace], offset); + } + } -/* harmony import */ var _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(455); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeScan", function() { return _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__["mergeScan"]; }); + return styles; +}; -/* harmony import */ var _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(456); -/* 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__(457); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "multicast", function() { return _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__["multicast"]; }); +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], -/* harmony import */ var _internal_operators_observeOn__WEBPACK_IMPORTED_MODULE_50__ = __webpack_require__(41); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "observeOn", function() { return _internal_operators_observeOn__WEBPACK_IMPORTED_MODULE_50__["observeOn"]; }); + // Bright color + blackBright: [90, 39], + 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], -/* harmony import */ var _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__ = __webpack_require__(458); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "onErrorResumeNext", function() { return _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__["onErrorResumeNext"]; }); + // 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] + } + }; -/* harmony import */ var _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__ = __webpack_require__(459); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pairwise", function() { return _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__["pairwise"]; }); + // Alias bright black as gray (and grey) + styles.color.gray = styles.color.blackBright; + styles.bgColor.bgGray = styles.bgColor.bgBlackBright; + styles.color.grey = styles.color.blackBright; + styles.bgColor.bgGrey = styles.bgColor.bgBlackBright; -/* harmony import */ var _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__ = __webpack_require__(460); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "partition", function() { return _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__["partition"]; }); + for (const [groupName, group] of Object.entries(styles)) { + for (const [styleName, style] of Object.entries(group)) { + styles[styleName] = { + open: `\u001B[${style[0]}m`, + close: `\u001B[${style[1]}m` + }; -/* harmony import */ var _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__ = __webpack_require__(461); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pluck", function() { return _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__["pluck"]; }); + group[styleName] = styles[styleName]; -/* harmony import */ var _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__ = __webpack_require__(462); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publish", function() { return _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__["publish"]; }); + codes.set(style[0], style[1]); + } -/* harmony import */ var _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__ = __webpack_require__(463); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishBehavior", function() { return _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__["publishBehavior"]; }); + Object.defineProperty(styles, groupName, { + value: group, + enumerable: false + }); + } -/* harmony import */ var _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__ = __webpack_require__(464); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishLast", function() { return _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__["publishLast"]; }); + Object.defineProperty(styles, 'codes', { + value: codes, + enumerable: false + }); -/* harmony import */ var _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__ = __webpack_require__(465); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishReplay", function() { return _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__["publishReplay"]; }); + styles.color.close = '\u001B[39m'; + styles.bgColor.close = '\u001B[49m'; -/* harmony import */ var _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__ = __webpack_require__(466); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "race", function() { return _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__["race"]; }); + setLazyProperty(styles.color, 'ansi', () => makeDynamicStyles(wrapAnsi16, 'ansi16', ansi2ansi, false)); + setLazyProperty(styles.color, 'ansi256', () => makeDynamicStyles(wrapAnsi256, 'ansi256', ansi2ansi, false)); + setLazyProperty(styles.color, 'ansi16m', () => makeDynamicStyles(wrapAnsi16m, 'rgb', rgb2rgb, false)); + setLazyProperty(styles.bgColor, 'ansi', () => makeDynamicStyles(wrapAnsi16, 'ansi16', ansi2ansi, true)); + setLazyProperty(styles.bgColor, 'ansi256', () => makeDynamicStyles(wrapAnsi256, 'ansi256', ansi2ansi, true)); + setLazyProperty(styles.bgColor, 'ansi16m', () => makeDynamicStyles(wrapAnsi16m, 'rgb', rgb2rgb, true)); -/* harmony import */ var _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__ = __webpack_require__(451); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "reduce", function() { return _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__["reduce"]; }); + return styles; +} -/* harmony import */ var _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__ = __webpack_require__(467); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "repeat", function() { return _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__["repeat"]; }); +// Make the export immutable +Object.defineProperty(module, 'exports', { + enumerable: true, + get: assembleStyles +}); -/* harmony import */ var _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__ = __webpack_require__(468); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "repeatWhen", function() { return _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__["repeatWhen"]; }); +/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(115)(module))) -/* harmony import */ var _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__ = __webpack_require__(469); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "retry", function() { return _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__["retry"]; }); +/***/ }), +/* 481 */ +/***/ (function(module, exports, __webpack_require__) { -/* harmony import */ var _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__ = __webpack_require__(470); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "retryWhen", function() { return _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__["retryWhen"]; }); +const conversions = __webpack_require__(482); +const route = __webpack_require__(483); -/* harmony import */ var _internal_operators_refCount__WEBPACK_IMPORTED_MODULE_65__ = __webpack_require__(30); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "refCount", function() { return _internal_operators_refCount__WEBPACK_IMPORTED_MODULE_65__["refCount"]; }); +const convert = {}; -/* harmony import */ var _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__ = __webpack_require__(471); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sample", function() { return _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__["sample"]; }); +const models = Object.keys(conversions); -/* harmony import */ var _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__ = __webpack_require__(472); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sampleTime", function() { return _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__["sampleTime"]; }); +function wrapRaw(fn) { + const wrappedFn = function (...args) { + const arg0 = args[0]; + if (arg0 === undefined || arg0 === null) { + return arg0; + } -/* harmony import */ var _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__ = __webpack_require__(452); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "scan", function() { return _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__["scan"]; }); + if (arg0.length > 1) { + args = arg0; + } -/* harmony import */ var _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__ = __webpack_require__(473); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sequenceEqual", function() { return _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__["sequenceEqual"]; }); + return fn(args); + }; -/* harmony import */ var _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__ = __webpack_require__(474); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "share", function() { return _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__["share"]; }); + // Preserve .conversion property if there is one + if ('conversion' in fn) { + wrappedFn.conversion = fn.conversion; + } -/* harmony import */ var _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__ = __webpack_require__(475); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "shareReplay", function() { return _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__["shareReplay"]; }); + return wrappedFn; +} -/* harmony import */ var _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__ = __webpack_require__(476); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "single", function() { return _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__["single"]; }); +function wrapRounded(fn) { + const wrappedFn = function (...args) { + const arg0 = args[0]; -/* harmony import */ var _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__ = __webpack_require__(477); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skip", function() { return _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__["skip"]; }); + if (arg0 === undefined || arg0 === null) { + return arg0; + } -/* harmony import */ var _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__ = __webpack_require__(478); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipLast", function() { return _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__["skipLast"]; }); + if (arg0.length > 1) { + args = arg0; + } -/* harmony import */ var _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__ = __webpack_require__(479); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipUntil", function() { return _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__["skipUntil"]; }); + const result = fn(args); -/* harmony import */ var _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__ = __webpack_require__(480); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipWhile", function() { return _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__["skipWhile"]; }); + // We're assuming the result is an array here. + // see notice in conversions.js; don't use box types + // in conversion functions. + if (typeof result === 'object') { + for (let len = result.length, i = 0; i < len; i++) { + result[i] = Math.round(result[i]); + } + } -/* harmony import */ var _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__ = __webpack_require__(481); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "startWith", function() { return _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__["startWith"]; }); + return result; + }; -/* harmony import */ var _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__ = __webpack_require__(482); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "subscribeOn", function() { return _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__["subscribeOn"]; }); + // Preserve .conversion property if there is one + if ('conversion' in fn) { + wrappedFn.conversion = fn.conversion; + } -/* harmony import */ var _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__ = __webpack_require__(484); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchAll", function() { return _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__["switchAll"]; }); + return wrappedFn; +} -/* harmony import */ var _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__ = __webpack_require__(485); -/* 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__(486); -/* 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__(434); -/* 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__(447); -/* 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__(487); -/* 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__(488); -/* 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__(489); -/* 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__(490); -/* 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__(491); -/* 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__(433); -/* 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__(492); -/* 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__(493); -/* 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__(494); -/* 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__(495); -/* 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__(496); -/* 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__(497); -/* 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__(498); -/* 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__(499); -/* 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__(500); -/* 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__(501); -/* 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__(502); -/* 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__(503); -/* 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__(504); -/* 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 */ +models.forEach(fromModel => { + convert[fromModel] = {}; + Object.defineProperty(convert[fromModel], 'channels', {value: conversions[fromModel].channels}); + Object.defineProperty(convert[fromModel], 'labels', {value: conversions[fromModel].labels}); + const routes = route(fromModel); + const routeModels = Object.keys(routes); + routeModels.forEach(toModel => { + const fn = routes[toModel]; + convert[fromModel][toModel] = wrapRounded(fn); + convert[fromModel][toModel].raw = wrapRaw(fn); + }); +}); +module.exports = convert; +/***/ }), +/* 482 */ +/***/ (function(module, exports, __webpack_require__) { +/* MIT license */ +/* eslint-disable no-mixed-operators */ +const cssKeywords = __webpack_require__(118); +// NOTE: conversions should only return primitive values (i.e. arrays, or +// values that give correct `typeof` results). +// do not use box values types (i.e. Number(), String(), etc.) +const reverseKeywords = {}; +for (const key of Object.keys(cssKeywords)) { + reverseKeywords[cssKeywords[key]] = key; +} +const convert = { + rgb: {channels: 3, labels: 'rgb'}, + hsl: {channels: 3, labels: 'hsl'}, + hsv: {channels: 3, labels: 'hsv'}, + hwb: {channels: 3, labels: 'hwb'}, + cmyk: {channels: 4, labels: 'cmyk'}, + xyz: {channels: 3, labels: 'xyz'}, + lab: {channels: 3, labels: 'lab'}, + lch: {channels: 3, labels: 'lch'}, + hex: {channels: 1, labels: ['hex']}, + keyword: {channels: 1, labels: ['keyword']}, + ansi16: {channels: 1, labels: ['ansi16']}, + ansi256: {channels: 1, labels: ['ansi256']}, + hcg: {channels: 3, labels: ['h', 'c', 'g']}, + apple: {channels: 3, labels: ['r16', 'g16', 'b16']}, + gray: {channels: 1, labels: ['gray']} +}; +module.exports = convert; +// Hide .channels and .labels properties +for (const model of Object.keys(convert)) { + if (!('channels' in convert[model])) { + throw new Error('missing channels property: ' + model); + } + if (!('labels' in convert[model])) { + throw new Error('missing channel labels property: ' + model); + } + if (convert[model].labels.length !== convert[model].channels) { + throw new Error('channel and label counts mismatch: ' + model); + } + const {channels, labels} = convert[model]; + delete convert[model].channels; + delete convert[model].labels; + Object.defineProperty(convert[model], 'channels', {value: channels}); + Object.defineProperty(convert[model], 'labels', {value: labels}); +} +convert.rgb.hsl = function (rgb) { + const r = rgb[0] / 255; + const g = rgb[1] / 255; + const b = rgb[2] / 255; + const min = Math.min(r, g, b); + const max = Math.max(r, g, b); + const delta = max - min; + let h; + let s; + if (max === min) { + h = 0; + } else if (r === max) { + h = (g - b) / delta; + } else if (g === max) { + h = 2 + (b - r) / delta; + } else if (b === max) { + h = 4 + (r - g) / delta; + } + h = Math.min(h * 60, 360); + if (h < 0) { + h += 360; + } + const l = (min + max) / 2; + if (max === min) { + s = 0; + } else if (l <= 0.5) { + s = delta / (max + min); + } else { + s = delta / (2 - max - min); + } + return [h, s * 100, l * 100]; +}; +convert.rgb.hsv = function (rgb) { + let rdif; + let gdif; + let bdif; + let h; + let s; + const r = rgb[0] / 255; + const g = rgb[1] / 255; + const b = rgb[2] / 255; + const v = Math.max(r, g, b); + const diff = v - Math.min(r, g, b); + const diffc = function (c) { + return (v - c) / 6 / diff + 1 / 2; + }; + if (diff === 0) { + h = 0; + s = 0; + } else { + s = diff / v; + rdif = diffc(r); + gdif = diffc(g); + bdif = diffc(b); + if (r === v) { + h = bdif - gdif; + } else if (g === v) { + h = (1 / 3) + rdif - bdif; + } else if (b === v) { + h = (2 / 3) + gdif - rdif; + } + if (h < 0) { + h += 1; + } else if (h > 1) { + h -= 1; + } + } + return [ + h * 360, + s * 100, + v * 100 + ]; +}; +convert.rgb.hwb = function (rgb) { + const r = rgb[0]; + const g = rgb[1]; + let b = rgb[2]; + const h = convert.rgb.hsl(rgb)[0]; + const w = 1 / 255 * Math.min(r, Math.min(g, b)); + b = 1 - 1 / 255 * Math.max(r, Math.max(g, b)); + return [h, w * 100, b * 100]; +}; +convert.rgb.cmyk = function (rgb) { + const r = rgb[0] / 255; + const g = rgb[1] / 255; + const b = rgb[2] / 255; + const k = Math.min(1 - r, 1 - g, 1 - b); + const c = (1 - r - k) / (1 - k) || 0; + const m = (1 - g - k) / (1 - k) || 0; + const y = (1 - b - k) / (1 - k) || 0; + return [c * 100, m * 100, y * 100, k * 100]; +}; +function comparativeDistance(x, y) { + /* + See https://en.m.wikipedia.org/wiki/Euclidean_distance#Squared_Euclidean_distance + */ + return ( + ((x[0] - y[0]) ** 2) + + ((x[1] - y[1]) ** 2) + + ((x[2] - y[2]) ** 2) + ); +} +convert.rgb.keyword = function (rgb) { + const reversed = reverseKeywords[rgb]; + if (reversed) { + return reversed; + } + let currentClosestDistance = Infinity; + let currentClosestKeyword; + for (const keyword of Object.keys(cssKeywords)) { + const value = cssKeywords[keyword]; + // Compute comparative distance + const distance = comparativeDistance(rgb, value); + // Check if its less, if so set as closest + if (distance < currentClosestDistance) { + currentClosestDistance = distance; + currentClosestKeyword = keyword; + } + } + return currentClosestKeyword; +}; +convert.keyword.rgb = function (keyword) { + return cssKeywords[keyword]; +}; +convert.rgb.xyz = function (rgb) { + let r = rgb[0] / 255; + let g = rgb[1] / 255; + let b = rgb[2] / 255; + // Assume sRGB + r = r > 0.04045 ? (((r + 0.055) / 1.055) ** 2.4) : (r / 12.92); + g = g > 0.04045 ? (((g + 0.055) / 1.055) ** 2.4) : (g / 12.92); + b = b > 0.04045 ? (((b + 0.055) / 1.055) ** 2.4) : (b / 12.92); + const x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805); + const y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722); + const z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505); + return [x * 100, y * 100, z * 100]; +}; +convert.rgb.lab = function (rgb) { + const xyz = convert.rgb.xyz(rgb); + let x = xyz[0]; + let y = xyz[1]; + let z = xyz[2]; + x /= 95.047; + y /= 100; + z /= 108.883; + x = x > 0.008856 ? (x ** (1 / 3)) : (7.787 * x) + (16 / 116); + y = y > 0.008856 ? (y ** (1 / 3)) : (7.787 * y) + (16 / 116); + z = z > 0.008856 ? (z ** (1 / 3)) : (7.787 * z) + (16 / 116); + const l = (116 * y) - 16; + const a = 500 * (x - y); + const b = 200 * (y - z); + return [l, a, b]; +}; +convert.hsl.rgb = function (hsl) { + const h = hsl[0] / 360; + const s = hsl[1] / 100; + const l = hsl[2] / 100; + let t2; + let t3; + let val; + if (s === 0) { + val = l * 255; + return [val, val, val]; + } + if (l < 0.5) { + t2 = l * (1 + s); + } else { + t2 = l + s - l * s; + } + const t1 = 2 * l - t2; + const rgb = [0, 0, 0]; + for (let i = 0; i < 3; i++) { + t3 = h + 1 / 3 * -(i - 1); + if (t3 < 0) { + t3++; + } + if (t3 > 1) { + t3--; + } + if (6 * t3 < 1) { + val = t1 + (t2 - t1) * 6 * t3; + } else if (2 * t3 < 1) { + val = t2; + } else if (3 * t3 < 2) { + val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; + } else { + val = t1; + } + rgb[i] = val * 255; + } + return rgb; +}; +convert.hsl.hsv = function (hsl) { + const h = hsl[0]; + let s = hsl[1] / 100; + let l = hsl[2] / 100; + let smin = s; + const lmin = Math.max(l, 0.01); + l *= 2; + s *= (l <= 1) ? l : 2 - l; + smin *= lmin <= 1 ? lmin : 2 - lmin; + const v = (l + s) / 2; + const sv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s); + return [h, sv * 100, v * 100]; +}; +convert.hsv.rgb = function (hsv) { + const h = hsv[0] / 60; + const s = hsv[1] / 100; + let v = hsv[2] / 100; + const hi = Math.floor(h) % 6; + const f = h - Math.floor(h); + const p = 255 * v * (1 - s); + const q = 255 * v * (1 - (s * f)); + const t = 255 * v * (1 - (s * (1 - f))); + v *= 255; + switch (hi) { + case 0: + return [v, t, p]; + case 1: + return [q, v, p]; + case 2: + return [p, v, t]; + case 3: + return [p, q, v]; + case 4: + return [t, p, v]; + case 5: + return [v, p, q]; + } +}; +convert.hsv.hsl = function (hsv) { + const h = hsv[0]; + const s = hsv[1] / 100; + const v = hsv[2] / 100; + const vmin = Math.max(v, 0.01); + let sl; + let l; + l = (2 - s) * v; + const lmin = (2 - s) * vmin; + sl = s * vmin; + sl /= (lmin <= 1) ? lmin : 2 - lmin; + sl = sl || 0; + l /= 2; + return [h, sl * 100, l * 100]; +}; +// http://dev.w3.org/csswg/css-color/#hwb-to-rgb +convert.hwb.rgb = function (hwb) { + const h = hwb[0] / 360; + let wh = hwb[1] / 100; + let bl = hwb[2] / 100; + const ratio = wh + bl; + let f; + // Wh + bl cant be > 1 + if (ratio > 1) { + wh /= ratio; + bl /= ratio; + } + const i = Math.floor(6 * h); + const v = 1 - bl; + f = 6 * h - i; + if ((i & 0x01) !== 0) { + f = 1 - f; + } + const n = wh + f * (v - wh); // Linear interpolation + let r; + let g; + let b; + /* eslint-disable max-statements-per-line,no-multi-spaces */ + switch (i) { + default: + case 6: + case 0: r = v; g = n; b = wh; break; + case 1: r = n; g = v; b = wh; break; + case 2: r = wh; g = v; b = n; break; + case 3: r = wh; g = n; b = v; break; + case 4: r = n; g = wh; b = v; break; + case 5: r = v; g = wh; b = n; break; + } + /* eslint-enable max-statements-per-line,no-multi-spaces */ + return [r * 255, g * 255, b * 255]; +}; +convert.cmyk.rgb = function (cmyk) { + const c = cmyk[0] / 100; + const m = cmyk[1] / 100; + const y = cmyk[2] / 100; + const k = cmyk[3] / 100; + const r = 1 - Math.min(1, c * (1 - k) + k); + const g = 1 - Math.min(1, m * (1 - k) + k); + const b = 1 - Math.min(1, y * (1 - k) + k); + return [r * 255, g * 255, b * 255]; +}; +convert.xyz.rgb = function (xyz) { + const x = xyz[0] / 100; + const y = xyz[1] / 100; + const z = xyz[2] / 100; + let r; + let g; + let b; + r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986); + g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415); + b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570); + // Assume sRGB + r = r > 0.0031308 + ? ((1.055 * (r ** (1.0 / 2.4))) - 0.055) + : r * 12.92; + g = g > 0.0031308 + ? ((1.055 * (g ** (1.0 / 2.4))) - 0.055) + : g * 12.92; + b = b > 0.0031308 + ? ((1.055 * (b ** (1.0 / 2.4))) - 0.055) + : b * 12.92; + r = Math.min(Math.max(0, r), 1); + g = Math.min(Math.max(0, g), 1); + b = Math.min(Math.max(0, b), 1); + return [r * 255, g * 255, b * 255]; +}; +convert.xyz.lab = function (xyz) { + let x = xyz[0]; + let y = xyz[1]; + let z = xyz[2]; + x /= 95.047; + y /= 100; + z /= 108.883; + x = x > 0.008856 ? (x ** (1 / 3)) : (7.787 * x) + (16 / 116); + y = y > 0.008856 ? (y ** (1 / 3)) : (7.787 * y) + (16 / 116); + z = z > 0.008856 ? (z ** (1 / 3)) : (7.787 * z) + (16 / 116); + const l = (116 * y) - 16; + const a = 500 * (x - y); + const b = 200 * (y - z); + return [l, a, b]; +}; +convert.lab.xyz = function (lab) { + const l = lab[0]; + const a = lab[1]; + const b = lab[2]; + let x; + let y; + let z; + y = (l + 16) / 116; + x = a / 500 + y; + z = y - b / 200; + const y2 = y ** 3; + const x2 = x ** 3; + const z2 = z ** 3; + y = y2 > 0.008856 ? y2 : (y - 16 / 116) / 7.787; + x = x2 > 0.008856 ? x2 : (x - 16 / 116) / 7.787; + z = z2 > 0.008856 ? z2 : (z - 16 / 116) / 7.787; + x *= 95.047; + y *= 100; + z *= 108.883; + return [x, y, z]; +}; +convert.lab.lch = function (lab) { + const l = lab[0]; + const a = lab[1]; + const b = lab[2]; + let h; + const hr = Math.atan2(b, a); + h = hr * 360 / 2 / Math.PI; + if (h < 0) { + h += 360; + } + const c = Math.sqrt(a * a + b * b); + return [l, c, h]; +}; -//# sourceMappingURL=index.js.map +convert.lch.lab = function (lch) { + const l = lch[0]; + const c = lch[1]; + const h = lch[2]; + const hr = h / 360 * 2 * Math.PI; + const a = c * Math.cos(hr); + const b = c * Math.sin(hr); -/***/ }), -/* 408 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + return [l, a, b]; +}; -"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__(12); -/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(90); -/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */ +convert.rgb.ansi16 = function (args, saturation = null) { + const [r, g, b] = args; + let value = saturation === null ? convert.rgb.hsv(args)[2] : saturation; // Hsv -> ansi16 optimization + value = Math.round(value / 50); -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(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["innerSubscribe"])(duration, new _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleInnerSubscriber"](this)); - 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 = undefined; - throttled.unsubscribe(); - } - if (hasValue) { - this.value = undefined; - this.hasValue = false; - this.destination.next(value); - } - }; - AuditSubscriber.prototype.notifyNext = function () { - this.clearThrottle(); - }; - AuditSubscriber.prototype.notifyComplete = function () { - this.clearThrottle(); - }; - return AuditSubscriber; -}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleOuterSubscriber"])); -//# sourceMappingURL=audit.js.map + if (value === 0) { + return 30; + } + let ansi = 30 + + ((Math.round(b / 255) << 2) + | (Math.round(g / 255) << 1) + | Math.round(r / 255)); -/***/ }), -/* 409 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (value === 2) { + ansi += 60; + } -"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__(55); -/* harmony import */ var _audit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(408); -/* harmony import */ var _observable_timer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(108); -/** PURE_IMPORTS_START _scheduler_async,_audit,_observable_timer PURE_IMPORTS_END */ + return ansi; +}; +convert.hsv.ansi16 = function (args) { + // Optimization here; we already know the value and don't need to get + // it converted for us. + return convert.rgb.ansi16(convert.hsv.rgb(args), args[2]); +}; +convert.rgb.ansi256 = function (args) { + const r = args[0]; + const g = args[1]; + const b = args[2]; -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 + // We use the extended greyscale palette here, with the exception of + // black and white. normal palette only has 4 greyscale shades. + if (r === g && g === b) { + if (r < 8) { + return 16; + } + if (r > 248) { + return 231; + } -/***/ }), -/* 410 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + return Math.round(((r - 8) / 247) * 24) + 232; + } -"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__(12); -/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(90); -/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */ + const ansi = 16 + + (36 * Math.round(r / 255 * 5)) + + (6 * Math.round(g / 255 * 5)) + + Math.round(b / 255 * 5); + return ansi; +}; -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(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["innerSubscribe"])(closingNotifier, new _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleInnerSubscriber"](_this))); - return _this; - } - BufferSubscriber.prototype._next = function (value) { - this.buffer.push(value); - }; - BufferSubscriber.prototype.notifyNext = function () { - var buffer = this.buffer; - this.buffer = []; - this.destination.next(buffer); - }; - return BufferSubscriber; -}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleOuterSubscriber"])); -//# sourceMappingURL=buffer.js.map +convert.ansi16.rgb = function (args) { + let color = args % 10; + // Handle greyscale + if (color === 0 || color === 7) { + if (args > 50) { + color += 3.5; + } -/***/ }), -/* 411 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + color = color / 10.5 * 255; -"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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + return [color, color, color]; + } + const mult = (~~(args > 50) + 1) * 0.5; + const r = ((color & 1) * mult) * 255; + const g = (((color >> 1) & 1) * mult) * 255; + const b = (((color >> 2) & 1) * mult) * 255; -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 + return [r, g, b]; +}; +convert.ansi256.rgb = function (args) { + // Handle greyscale + if (args >= 232) { + const c = (args - 232) * 10 + 8; + return [c, c, c]; + } -/***/ }), -/* 412 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + args -= 16; -"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__(12); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(55); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(11); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(45); -/** PURE_IMPORTS_START tslib,_scheduler_async,_Subscriber,_util_isScheduler PURE_IMPORTS_END */ + let rem; + const r = Math.floor(args / 36) / 5 * 255; + const g = Math.floor((rem = args % 36) / 6) / 5 * 255; + const b = (rem % 6) / 5 * 255; + return [r, g, b]; +}; +convert.rgb.hex = function (args) { + const integer = ((Math.round(args[0]) & 0xFF) << 16) + + ((Math.round(args[1]) & 0xFF) << 8) + + (Math.round(args[2]) & 0xFF); + const string = integer.toString(16).toUpperCase(); + return '000000'.substring(string.length) + string; +}; -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 +convert.hex.rgb = function (args) { + const match = args.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i); + if (!match) { + return [0, 0, 0]; + } + let colorString = match[0]; -/***/ }), -/* 413 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (match[0].length === 3) { + colorString = colorString.split('').map(char => { + return char + char; + }).join(''); + } -"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__(12); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(17); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(70); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(69); -/** PURE_IMPORTS_START tslib,_Subscription,_util_subscribeToResult,_OuterSubscriber PURE_IMPORTS_END */ + const integer = parseInt(colorString, 16); + const r = (integer >> 16) & 0xFF; + const g = (integer >> 8) & 0xFF; + const b = integer & 0xFF; + return [r, g, b]; +}; +convert.rgb.hcg = function (rgb) { + const r = rgb[0] / 255; + const g = rgb[1] / 255; + const b = rgb[2] / 255; + const max = Math.max(Math.max(r, g), b); + const min = Math.min(Math.min(r, g), b); + const chroma = (max - min); + let grayscale; + let hue; + if (chroma < 1) { + grayscale = min / (1 - chroma); + } else { + grayscale = 0; + } -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.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) { - 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 + if (chroma <= 0) { + hue = 0; + } else + if (max === r) { + hue = ((g - b) / chroma) % 6; + } else + if (max === g) { + hue = 2 + (b - r) / chroma; + } else { + hue = 4 + (r - g) / chroma; + } + hue /= 6; + hue %= 1; -/***/ }), -/* 414 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + return [hue * 360, chroma * 100, grayscale * 100]; +}; -"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__(12); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(17); -/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(90); -/** PURE_IMPORTS_START tslib,_Subscription,_innerSubscribe PURE_IMPORTS_END */ +convert.hsl.hcg = function (hsl) { + const s = hsl[1] / 100; + const l = hsl[2] / 100; + const c = l < 0.5 ? (2.0 * s * l) : (2.0 * s * (1.0 - l)); + let f = 0; + if (c < 1.0) { + f = (l - 0.5 * c) / (1.0 - c); + } -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 = undefined; - this.subscribing = false; - }; - BufferWhenSubscriber.prototype.notifyNext = function () { - 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(_innerSubscribe__WEBPACK_IMPORTED_MODULE_2__["innerSubscribe"])(closingNotifier, new _innerSubscribe__WEBPACK_IMPORTED_MODULE_2__["SimpleInnerSubscriber"](this))); - this.subscribing = false; - }; - return BufferWhenSubscriber; -}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_2__["SimpleOuterSubscriber"])); -//# sourceMappingURL=bufferWhen.js.map + return [hsl[0], c * 100, f * 100]; +}; +convert.hsv.hcg = function (hsv) { + const s = hsv[1] / 100; + const v = hsv[2] / 100; -/***/ }), -/* 415 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + const c = s * v; + let f = 0; -"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__(12); -/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(90); -/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */ + if (c < 1.0) { + f = (v - c) / (1 - c); + } + return [hsv[0], c * 100, f * 100]; +}; -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 _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleInnerSubscriber"](this); - this.add(innerSubscriber); - var innerSubscription = Object(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["innerSubscribe"])(result, innerSubscriber); - if (innerSubscription !== innerSubscriber) { - this.add(innerSubscription); - } - } - }; - return CatchSubscriber; -}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleOuterSubscriber"])); -//# sourceMappingURL=catchError.js.map +convert.hcg.rgb = function (hcg) { + const h = hcg[0] / 360; + const c = hcg[1] / 100; + const g = hcg[2] / 100; + if (c === 0.0) { + return [g * 255, g * 255, g * 255]; + } -/***/ }), -/* 416 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + const pure = [0, 0, 0]; + const hi = (h % 1) * 6; + const v = hi % 1; + const w = 1 - v; + let mg = 0; -"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__(68); -/** PURE_IMPORTS_START _observable_combineLatest PURE_IMPORTS_END */ + /* eslint-disable max-statements-per-line */ + switch (Math.floor(hi)) { + case 0: + pure[0] = 1; pure[1] = v; pure[2] = 0; break; + case 1: + pure[0] = w; pure[1] = 1; pure[2] = 0; break; + case 2: + pure[0] = 0; pure[1] = 1; pure[2] = v; break; + case 3: + pure[0] = 0; pure[1] = w; pure[2] = 1; break; + case 4: + pure[0] = v; pure[1] = 0; pure[2] = 1; break; + default: + pure[0] = 1; pure[1] = 0; pure[2] = w; + } + /* eslint-enable max-statements-per-line */ -function combineAll(project) { - return function (source) { return source.lift(new _observable_combineLatest__WEBPACK_IMPORTED_MODULE_0__["CombineLatestOperator"](project)); }; -} -//# sourceMappingURL=combineAll.js.map + mg = (1.0 - c) * g; + return [ + (c * pure[0] + mg) * 255, + (c * pure[1] + mg) * 255, + (c * pure[2] + mg) * 255 + ]; +}; -/***/ }), -/* 417 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +convert.hcg.hsv = function (hcg) { + const c = hcg[1] / 100; + const g = hcg[2] / 100; -"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__(18); -/* harmony import */ var _observable_combineLatest__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(68); -/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(83); -/** PURE_IMPORTS_START _util_isArray,_observable_combineLatest,_observable_from PURE_IMPORTS_END */ + const v = c + g * (1.0 - c); + let f = 0; + if (v > 0.0) { + f = c / v; + } + return [hcg[0], f * 100, v * 100]; +}; -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 +convert.hcg.hsl = function (hcg) { + const c = hcg[1] / 100; + const g = hcg[2] / 100; + const l = g * (1.0 - c) + 0.5 * c; + let s = 0; -/***/ }), -/* 418 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (l > 0.0 && l < 0.5) { + s = c / (2 * l); + } else + if (l >= 0.5 && l < 1.0) { + s = c / (2 * (1 - l)); + } -"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__(79); -/** PURE_IMPORTS_START _observable_concat PURE_IMPORTS_END */ + return [hcg[0], s * 100, l * 100]; +}; -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 +convert.hcg.hwb = function (hcg) { + const c = hcg[1] / 100; + const g = hcg[2] / 100; + const v = c + g * (1.0 - c); + return [hcg[0], (v - c) * 100, (1 - v) * 100]; +}; +convert.hwb.hcg = function (hwb) { + const w = hwb[1] / 100; + const b = hwb[2] / 100; + const v = 1 - b; + const c = v - w; + let g = 0; -/***/ }), -/* 419 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (c < 1) { + g = (v - c) / (1 - c); + } -"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__(82); -/** PURE_IMPORTS_START _mergeMap PURE_IMPORTS_END */ + return [hwb[0], c * 100, g * 100]; +}; -function concatMap(project, resultSelector) { - return Object(_mergeMap__WEBPACK_IMPORTED_MODULE_0__["mergeMap"])(project, resultSelector, 1); -} -//# sourceMappingURL=concatMap.js.map +convert.apple.rgb = function (apple) { + return [(apple[0] / 65535) * 255, (apple[1] / 65535) * 255, (apple[2] / 65535) * 255]; +}; +convert.rgb.apple = function (rgb) { + return [(rgb[0] / 255) * 65535, (rgb[1] / 255) * 65535, (rgb[2] / 255) * 65535]; +}; -/***/ }), -/* 420 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +convert.gray.rgb = function (args) { + return [args[0] / 100 * 255, args[0] / 100 * 255, args[0] / 100 * 255]; +}; -"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__(419); -/** PURE_IMPORTS_START _concatMap PURE_IMPORTS_END */ +convert.gray.hsl = function (args) { + return [0, 0, args[0]]; +}; -function concatMapTo(innerObservable, resultSelector) { - return Object(_concatMap__WEBPACK_IMPORTED_MODULE_0__["concatMap"])(function () { return innerObservable; }, resultSelector); -} -//# sourceMappingURL=concatMapTo.js.map +convert.gray.hsv = convert.gray.hsl; +convert.gray.hwb = function (gray) { + return [0, 100, gray[0]]; +}; -/***/ }), -/* 421 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +convert.gray.cmyk = function (gray) { + return [0, 0, 0, gray[0]]; +}; -"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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ +convert.gray.lab = function (gray) { + return [gray[0], 0, 0]; +}; +convert.gray.hex = function (gray) { + const val = Math.round(gray[0] / 100 * 255) & 0xFF; + const integer = (val << 16) + (val << 8) + val; -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 + const string = integer.toString(16).toUpperCase(); + return '000000'.substring(string.length) + string; +}; + +convert.rgb.gray = function (rgb) { + const val = (rgb[0] + rgb[1] + rgb[2]) / 3; + return [val / 255 * 100]; +}; /***/ }), -/* 422 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 483 */ +/***/ (function(module, 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__(12); -/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(90); -/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */ +const conversions = __webpack_require__(482); +/* + This function routes a model to all other models. -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; - 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(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["innerSubscribe"])(duration, new _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleInnerSubscriber"](this)); - if (subscription && !subscription.closed) { - this.add(this.durationSubscription = subscription); - } - }; - DebounceSubscriber.prototype.notifyNext = function () { - 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 = undefined; - subscription.unsubscribe(); - this.remove(subscription); - } - this.value = undefined; - this.hasValue = false; - _super.prototype._next.call(this, value); - } - }; - return DebounceSubscriber; -}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleOuterSubscriber"])); -//# sourceMappingURL=debounce.js.map + all functions that are routed have a property `.conversion` attached + to the returned synthetic function. This property is an array + of strings, each with the steps in between the 'from' and 'to' + color models (inclusive). + + conversions that are not possible simply are not included. +*/ + +function buildGraph() { + const graph = {}; + // https://jsperf.com/object-keys-vs-for-in-with-closure/3 + const models = Object.keys(conversions); + for (let len = models.length, i = 0; i < len; i++) { + graph[models[i]] = { + // http://jsperf.com/1-vs-infinity + // micro-opt, but this is simple. + distance: -1, + parent: null + }; + } -/***/ }), -/* 423 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + return graph; +} -"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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(55); -/** PURE_IMPORTS_START tslib,_Subscriber,_scheduler_async PURE_IMPORTS_END */ +// https://en.wikipedia.org/wiki/Breadth-first_search +function deriveBFS(fromModel) { + const graph = buildGraph(); + const queue = [fromModel]; // Unshift -> queue -> pop + graph[fromModel].distance = 0; + while (queue.length) { + const current = queue.pop(); + const adjacents = Object.keys(conversions[current]); -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)); }; + for (let len = adjacents.length, i = 0; i < len; i++) { + const adjacent = adjacents[i]; + const node = graph[adjacent]; + + if (node.distance === -1) { + node.distance = graph[current].distance + 1; + node.parent = current; + queue.unshift(adjacent); + } + } + } + + return graph; } -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(); + +function link(from, to) { + return function (args) { + return to(from(args)); + }; } -//# sourceMappingURL=debounceTime.js.map +function wrapConversion(toModel, graph) { + const path = [graph[toModel].parent, toModel]; + let fn = conversions[graph[toModel].parent][toModel]; -/***/ }), -/* 424 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + let cur = graph[toModel].parent; + while (graph[cur].parent) { + path.unshift(graph[cur].parent); + fn = link(conversions[graph[cur].parent][cur], fn); + cur = graph[cur].parent; + } -"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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + fn.conversion = path; + return fn; +} + +module.exports = function (fromModel) { + const graph = deriveBFS(fromModel); + const conversion = {}; + const models = Object.keys(graph); + for (let len = models.length, i = 0; i < len; i++) { + const toModel = models[i]; + const node = graph[toModel]; + + if (node.parent === null) { + // No possible conversion, or this node is the source model. + continue; + } + + conversion[toModel] = wrapConversion(toModel, graph); + } + + return conversion; +}; -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 /***/ }), -/* 425 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 484 */ +/***/ (function(module, 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__(12); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(55); -/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(426); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(11); -/* harmony import */ var _Notification__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(42); -/** PURE_IMPORTS_START tslib,_scheduler_async,_util_isDate,_Subscriber,_Notification PURE_IMPORTS_END */ +const stringReplaceAll = (string, substring, replacer) => { + let index = string.indexOf(substring); + if (index === -1) { + return string; + } + const substringLength = substring.length; + let endIndex = 0; + let returnValue = ''; + do { + returnValue += string.substr(endIndex, index - endIndex) + substring + replacer; + endIndex = index + substringLength; + index = string.indexOf(substring, endIndex); + } while (index !== -1); + returnValue += string.substr(endIndex); + return returnValue; +}; -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 +const stringEncaseCRLFWithFirstIndex = (string, prefix, postfix, index) => { + let endIndex = 0; + let returnValue = ''; + do { + const gotCR = string[index - 1] === '\r'; + returnValue += string.substr(endIndex, (gotCR ? index - 1 : index) - endIndex) + prefix + (gotCR ? '\r\n' : '\n') + postfix; + endIndex = index + 1; + index = string.indexOf('\n', endIndex); + } while (index !== -1); + + returnValue += string.substr(endIndex); + return returnValue; +}; + +module.exports = { + stringReplaceAll, + stringEncaseCRLFWithFirstIndex +}; /***/ }), -/* 426 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 485 */ +/***/ (function(module, 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 +const TEMPLATE_REGEX = /(?:\\(u(?:[a-f\d]{4}|\{[a-f\d]{1,6}\})|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}|\{[a-f\d]{1,6}\})|x[a-f\d]{2}|.)|([^\\])/gi; -/***/ }), -/* 427 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +const ESCAPES = new Map([ + ['n', '\n'], + ['r', '\r'], + ['t', '\t'], + ['b', '\b'], + ['f', '\f'], + ['v', '\v'], + ['0', '\0'], + ['\\', '\\'], + ['e', '\u001B'], + ['a', '\u0007'] +]); -"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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(9); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(69); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(70); -/** PURE_IMPORTS_START tslib,_Subscriber,_Observable,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ +function unescape(c) { + const u = c[0] === 'u'; + const bracket = c[1] === '{'; + if ((u && !bracket && c.length === 5) || (c[0] === 'x' && c.length === 3)) { + return String.fromCharCode(parseInt(c.slice(1), 16)); + } + if (u && bracket) { + return String.fromCodePoint(parseInt(c.slice(2, -1), 16)); + } + return ESCAPES.get(c) || c; +} +function parseArguments(name, arguments_) { + const results = []; + const chunks = arguments_.trim().split(/\s*,\s*/g); + let matches; -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)); }; + for (const chunk of chunks) { + const number = Number(chunk); + if (!Number.isNaN(number)) { + results.push(number); + } else if ((matches = chunk.match(STRING_REGEX))) { + results.push(matches[2].replace(ESCAPE_REGEX, (m, escape, character) => escape ? unescape(escape) : character)); + } else { + throw new Error(`Invalid Chalk template style argument: ${chunk} (in style '${name}')`); + } + } + + return results; } -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 +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]); + } + } -/***/ }), -/* 428 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + return results; +} -"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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ +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); + } + } -function dematerialize() { - return function dematerializeOperatorFunction(source) { - return source.lift(new DeMaterializeOperator()); - }; + let current = chalk; + for (const [styleName, styles] of Object.entries(enabled)) { + if (!Array.isArray(styles)) { + continue; + } + + if (!(styleName in current)) { + throw new Error(`Unknown Chalk style: ${styleName}`); + } + + current = styles.length > 0 ? current[styleName](...styles) : current[styleName]; + } + + return current; } -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 + +module.exports = (chalk, temporary) => { + const styles = []; + const chunks = []; + let chunk = []; + + // eslint-disable-next-line max-params + temporary.replace(TEMPLATE_REGEX, (m, escapeCharacter, inverse, style, close, character) => { + if (escapeCharacter) { + chunk.push(unescape(escapeCharacter)); + } else if (style) { + const string = chunk.join(''); + chunk = []; + chunks.push(styles.length === 0 ? string : buildStyle(chalk, styles)(string)); + 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(character); + } + }); + + 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(''); +}; /***/ }), -/* 429 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 486 */ +/***/ (function(module, 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__(12); -/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(90); -/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */ +const restoreCursor = __webpack_require__(487); -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(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["innerSubscribe"])(flushes, new _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleInnerSubscriber"](_this))); - } - return _this; - } - DistinctSubscriber.prototype.notifyNext = function () { - this.values.clear(); - }; - DistinctSubscriber.prototype.notifyError = function (error) { - 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; -}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleOuterSubscriber"])); +let isHidden = false; -//# sourceMappingURL=distinct.js.map +exports.show = (writableStream = process.stderr) => { + if (!writableStream.isTTY) { + return; + } + + isHidden = false; + writableStream.write('\u001B[?25h'); +}; + +exports.hide = (writableStream = process.stderr) => { + if (!writableStream.isTTY) { + return; + } + + restoreCursor(); + isHidden = true; + writableStream.write('\u001B[?25l'); +}; + +exports.toggle = (force, writableStream) => { + if (force !== undefined) { + isHidden = force; + } + + if (isHidden) { + exports.show(writableStream); + } else { + exports.hide(writableStream); + } +}; /***/ }), -/* 430 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 487 */ +/***/ (function(module, 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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ +const onetime = __webpack_require__(337); +const signalExit = __webpack_require__(309); -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 = onetime(() => { + signalExit(() => { + process.stderr.write('\u001B[?25h'); + }, {alwaysLast: true}); +}); /***/ }), -/* 431 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 488 */ +/***/ (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__(430); -/** 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 + +const spinners = Object.assign({}, __webpack_require__(489)); + +const spinnersList = Object.keys(spinners); + +Object.defineProperty(spinners, 'random', { + get() { + const randomIndex = Math.floor(Math.random() * spinnersList.length); + const spinnerName = spinnersList[randomIndex]; + return spinners[spinnerName]; + } +}); + +module.exports = spinners; +// TODO: Remove this for the next major release +module.exports.default = spinners; /***/ }), -/* 432 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 489 */ +/***/ (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\":[\"⢀⠀\",\"⡀⠀\",\"⠄⠀\",\"⢂⠀\",\"⡂⠀\",\"⠅⠀\",\"⢃⠀\",\"⡃⠀\",\"⠍⠀\",\"⢋⠀\",\"⡋⠀\",\"⠍⠁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⢈⠩\",\"⡀⢙\",\"⠄⡙\",\"⢂⠩\",\"⡂⢘\",\"⠅⡘\",\"⢃⠨\",\"⡃⢐\",\"⠍⡐\",\"⢋⠠\",\"⡋⢀\",\"⠍⡁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⠈⠩\",\"⠀⢙\",\"⠀⡙\",\"⠀⠩\",\"⠀⢘\",\"⠀⡘\",\"⠀⠨\",\"⠀⢐\",\"⠀⡐\",\"⠀⠠\",\"⠀⢀\",\"⠀⡀\"]},\"dots8Bit\":{\"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\":[\"🌍 \",\"🌎 \",\"🌏 \"]},\"material\":{\"interval\":17,\"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\":[\"🌲\",\"🎄\"]},\"grenade\":{\"interval\":80,\"frames\":[\"، \",\"′ \",\" ´ \",\" ‾ \",\" ⸌\",\" ⸊\",\" |\",\" ⁎\",\" ⁕\",\" ෴ \",\" ⁓\",\" \",\" \",\" \"]},\"point\":{\"interval\":125,\"frames\":[\"∙∙∙\",\"●∙∙\",\"∙●∙\",\"∙∙●\",\"∙∙∙\"]},\"layer\":{\"interval\":150,\"frames\":[\"-\",\"=\",\"≡\"]},\"betaWave\":{\"interval\":80,\"frames\":[\"ρββββββ\",\"βρβββββ\",\"ββρββββ\",\"βββρβββ\",\"ββββρββ\",\"βββββρβ\",\"ββββββρ\"]},\"aesthetic\":{\"interval\":80,\"frames\":[\"▰▱▱▱▱▱▱\",\"▰▰▱▱▱▱▱\",\"▰▰▰▱▱▱▱\",\"▰▰▰▰▱▱▱\",\"▰▰▰▰▰▱▱\",\"▰▰▰▰▰▰▱\",\"▰▰▰▰▰▰▰\",\"▰▱▱▱▱▱▱\"]}}"); + +/***/ }), +/* 490 */ +/***/ (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__(62); -/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(105); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(433); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(424); -/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(434); -/** PURE_IMPORTS_START _util_ArgumentOutOfRangeError,_filter,_throwIfEmpty,_defaultIfEmpty,_take PURE_IMPORTS_END */ +const chalk = __webpack_require__(491); +const isSupported = process.platform !== 'win32' || process.env.CI || process.env.TERM === 'xterm-256color'; +const main = { + info: chalk.blue('ℹ'), + success: chalk.green('✔'), + warning: chalk.yellow('⚠'), + error: chalk.red('✖') +}; +const fallbacks = { + info: chalk.blue('i'), + success: chalk.green('√'), + warning: chalk.yellow('‼'), + error: chalk.red('×') +}; -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 +module.exports = isSupported ? main : fallbacks; /***/ }), -/* 433 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 491 */ +/***/ (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__(12); -/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(63); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(11); -/** PURE_IMPORTS_START tslib,_util_EmptyError,_Subscriber PURE_IMPORTS_END */ +const escapeStringRegexp = __webpack_require__(265); +const ansiStyles = __webpack_require__(492); +const stdoutColor = __webpack_require__(497).stdout; +const template = __webpack_require__(499); -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"](); +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; } -//# sourceMappingURL=throwIfEmpty.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); -/***/ }), -/* 434 */ -/***/ (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__, "take", function() { return take; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(62); -/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(43); -/** PURE_IMPORTS_START tslib,_Subscriber,_util_ArgumentOutOfRangeError,_observable_empty PURE_IMPORTS_END */ + Object.setPrototypeOf(chalk, Chalk.prototype); + Object.setPrototypeOf(chalk.template, chalk); + chalk.template.constructor = Chalk; + return chalk.template; + } + applyOptions(this, options); +} -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)); - } - }; +// Use bright blue on Windows as the normal blue color is illegible +if (isSimpleWindowsTerm) { + ansiStyles.blue.open = '\u001B[94m'; } -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 +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); + }; + } + }; +} -/***/ }), -/* 435 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +ansiStyles.bgColor.closeRe = new RegExp(escapeStringRegexp(ansiStyles.bgColor.close), 'g'); +for (const model of Object.keys(ansiStyles.bgColor.ansi)) { + if (skipModels.has(model)) { + continue; + } -"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__(79); -/* harmony import */ var _observable_of__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(44); -/** PURE_IMPORTS_START _observable_concat,_observable_of PURE_IMPORTS_END */ + 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 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 +function build(_styles, _empty, key) { + const builder = function () { + return applyStyle.apply(builder, arguments); + }; + builder._styles = _styles; + builder._empty = _empty; -/***/ }), -/* 436 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + const self = this; -"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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + 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'; -function every(predicate, thisArg) { - return function (source) { return source.lift(new EveryOperator(predicate, thisArg, source)); }; + // `__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; } -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 +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]); -/***/ }), -/* 437 */ -/***/ (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__, "exhaust", function() { return exhaust; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(12); -/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(90); -/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */ + 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; + } -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(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["innerSubscribe"])(value, new _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleInnerSubscriber"](this))); - } - }; - SwitchFirstSubscriber.prototype._complete = function () { - this.hasCompleted = true; - if (!this.hasSubscription) { - this.destination.complete(); - } - }; - SwitchFirstSubscriber.prototype.notifyComplete = function () { - this.hasSubscription = false; - if (this.hasCompleted) { - this.destination.complete(); - } - }; - return SwitchFirstSubscriber; -}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleOuterSubscriber"])); -//# sourceMappingURL=exhaust.js.map + // 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; -/***/ }), -/* 438 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + // 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}`); + } -"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__(12); -/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(66); -/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(83); -/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(90); -/** PURE_IMPORTS_START tslib,_map,_observable_from,_innerSubscribe PURE_IMPORTS_END */ + // 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]]; -function exhaustMap(project, resultSelector) { - if (resultSelector) { - return function (source) { return source.pipe(exhaustMap(function (a, i) { return Object(_observable_from__WEBPACK_IMPORTED_MODULE_2__["from"])(project(a, i)).pipe(Object(_map__WEBPACK_IMPORTED_MODULE_1__["map"])(function (b, ii) { return resultSelector(a, b, i, ii); })); })); }; - } - return function (source) { - return source.lift(new ExhaustMapOperator(project)); - }; + 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('')); } -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); - }; - ExhaustMapSubscriber.prototype._innerSub = function (result) { - var innerSubscriber = new _innerSubscribe__WEBPACK_IMPORTED_MODULE_3__["SimpleInnerSubscriber"](this); - var destination = this.destination; - destination.add(innerSubscriber); - var innerSubscription = Object(_innerSubscribe__WEBPACK_IMPORTED_MODULE_3__["innerSubscribe"])(result, innerSubscriber); - if (innerSubscription !== innerSubscriber) { - destination.add(innerSubscription); - } - }; - ExhaustMapSubscriber.prototype._complete = function () { - this.hasCompleted = true; - if (!this.hasSubscription) { - this.destination.complete(); - } - this.unsubscribe(); - }; - ExhaustMapSubscriber.prototype.notifyNext = function (innerValue) { - this.destination.next(innerValue); - }; - ExhaustMapSubscriber.prototype.notifyError = function (err) { - this.destination.error(err); - }; - ExhaustMapSubscriber.prototype.notifyComplete = function () { - this.hasSubscription = false; - if (this.hasCompleted) { - this.destination.complete(); - } - }; - return ExhaustMapSubscriber; -}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_3__["SimpleOuterSubscriber"])); -//# sourceMappingURL=exhaustMap.js.map + +Object.defineProperties(Chalk.prototype, styles); + +module.exports = Chalk(); // eslint-disable-line new-cap +module.exports.supportsColor = stdoutColor; +module.exports.default = module.exports; // For TypeScript /***/ }), -/* 439 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 492 */ +/***/ (function(module, 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__(12); -/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(90); -/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */ +/* WEBPACK VAR INJECTION */(function(module) { +const colorConvert = __webpack_require__(493); +const wrapAnsi16 = (fn, offset) => function () { + const code = fn.apply(colorConvert, arguments); + return `\u001B[${code + offset}m`; +}; -function expand(project, concurrent, scheduler) { - if (concurrent === void 0) { - concurrent = Number.POSITIVE_INFINITY; - } - 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; -}()); +const wrapAnsi256 = (fn, offset) => function () { + const code = fn.apply(colorConvert, arguments); + return `\u001B[${38 + offset};5;${code}m`; +}; -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(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["innerSubscribe"])(result, new _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleInnerSubscriber"](this))); - }; - ExpandSubscriber.prototype._complete = function () { - this.hasCompleted = true; - if (this.hasCompleted && this.active === 0) { - this.destination.complete(); - } - this.unsubscribe(); - }; - ExpandSubscriber.prototype.notifyNext = function (innerValue) { - this._next(innerValue); - }; - ExpandSubscriber.prototype.notifyComplete = function () { - var buffer = this.buffer; - this.active--; - if (buffer && buffer.length > 0) { - this._next(buffer.shift()); - } - if (this.hasCompleted && this.active === 0) { - this.destination.complete(); - } - }; - return ExpandSubscriber; -}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleOuterSubscriber"])); +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] + } + }; -//# sourceMappingURL=expand.js.map + // Fix humans + styles.color.grey = styles.color.gray; + for (const groupName of Object.keys(styles)) { + const group = styles[groupName]; -/***/ }), -/* 440 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + for (const styleName of Object.keys(group)) { + const style = group[styleName]; -"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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(17); -/** PURE_IMPORTS_START tslib,_Subscriber,_Subscription PURE_IMPORTS_END */ + styles[styleName] = { + open: `\u001B[${style[0]}m`, + close: `\u001B[${style[1]}m` + }; + group[styleName] = styles[styleName]; + codes.set(style[0], style[1]); + } -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 + Object.defineProperty(styles, groupName, { + value: group, + enumerable: false + }); + Object.defineProperty(styles, 'codes', { + value: codes, + enumerable: false + }); + } -/***/ }), -/* 441 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + const ansi2ansi = n => n; + const rgb2rgb = (r, g, b) => [r, g, b]; -"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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + 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) + }; -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; -}()); + styles.bgColor.ansi = { + ansi: wrapAnsi16(ansi2ansi, 10) + }; + styles.bgColor.ansi256 = { + ansi256: wrapAnsi256(ansi2ansi, 10) + }; + styles.bgColor.ansi16m = { + rgb: wrapAnsi16m(rgb2rgb, 10) + }; -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"])); + for (let key of Object.keys(colorConvert)) { + if (typeof colorConvert[key] !== 'object') { + continue; + } -//# sourceMappingURL=find.js.map + const suite = colorConvert[key]; + if (key === 'ansi16') { + key = 'ansi'; + } -/***/ }), -/* 442 */ -/***/ (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__, "findIndex", function() { return findIndex; }); -/* harmony import */ var _operators_find__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(441); -/** PURE_IMPORTS_START _operators_find PURE_IMPORTS_END */ + if ('ansi256' in suite) { + styles.color.ansi256[key] = wrapAnsi256(suite.ansi256, 0); + styles.bgColor.ansi256[key] = wrapAnsi256(suite.ansi256, 10); + } -function findIndex(predicate, thisArg) { - return function (source) { return source.lift(new _operators_find__WEBPACK_IMPORTED_MODULE_0__["FindValueOperator"](predicate, source, true, thisArg)); }; + if ('rgb' in suite) { + styles.color.ansi16m[key] = wrapAnsi16m(suite.rgb, 0); + styles.bgColor.ansi16m[key] = wrapAnsi16m(suite.rgb, 10); + } + } + + return styles; } -//# sourceMappingURL=findIndex.js.map +// Make the export immutable +Object.defineProperty(module, 'exports', { + enumerable: true, + get: assembleStyles +}); + +/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(115)(module))) /***/ }), -/* 443 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 493 */ +/***/ (function(module, 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__(63); -/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(105); -/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(434); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(424); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(433); -/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(25); -/** PURE_IMPORTS_START _util_EmptyError,_filter,_take,_defaultIfEmpty,_throwIfEmpty,_util_identity PURE_IMPORTS_END */ +var conversions = __webpack_require__(494); +var route = __webpack_require__(496); +var convert = {}; +var models = Object.keys(conversions); +function wrapRaw(fn) { + var wrappedFn = function (args) { + if (args === undefined || args === null) { + return args; + } + if (arguments.length > 1) { + args = Array.prototype.slice.call(arguments); + } + return fn(args); + }; -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"](); })); }; + // preserve .conversion property if there is one + if ('conversion' in fn) { + wrappedFn.conversion = fn.conversion; + } + + return wrappedFn; } -//# sourceMappingURL=first.js.map +function wrapRounded(fn) { + var wrappedFn = function (args) { + if (args === undefined || args === null) { + return args; + } -/***/ }), -/* 444 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (arguments.length > 1) { + args = Array.prototype.slice.call(arguments); + } -"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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + var result = fn(args); + + // we're assuming the result is an array here. + // see notice in conversions.js; don't use box types + // in conversion functions. + if (typeof result === 'object') { + for (var len = result.length, i = 0; i < len; i++) { + result[i] = Math.round(result[i]); + } + } + return result; + }; -function ignoreElements() { - return function ignoreElementsOperatorFunction(source) { - return source.lift(new IgnoreElementsOperator()); - }; + // preserve .conversion property if there is one + if ('conversion' in fn) { + wrappedFn.conversion = fn.conversion; + } + + return wrappedFn; } -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 +models.forEach(function (fromModel) { + convert[fromModel] = {}; -/***/ }), -/* 445 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + Object.defineProperty(convert[fromModel], 'channels', {value: conversions[fromModel].channels}); + Object.defineProperty(convert[fromModel], 'labels', {value: conversions[fromModel].labels}); -"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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + var routes = route(fromModel); + var routeModels = Object.keys(routes); + routeModels.forEach(function (toModel) { + var fn = routes[toModel]; -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 + convert[fromModel][toModel] = wrapRounded(fn); + convert[fromModel][toModel].raw = wrapRaw(fn); + }); +}); + +module.exports = convert; /***/ }), -/* 446 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 494 */ +/***/ (function(module, 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__(63); -/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(105); -/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(447); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(433); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(424); -/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(25); -/** PURE_IMPORTS_START _util_EmptyError,_filter,_takeLast,_throwIfEmpty,_defaultIfEmpty,_util_identity PURE_IMPORTS_END */ +/* MIT license */ +var cssKeywords = __webpack_require__(495); + +// NOTE: conversions should only return primitive values (i.e. arrays, or +// values that give correct `typeof` results). +// do not use box values types (i.e. Number(), String(), etc.) +var reverseKeywords = {}; +for (var key in cssKeywords) { + if (cssKeywords.hasOwnProperty(key)) { + reverseKeywords[cssKeywords[key]] = key; + } +} +var convert = module.exports = { + rgb: {channels: 3, labels: 'rgb'}, + hsl: {channels: 3, labels: 'hsl'}, + hsv: {channels: 3, labels: 'hsv'}, + hwb: {channels: 3, labels: 'hwb'}, + cmyk: {channels: 4, labels: 'cmyk'}, + xyz: {channels: 3, labels: 'xyz'}, + lab: {channels: 3, labels: 'lab'}, + lch: {channels: 3, labels: 'lch'}, + hex: {channels: 1, labels: ['hex']}, + keyword: {channels: 1, labels: ['keyword']}, + ansi16: {channels: 1, labels: ['ansi16']}, + ansi256: {channels: 1, labels: ['ansi256']}, + hcg: {channels: 3, labels: ['h', 'c', 'g']}, + apple: {channels: 3, labels: ['r16', 'g16', 'b16']}, + gray: {channels: 1, labels: ['gray']} +}; +// hide .channels and .labels properties +for (var model in convert) { + if (convert.hasOwnProperty(model)) { + if (!('channels' in convert[model])) { + throw new Error('missing channels property: ' + model); + } + if (!('labels' in convert[model])) { + throw new Error('missing channel labels property: ' + model); + } + if (convert[model].labels.length !== convert[model].channels) { + throw new Error('channel and label counts mismatch: ' + model); + } -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"](); })); }; + var channels = convert[model].channels; + var labels = convert[model].labels; + delete convert[model].channels; + delete convert[model].labels; + Object.defineProperty(convert[model], 'channels', {value: channels}); + Object.defineProperty(convert[model], 'labels', {value: labels}); + } } -//# sourceMappingURL=last.js.map +convert.rgb.hsl = function (rgb) { + var r = rgb[0] / 255; + var g = rgb[1] / 255; + var b = rgb[2] / 255; + var min = Math.min(r, g, b); + var max = Math.max(r, g, b); + var delta = max - min; + var h; + var s; + var l; -/***/ }), -/* 447 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (max === min) { + h = 0; + } else if (r === max) { + h = (g - b) / delta; + } else if (g === max) { + h = 2 + (b - r) / delta; + } else if (b === max) { + h = 4 + (r - g) / delta; + } -"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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(62); -/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(43); -/** PURE_IMPORTS_START tslib,_Subscriber,_util_ArgumentOutOfRangeError,_observable_empty PURE_IMPORTS_END */ + h = Math.min(h * 60, 360); + if (h < 0) { + h += 360; + } + l = (min + max) / 2; + if (max === min) { + s = 0; + } else if (l <= 0.5) { + s = delta / (max + min); + } else { + s = delta / (2 - max - min); + } -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 + return [h, s * 100, l * 100]; +}; + +convert.rgb.hsv = function (rgb) { + var rdif; + var gdif; + var bdif; + var h; + var s; + + var r = rgb[0] / 255; + var g = rgb[1] / 255; + var b = rgb[2] / 255; + var v = Math.max(r, g, b); + var diff = v - Math.min(r, g, b); + var diffc = function (c) { + return (v - c) / 6 / diff + 1 / 2; + }; + + if (diff === 0) { + h = s = 0; + } else { + s = diff / v; + rdif = diffc(r); + gdif = diffc(g); + bdif = diffc(b); + if (r === v) { + h = bdif - gdif; + } else if (g === v) { + h = (1 / 3) + rdif - bdif; + } else if (b === v) { + h = (2 / 3) + gdif - rdif; + } + if (h < 0) { + h += 1; + } else if (h > 1) { + h -= 1; + } + } -/***/ }), -/* 448 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + return [ + h * 360, + s * 100, + v * 100 + ]; +}; -"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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ +convert.rgb.hwb = function (rgb) { + var r = rgb[0]; + var g = rgb[1]; + var b = rgb[2]; + var h = convert.rgb.hsl(rgb)[0]; + var w = 1 / 255 * Math.min(r, Math.min(g, b)); + b = 1 - 1 / 255 * Math.max(r, Math.max(g, b)); -function mapTo(value) { - return function (source) { return source.lift(new MapToOperator(value)); }; + return [h, w * 100, b * 100]; +}; + +convert.rgb.cmyk = function (rgb) { + var r = rgb[0] / 255; + var g = rgb[1] / 255; + var b = rgb[2] / 255; + var c; + var m; + var y; + var k; + + k = Math.min(1 - r, 1 - g, 1 - b); + c = (1 - r - k) / (1 - k) || 0; + m = (1 - g - k) / (1 - k) || 0; + y = (1 - b - k) / (1 - k) || 0; + + return [c * 100, m * 100, y * 100, k * 100]; +}; + +/** + * See https://en.m.wikipedia.org/wiki/Euclidean_distance#Squared_Euclidean_distance + * */ +function comparativeDistance(x, y) { + return ( + Math.pow(x[0] - y[0], 2) + + Math.pow(x[1] - y[1], 2) + + Math.pow(x[2] - y[2], 2) + ); } -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 +convert.rgb.keyword = function (rgb) { + var reversed = reverseKeywords[rgb]; + if (reversed) { + return reversed; + } -/***/ }), -/* 449 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + var currentClosestDistance = Infinity; + var currentClosestKeyword; -"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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/* harmony import */ var _Notification__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(42); -/** PURE_IMPORTS_START tslib,_Subscriber,_Notification PURE_IMPORTS_END */ + for (var keyword in cssKeywords) { + if (cssKeywords.hasOwnProperty(keyword)) { + var value = cssKeywords[keyword]; + // Compute comparative distance + var distance = comparativeDistance(rgb, value); + // Check if its less, if so set as closest + if (distance < currentClosestDistance) { + currentClosestDistance = distance; + currentClosestKeyword = keyword; + } + } + } -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 + return currentClosestKeyword; +}; +convert.keyword.rgb = function (keyword) { + return cssKeywords[keyword]; +}; -/***/ }), -/* 450 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +convert.rgb.xyz = function (rgb) { + var r = rgb[0] / 255; + var g = rgb[1] / 255; + var b = rgb[2] / 255; -"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__(451); -/** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ + // assume sRGB + r = r > 0.04045 ? Math.pow(((r + 0.055) / 1.055), 2.4) : (r / 12.92); + g = g > 0.04045 ? Math.pow(((g + 0.055) / 1.055), 2.4) : (g / 12.92); + b = b > 0.04045 ? Math.pow(((b + 0.055) / 1.055), 2.4) : (b / 12.92); -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 + var x = (r * 0.4124) + (g * 0.3576) + (b * 0.1805); + var y = (r * 0.2126) + (g * 0.7152) + (b * 0.0722); + var z = (r * 0.0193) + (g * 0.1192) + (b * 0.9505); + return [x * 100, y * 100, z * 100]; +}; -/***/ }), -/* 451 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +convert.rgb.lab = function (rgb) { + var xyz = convert.rgb.xyz(rgb); + var x = xyz[0]; + var y = xyz[1]; + var z = xyz[2]; + var l; + var a; + var b; -"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__(452); -/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(447); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(424); -/* harmony import */ var _util_pipe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(24); -/** PURE_IMPORTS_START _scan,_takeLast,_defaultIfEmpty,_util_pipe PURE_IMPORTS_END */ + x /= 95.047; + y /= 100; + z /= 108.883; + x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116); + y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116); + z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116); + l = (116 * y) - 16; + a = 500 * (x - y); + b = 200 * (y - z); + return [l, a, b]; +}; -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 +convert.hsl.rgb = function (hsl) { + var h = hsl[0] / 360; + var s = hsl[1] / 100; + var l = hsl[2] / 100; + var t1; + var t2; + var t3; + var rgb; + var val; + if (s === 0) { + val = l * 255; + return [val, val, val]; + } -/***/ }), -/* 452 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (l < 0.5) { + t2 = l * (1 + s); + } else { + t2 = l + s - l * s; + } -"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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + t1 = 2 * l - t2; + + rgb = [0, 0, 0]; + for (var i = 0; i < 3; i++) { + t3 = h + 1 / 3 * -(i - 1); + if (t3 < 0) { + t3++; + } + if (t3 > 1) { + t3--; + } + if (6 * t3 < 1) { + val = t1 + (t2 - t1) * 6 * t3; + } else if (2 * t3 < 1) { + val = t2; + } else if (3 * t3 < 2) { + val = t1 + (t2 - t1) * (2 / 3 - t3) * 6; + } else { + val = t1; + } -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 + rgb[i] = val * 255; + } + return rgb; +}; -/***/ }), -/* 453 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +convert.hsl.hsv = function (hsl) { + var h = hsl[0]; + var s = hsl[1] / 100; + var l = hsl[2] / 100; + var smin = s; + var lmin = Math.max(l, 0.01); + var sv; + var v; -"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__(99); -/** PURE_IMPORTS_START _observable_merge PURE_IMPORTS_END */ + l *= 2; + s *= (l <= 1) ? l : 2 - l; + smin *= lmin <= 1 ? lmin : 2 - lmin; + v = (l + s) / 2; + sv = l === 0 ? (2 * smin) / (lmin + smin) : (2 * s) / (l + s); -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 [h, sv * 100, v * 100]; +}; +convert.hsv.rgb = function (hsv) { + var h = hsv[0] / 60; + var s = hsv[1] / 100; + var v = hsv[2] / 100; + var hi = Math.floor(h) % 6; -/***/ }), -/* 454 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + var f = h - Math.floor(h); + var p = 255 * v * (1 - s); + var q = 255 * v * (1 - (s * f)); + var t = 255 * v * (1 - (s * (1 - f))); + v *= 255; -"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__(82); -/** PURE_IMPORTS_START _mergeMap PURE_IMPORTS_END */ + switch (hi) { + case 0: + return [v, t, p]; + case 1: + return [q, v, p]; + case 2: + return [p, v, t]; + case 3: + return [p, q, v]; + case 4: + return [t, p, v]; + case 5: + return [v, p, q]; + } +}; -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 +convert.hsv.hsl = function (hsv) { + var h = hsv[0]; + var s = hsv[1] / 100; + var v = hsv[2] / 100; + var vmin = Math.max(v, 0.01); + var lmin; + var sl; + var l; + l = (2 - s) * v; + lmin = (2 - s) * vmin; + sl = s * vmin; + sl /= (lmin <= 1) ? lmin : 2 - lmin; + sl = sl || 0; + l /= 2; -/***/ }), -/* 455 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + return [h, sl * 100, l * 100]; +}; -"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__(12); -/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(90); -/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */ +// http://dev.w3.org/csswg/css-color/#hwb-to-rgb +convert.hwb.rgb = function (hwb) { + var h = hwb[0] / 360; + var wh = hwb[1] / 100; + var bl = hwb[2] / 100; + var ratio = wh + bl; + var i; + var v; + var f; + var n; + // wh + bl cant be > 1 + if (ratio > 1) { + wh /= ratio; + bl /= ratio; + } -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; -}()); + i = Math.floor(6 * h); + v = 1 - bl; + f = 6 * h - i; + + if ((i & 0x01) !== 0) { + f = 1 - f; + } + + n = wh + f * (v - wh); // linear interpolation + + var r; + var g; + var b; + switch (i) { + default: + case 6: + case 0: r = v; g = n; b = wh; break; + case 1: r = n; g = v; b = wh; break; + case 2: r = wh; g = v; b = n; break; + case 3: r = wh; g = n; b = v; break; + case 4: r = n; g = wh; b = v; break; + case 5: r = v; g = wh; b = n; break; + } + + return [r * 255, g * 255, b * 255]; +}; + +convert.cmyk.rgb = function (cmyk) { + var c = cmyk[0] / 100; + var m = cmyk[1] / 100; + var y = cmyk[2] / 100; + var k = cmyk[3] / 100; + var r; + var g; + var b; -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); - } - else { - this.buffer.push(value); - } - }; - MergeScanSubscriber.prototype._innerSub = function (ish) { - var innerSubscriber = new _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleInnerSubscriber"](this); - var destination = this.destination; - destination.add(innerSubscriber); - var innerSubscription = Object(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["innerSubscribe"])(ish, innerSubscriber); - if (innerSubscription !== innerSubscriber) { - destination.add(innerSubscription); - } - }; - 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 (innerValue) { - var destination = this.destination; - this.acc = innerValue; - this.hasValue = true; - destination.next(innerValue); - }; - MergeScanSubscriber.prototype.notifyComplete = function () { - var buffer = this.buffer; - 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; -}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleOuterSubscriber"])); + r = 1 - Math.min(1, c * (1 - k) + k); + g = 1 - Math.min(1, m * (1 - k) + k); + b = 1 - Math.min(1, y * (1 - k) + k); -//# sourceMappingURL=mergeScan.js.map + return [r * 255, g * 255, b * 255]; +}; +convert.xyz.rgb = function (xyz) { + var x = xyz[0] / 100; + var y = xyz[1] / 100; + var z = xyz[2] / 100; + var r; + var g; + var b; -/***/ }), -/* 456 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + r = (x * 3.2406) + (y * -1.5372) + (z * -0.4986); + g = (x * -0.9689) + (y * 1.8758) + (z * 0.0415); + b = (x * 0.0557) + (y * -0.2040) + (z * 1.0570); -"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__(451); -/** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ + // assume sRGB + r = r > 0.0031308 + ? ((1.055 * Math.pow(r, 1.0 / 2.4)) - 0.055) + : r * 12.92; -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 + g = g > 0.0031308 + ? ((1.055 * Math.pow(g, 1.0 / 2.4)) - 0.055) + : g * 12.92; + b = b > 0.0031308 + ? ((1.055 * Math.pow(b, 1.0 / 2.4)) - 0.055) + : b * 12.92; -/***/ }), -/* 457 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + r = Math.min(Math.max(0, r), 1); + g = Math.min(Math.max(0, g), 1); + b = Math.min(Math.max(0, b), 1); -"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__(26); -/** PURE_IMPORTS_START _observable_ConnectableObservable PURE_IMPORTS_END */ + return [r * 255, g * 255, b * 255]; +}; -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; -}()); +convert.xyz.lab = function (xyz) { + var x = xyz[0]; + var y = xyz[1]; + var z = xyz[2]; + var l; + var a; + var b; -//# sourceMappingURL=multicast.js.map + x /= 95.047; + y /= 100; + z /= 108.883; + x = x > 0.008856 ? Math.pow(x, 1 / 3) : (7.787 * x) + (16 / 116); + y = y > 0.008856 ? Math.pow(y, 1 / 3) : (7.787 * y) + (16 / 116); + z = z > 0.008856 ? Math.pow(z, 1 / 3) : (7.787 * z) + (16 / 116); -/***/ }), -/* 458 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + l = (116 * y) - 16; + a = 500 * (x - y); + b = 200 * (y - z); -"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__(12); -/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(83); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(18); -/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(90); -/** PURE_IMPORTS_START tslib,_observable_from,_util_isArray,_innerSubscribe PURE_IMPORTS_END */ + return [l, a, b]; +}; +convert.lab.xyz = function (lab) { + var l = lab[0]; + var a = lab[1]; + var b = lab[2]; + var x; + var y; + var z; + y = (l + 16) / 116; + x = a / 500 + y; + z = y - b / 200; + var y2 = Math.pow(y, 3); + var x2 = Math.pow(x, 3); + var z2 = Math.pow(z, 3); + y = y2 > 0.008856 ? y2 : (y - 16 / 116) / 7.787; + x = x2 > 0.008856 ? x2 : (x - 16 / 116) / 7.787; + z = z2 > 0.008856 ? z2 : (z - 16 / 116) / 7.787; -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 = undefined; - 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).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 () { - this.subscribeToNextSource(); - }; - OnErrorResumeNextSubscriber.prototype.notifyComplete = function () { - 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 _innerSubscribe__WEBPACK_IMPORTED_MODULE_3__["SimpleInnerSubscriber"](this); - var destination = this.destination; - destination.add(innerSubscriber); - var innerSubscription = Object(_innerSubscribe__WEBPACK_IMPORTED_MODULE_3__["innerSubscribe"])(next, innerSubscriber); - if (innerSubscription !== innerSubscriber) { - destination.add(innerSubscription); - } - } - else { - this.destination.complete(); - } - }; - return OnErrorResumeNextSubscriber; -}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_3__["SimpleOuterSubscriber"])); -//# sourceMappingURL=onErrorResumeNext.js.map + x *= 95.047; + y *= 100; + z *= 108.883; + return [x, y, z]; +}; -/***/ }), -/* 459 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +convert.lab.lch = function (lab) { + var l = lab[0]; + var a = lab[1]; + var b = lab[2]; + var hr; + var h; + var c; -"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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + hr = Math.atan2(b, a); + h = hr * 360 / 2 / Math.PI; + if (h < 0) { + h += 360; + } -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 + c = Math.sqrt(a * a + b * b); + return [l, c, h]; +}; -/***/ }), -/* 460 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +convert.lch.lab = function (lch) { + var l = lch[0]; + var c = lch[1]; + var h = lch[2]; + var a; + var b; + var hr; -"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__(104); -/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(105); -/** PURE_IMPORTS_START _util_not,_filter PURE_IMPORTS_END */ + hr = h / 360 * 2 * Math.PI; + a = c * Math.cos(hr); + b = c * Math.sin(hr); + return [l, a, b]; +}; -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 +convert.rgb.ansi16 = function (args) { + var r = args[0]; + var g = args[1]; + var b = args[2]; + var value = 1 in arguments ? arguments[1] : convert.rgb.hsv(args)[2]; // hsv -> ansi16 optimization + value = Math.round(value / 50); -/***/ }), -/* 461 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (value === 0) { + return 30; + } -"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__(66); -/** PURE_IMPORTS_START _map PURE_IMPORTS_END */ + var ansi = 30 + + ((Math.round(b / 255) << 2) + | (Math.round(g / 255) << 1) + | Math.round(r / 255)); -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 != null ? currentProp[props[i]] : undefined; - if (p !== void 0) { - currentProp = p; - } - else { - return undefined; - } - } - return currentProp; - }; - return mapper; -} -//# sourceMappingURL=pluck.js.map + if (value === 2) { + ansi += 60; + } + return ansi; +}; -/***/ }), -/* 462 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +convert.hsv.ansi16 = function (args) { + // optimization here; we already know the value and don't need to get + // it converted for us. + return convert.rgb.ansi16(convert.hsv.rgb(args), args[2]); +}; -"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__(27); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(457); -/** PURE_IMPORTS_START _Subject,_multicast PURE_IMPORTS_END */ +convert.rgb.ansi256 = function (args) { + var r = args[0]; + var g = args[1]; + var b = args[2]; + // we use the extended greyscale palette here, with the exception of + // black and white. normal palette only has 4 greyscale shades. + if (r === g && g === b) { + if (r < 8) { + return 16; + } -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 + if (r > 248) { + return 231; + } + return Math.round(((r - 8) / 247) * 24) + 232; + } -/***/ }), -/* 463 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + var ansi = 16 + + (36 * Math.round(r / 255 * 5)) + + (6 * Math.round(g / 255 * 5)) + + Math.round(b / 255 * 5); -"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__(32); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(457); -/** PURE_IMPORTS_START _BehaviorSubject,_multicast PURE_IMPORTS_END */ + return ansi; +}; +convert.ansi16.rgb = function (args) { + var color = args % 10; -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 + // handle greyscale + if (color === 0 || color === 7) { + if (args > 50) { + color += 3.5; + } + color = color / 10.5 * 255; -/***/ }), -/* 464 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + return [color, color, color]; + } -"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__(50); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(457); -/** PURE_IMPORTS_START _AsyncSubject,_multicast PURE_IMPORTS_END */ + var mult = (~~(args > 50) + 1) * 0.5; + var r = ((color & 1) * mult) * 255; + var g = (((color >> 1) & 1) * mult) * 255; + var b = (((color >> 2) & 1) * mult) * 255; + return [r, g, b]; +}; -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 +convert.ansi256.rgb = function (args) { + // handle greyscale + if (args >= 232) { + var c = (args - 232) * 10 + 8; + return [c, c, c]; + } + args -= 16; -/***/ }), -/* 465 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + var rem; + var r = Math.floor(args / 36) / 5 * 255; + var g = Math.floor((rem = args % 36) / 6) / 5 * 255; + var b = (rem % 6) / 5 * 255; -"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__(33); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(457); -/** PURE_IMPORTS_START _ReplaySubject,_multicast PURE_IMPORTS_END */ + return [r, g, b]; +}; +convert.rgb.hex = function (args) { + var integer = ((Math.round(args[0]) & 0xFF) << 16) + + ((Math.round(args[1]) & 0xFF) << 8) + + (Math.round(args[2]) & 0xFF); -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 + var string = integer.toString(16).toUpperCase(); + return '000000'.substring(string.length) + string; +}; +convert.hex.rgb = function (args) { + var match = args.toString(16).match(/[a-f0-9]{6}|[a-f0-9]{3}/i); + if (!match) { + return [0, 0, 0]; + } -/***/ }), -/* 466 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + var colorString = match[0]; -"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__(18); -/* harmony import */ var _observable_race__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(106); -/** PURE_IMPORTS_START _util_isArray,_observable_race PURE_IMPORTS_END */ + if (match[0].length === 3) { + colorString = colorString.split('').map(function (char) { + return char + char; + }).join(''); + } + var integer = parseInt(colorString, 16); + var r = (integer >> 16) & 0xFF; + var g = (integer >> 8) & 0xFF; + var b = integer & 0xFF; -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 + return [r, g, b]; +}; +convert.rgb.hcg = function (rgb) { + var r = rgb[0] / 255; + var g = rgb[1] / 255; + var b = rgb[2] / 255; + var max = Math.max(Math.max(r, g), b); + var min = Math.min(Math.min(r, g), b); + var chroma = (max - min); + var grayscale; + var hue; -/***/ }), -/* 467 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (chroma < 1) { + grayscale = min / (1 - chroma); + } else { + grayscale = 0; + } -"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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(43); -/** PURE_IMPORTS_START tslib,_Subscriber,_observable_empty PURE_IMPORTS_END */ + if (chroma <= 0) { + hue = 0; + } else + if (max === r) { + hue = ((g - b) / chroma) % 6; + } else + if (max === g) { + hue = 2 + (b - r) / chroma; + } else { + hue = 4 + (r - g) / chroma + 4; + } + hue /= 6; + hue %= 1; + return [hue * 360, chroma * 100, grayscale * 100]; +}; -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 +convert.hsl.hcg = function (hsl) { + var s = hsl[1] / 100; + var l = hsl[2] / 100; + var c = 1; + var f = 0; + if (l < 0.5) { + c = 2.0 * s * l; + } else { + c = 2.0 * s * (1.0 - l); + } -/***/ }), -/* 468 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (c < 1.0) { + f = (l - 0.5 * c) / (1.0 - c); + } -"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__(12); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(27); -/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(90); -/** PURE_IMPORTS_START tslib,_Subject,_innerSubscribe PURE_IMPORTS_END */ + return [hsl[0], c * 100, f * 100]; +}; +convert.hsv.hcg = function (hsv) { + var s = hsv[1] / 100; + var v = hsv[2] / 100; + var c = s * v; + var f = 0; -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 () { - this.sourceIsBeingSubscribedTo = true; - this.source.subscribe(this); - }; - RepeatWhenSubscriber.prototype.notifyComplete = function () { - 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(undefined); - } - }; - RepeatWhenSubscriber.prototype._unsubscribe = function () { - var _a = this, notifications = _a.notifications, retriesSubscription = _a.retriesSubscription; - if (notifications) { - notifications.unsubscribe(); - this.notifications = undefined; - } - if (retriesSubscription) { - retriesSubscription.unsubscribe(); - this.retriesSubscription = undefined; - } - this.retries = undefined; - }; - 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(_innerSubscribe__WEBPACK_IMPORTED_MODULE_2__["innerSubscribe"])(retries, new _innerSubscribe__WEBPACK_IMPORTED_MODULE_2__["SimpleInnerSubscriber"](this)); - }; - return RepeatWhenSubscriber; -}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_2__["SimpleOuterSubscriber"])); -//# sourceMappingURL=repeatWhen.js.map + if (c < 1.0) { + f = (v - c) / (1 - c); + } + return [hsv[0], c * 100, f * 100]; +}; -/***/ }), -/* 469 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +convert.hcg.rgb = function (hcg) { + var h = hcg[0] / 360; + var c = hcg[1] / 100; + var g = hcg[2] / 100; -"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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + if (c === 0.0) { + return [g * 255, g * 255, g * 255]; + } + var pure = [0, 0, 0]; + var hi = (h % 1) * 6; + var v = hi % 1; + var w = 1 - v; + var mg = 0; -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 + switch (Math.floor(hi)) { + case 0: + pure[0] = 1; pure[1] = v; pure[2] = 0; break; + case 1: + pure[0] = w; pure[1] = 1; pure[2] = 0; break; + case 2: + pure[0] = 0; pure[1] = 1; pure[2] = v; break; + case 3: + pure[0] = 0; pure[1] = w; pure[2] = 1; break; + case 4: + pure[0] = v; pure[1] = 0; pure[2] = 1; break; + default: + pure[0] = 1; pure[1] = 0; pure[2] = w; + } + mg = (1.0 - c) * g; -/***/ }), -/* 470 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + return [ + (c * pure[0] + mg) * 255, + (c * pure[1] + mg) * 255, + (c * pure[2] + mg) * 255 + ]; +}; -"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__(12); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(27); -/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(90); -/** PURE_IMPORTS_START tslib,_Subject,_innerSubscribe PURE_IMPORTS_END */ +convert.hcg.hsv = function (hcg) { + var c = hcg[1] / 100; + var g = hcg[2] / 100; + var v = c + g * (1.0 - c); + var f = 0; + if (v > 0.0) { + f = c / v; + } -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(_innerSubscribe__WEBPACK_IMPORTED_MODULE_2__["innerSubscribe"])(retries, new _innerSubscribe__WEBPACK_IMPORTED_MODULE_2__["SimpleInnerSubscriber"](this)); - } - else { - this.errors = undefined; - this.retriesSubscription = undefined; - } - 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 = undefined; - } - if (retriesSubscription) { - retriesSubscription.unsubscribe(); - this.retriesSubscription = undefined; - } - this.retries = undefined; - }; - RetryWhenSubscriber.prototype.notifyNext = function () { - var _unsubscribe = this._unsubscribe; - this._unsubscribe = null; - this._unsubscribeAndRecycle(); - this._unsubscribe = _unsubscribe; - this.source.subscribe(this); - }; - return RetryWhenSubscriber; -}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_2__["SimpleOuterSubscriber"])); -//# sourceMappingURL=retryWhen.js.map + return [hcg[0], f * 100, v * 100]; +}; +convert.hcg.hsl = function (hcg) { + var c = hcg[1] / 100; + var g = hcg[2] / 100; -/***/ }), -/* 471 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + var l = g * (1.0 - c) + 0.5 * c; + var s = 0; -"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__(12); -/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(90); -/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */ + if (l > 0.0 && l < 0.5) { + s = c / (2 * l); + } else + if (l >= 0.5 && l < 1.0) { + s = c / (2 * (1 - l)); + } + return [hcg[0], s * 100, l * 100]; +}; -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(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["innerSubscribe"])(this.notifier, new _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleInnerSubscriber"](sampleSubscriber))); - 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 () { - 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; -}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleOuterSubscriber"])); -//# sourceMappingURL=sample.js.map +convert.hcg.hwb = function (hcg) { + var c = hcg[1] / 100; + var g = hcg[2] / 100; + var v = c + g * (1.0 - c); + return [hcg[0], (v - c) * 100, (1 - v) * 100]; +}; +convert.hwb.hcg = function (hwb) { + var w = hwb[1] / 100; + var b = hwb[2] / 100; + var v = 1 - b; + var c = v - w; + var g = 0; -/***/ }), -/* 472 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (c < 1) { + g = (v - c) / (1 - c); + } -"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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(55); -/** PURE_IMPORTS_START tslib,_Subscriber,_scheduler_async PURE_IMPORTS_END */ + return [hwb[0], c * 100, g * 100]; +}; +convert.apple.rgb = function (apple) { + return [(apple[0] / 65535) * 255, (apple[1] / 65535) * 255, (apple[2] / 65535) * 255]; +}; +convert.rgb.apple = function (rgb) { + return [(rgb[0] / 255) * 65535, (rgb[1] / 255) * 65535, (rgb[2] / 255) * 65535]; +}; -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 +convert.gray.rgb = function (args) { + return [args[0] / 100 * 255, args[0] / 100 * 255, args[0] / 100 * 255]; +}; +convert.gray.hsl = convert.gray.hsv = function (args) { + return [0, 0, args[0]]; +}; -/***/ }), -/* 473 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +convert.gray.hwb = function (gray) { + return [0, 100, gray[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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ +convert.gray.cmyk = function (gray) { + return [0, 0, 0, gray[0]]; +}; +convert.gray.lab = function (gray) { + return [gray[0], 0, 0]; +}; -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; -}()); +convert.gray.hex = function (gray) { + var val = Math.round(gray[0] / 100 * 255) & 0xFF; + var integer = (val << 16) + (val << 8) + val; -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 string = integer.toString(16).toUpperCase(); + return '000000'.substring(string.length) + string; +}; -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 +convert.rgb.gray = function (rgb) { + var val = (rgb[0] + rgb[1] + rgb[2]) / 3; + return [val / 255 * 100]; +}; /***/ }), -/* 474 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 495 */ +/***/ (function(module, 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__(457); -/* harmony import */ var _refCount__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(30); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(27); -/** PURE_IMPORTS_START _multicast,_refCount,_Subject PURE_IMPORTS_END */ + + +module.exports = { + "aliceblue": [240, 248, 255], + "antiquewhite": [250, 235, 215], + "aqua": [0, 255, 255], + "aquamarine": [127, 255, 212], + "azure": [240, 255, 255], + "beige": [245, 245, 220], + "bisque": [255, 228, 196], + "black": [0, 0, 0], + "blanchedalmond": [255, 235, 205], + "blue": [0, 0, 255], + "blueviolet": [138, 43, 226], + "brown": [165, 42, 42], + "burlywood": [222, 184, 135], + "cadetblue": [95, 158, 160], + "chartreuse": [127, 255, 0], + "chocolate": [210, 105, 30], + "coral": [255, 127, 80], + "cornflowerblue": [100, 149, 237], + "cornsilk": [255, 248, 220], + "crimson": [220, 20, 60], + "cyan": [0, 255, 255], + "darkblue": [0, 0, 139], + "darkcyan": [0, 139, 139], + "darkgoldenrod": [184, 134, 11], + "darkgray": [169, 169, 169], + "darkgreen": [0, 100, 0], + "darkgrey": [169, 169, 169], + "darkkhaki": [189, 183, 107], + "darkmagenta": [139, 0, 139], + "darkolivegreen": [85, 107, 47], + "darkorange": [255, 140, 0], + "darkorchid": [153, 50, 204], + "darkred": [139, 0, 0], + "darksalmon": [233, 150, 122], + "darkseagreen": [143, 188, 143], + "darkslateblue": [72, 61, 139], + "darkslategray": [47, 79, 79], + "darkslategrey": [47, 79, 79], + "darkturquoise": [0, 206, 209], + "darkviolet": [148, 0, 211], + "deeppink": [255, 20, 147], + "deepskyblue": [0, 191, 255], + "dimgray": [105, 105, 105], + "dimgrey": [105, 105, 105], + "dodgerblue": [30, 144, 255], + "firebrick": [178, 34, 34], + "floralwhite": [255, 250, 240], + "forestgreen": [34, 139, 34], + "fuchsia": [255, 0, 255], + "gainsboro": [220, 220, 220], + "ghostwhite": [248, 248, 255], + "gold": [255, 215, 0], + "goldenrod": [218, 165, 32], + "gray": [128, 128, 128], + "green": [0, 128, 0], + "greenyellow": [173, 255, 47], + "grey": [128, 128, 128], + "honeydew": [240, 255, 240], + "hotpink": [255, 105, 180], + "indianred": [205, 92, 92], + "indigo": [75, 0, 130], + "ivory": [255, 255, 240], + "khaki": [240, 230, 140], + "lavender": [230, 230, 250], + "lavenderblush": [255, 240, 245], + "lawngreen": [124, 252, 0], + "lemonchiffon": [255, 250, 205], + "lightblue": [173, 216, 230], + "lightcoral": [240, 128, 128], + "lightcyan": [224, 255, 255], + "lightgoldenrodyellow": [250, 250, 210], + "lightgray": [211, 211, 211], + "lightgreen": [144, 238, 144], + "lightgrey": [211, 211, 211], + "lightpink": [255, 182, 193], + "lightsalmon": [255, 160, 122], + "lightseagreen": [32, 178, 170], + "lightskyblue": [135, 206, 250], + "lightslategray": [119, 136, 153], + "lightslategrey": [119, 136, 153], + "lightsteelblue": [176, 196, 222], + "lightyellow": [255, 255, 224], + "lime": [0, 255, 0], + "limegreen": [50, 205, 50], + "linen": [250, 240, 230], + "magenta": [255, 0, 255], + "maroon": [128, 0, 0], + "mediumaquamarine": [102, 205, 170], + "mediumblue": [0, 0, 205], + "mediumorchid": [186, 85, 211], + "mediumpurple": [147, 112, 219], + "mediumseagreen": [60, 179, 113], + "mediumslateblue": [123, 104, 238], + "mediumspringgreen": [0, 250, 154], + "mediumturquoise": [72, 209, 204], + "mediumvioletred": [199, 21, 133], + "midnightblue": [25, 25, 112], + "mintcream": [245, 255, 250], + "mistyrose": [255, 228, 225], + "moccasin": [255, 228, 181], + "navajowhite": [255, 222, 173], + "navy": [0, 0, 128], + "oldlace": [253, 245, 230], + "olive": [128, 128, 0], + "olivedrab": [107, 142, 35], + "orange": [255, 165, 0], + "orangered": [255, 69, 0], + "orchid": [218, 112, 214], + "palegoldenrod": [238, 232, 170], + "palegreen": [152, 251, 152], + "paleturquoise": [175, 238, 238], + "palevioletred": [219, 112, 147], + "papayawhip": [255, 239, 213], + "peachpuff": [255, 218, 185], + "peru": [205, 133, 63], + "pink": [255, 192, 203], + "plum": [221, 160, 221], + "powderblue": [176, 224, 230], + "purple": [128, 0, 128], + "rebeccapurple": [102, 51, 153], + "red": [255, 0, 0], + "rosybrown": [188, 143, 143], + "royalblue": [65, 105, 225], + "saddlebrown": [139, 69, 19], + "salmon": [250, 128, 114], + "sandybrown": [244, 164, 96], + "seagreen": [46, 139, 87], + "seashell": [255, 245, 238], + "sienna": [160, 82, 45], + "silver": [192, 192, 192], + "skyblue": [135, 206, 235], + "slateblue": [106, 90, 205], + "slategray": [112, 128, 144], + "slategrey": [112, 128, 144], + "snow": [255, 250, 250], + "springgreen": [0, 255, 127], + "steelblue": [70, 130, 180], + "tan": [210, 180, 140], + "teal": [0, 128, 128], + "thistle": [216, 191, 216], + "tomato": [255, 99, 71], + "turquoise": [64, 224, 208], + "violet": [238, 130, 238], + "wheat": [245, 222, 179], + "white": [255, 255, 255], + "whitesmoke": [245, 245, 245], + "yellow": [255, 255, 0], + "yellowgreen": [154, 205, 50] +}; + +/***/ }), +/* 496 */ +/***/ (function(module, exports, __webpack_require__) { +var conversions = __webpack_require__(494); -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 +/* + this function routes a model to all other models. + all functions that are routed have a property `.conversion` attached + to the returned synthetic function. This property is an array + of strings, each with the steps in between the 'from' and 'to' + color models (inclusive). -/***/ }), -/* 475 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + conversions that are not possible simply are not included. +*/ -"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__(33); -/** PURE_IMPORTS_START _ReplaySubject PURE_IMPORTS_END */ +function buildGraph() { + var graph = {}; + // https://jsperf.com/object-keys-vs-for-in-with-closure/3 + var models = Object.keys(conversions); -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++; - var innerSub; - if (!subject || hasError) { - hasError = false; - subject = new _ReplaySubject__WEBPACK_IMPORTED_MODULE_0__["ReplaySubject"](bufferSize, windowTime, scheduler); - innerSub = subject.subscribe(this); - subscription = source.subscribe({ - next: function (value) { subject.next(value); }, - error: function (err) { - hasError = true; - subject.error(err); - }, - complete: function () { - isComplete = true; - subscription = undefined; - subject.complete(); - }, - }); - } - else { - innerSub = subject.subscribe(this); - } - this.add(function () { - refCount--; - innerSub.unsubscribe(); - if (subscription && !isComplete && useRefCount && refCount === 0) { - subscription.unsubscribe(); - subscription = undefined; - subject = undefined; - } - }); - }; + for (var len = models.length, i = 0; i < len; i++) { + graph[models[i]] = { + // http://jsperf.com/1-vs-infinity + // micro-opt, but this is simple. + distance: -1, + parent: null + }; + } + + return graph; } -//# sourceMappingURL=shareReplay.js.map +// https://en.wikipedia.org/wiki/Breadth-first_search +function deriveBFS(fromModel) { + var graph = buildGraph(); + var queue = [fromModel]; // unshift -> queue -> pop -/***/ }), -/* 476 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + graph[fromModel].distance = 0; -"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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(63); -/** PURE_IMPORTS_START tslib,_Subscriber,_util_EmptyError PURE_IMPORTS_END */ + while (queue.length) { + var current = queue.pop(); + var adjacents = Object.keys(conversions[current]); + + for (var len = adjacents.length, i = 0; i < len; i++) { + var adjacent = adjacents[i]; + var node = graph[adjacent]; + + if (node.distance === -1) { + node.distance = graph[current].distance + 1; + node.parent = current; + queue.unshift(adjacent); + } + } + } + + return graph; +} + +function link(from, to) { + return function (args) { + return to(from(args)); + }; +} +function wrapConversion(toModel, graph) { + var path = [graph[toModel].parent, toModel]; + var fn = conversions[graph[toModel].parent][toModel]; + var cur = graph[toModel].parent; + while (graph[cur].parent) { + path.unshift(graph[cur].parent); + fn = link(conversions[graph[cur].parent][cur], fn); + cur = graph[cur].parent; + } -function single(predicate) { - return function (source) { return source.lift(new SingleOperator(predicate, source)); }; + fn.conversion = path; + return fn; } -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 +module.exports = function (fromModel) { + var graph = deriveBFS(fromModel); + var conversion = {}; -/***/ }), -/* 477 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + var models = Object.keys(graph); + for (var len = models.length, i = 0; i < len; i++) { + var toModel = models[i]; + var node = graph[toModel]; -"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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + if (node.parent === null) { + // no possible conversion, or this node is the source model. + continue; + } + conversion[toModel] = wrapConversion(toModel, graph); + } + + return conversion; +}; -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 /***/ }), -/* 478 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 497 */ +/***/ (function(module, 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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(62); -/** PURE_IMPORTS_START tslib,_Subscriber,_util_ArgumentOutOfRangeError PURE_IMPORTS_END */ +const os = __webpack_require__(121); +const hasFlag = __webpack_require__(498); +const env = process.env; -function skipLast(count) { - return function (source) { return source.lift(new SkipLastOperator(count)); }; +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 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 +function translateLevel(level) { + if (level === 0) { + return false; + } -/***/ }), -/* 479 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + return { + level, + hasBasic: true, + has256: level >= 2, + has16m: level >= 3 + }; +} -"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__(12); -/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(90); -/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */ +function supportsColor(stream) { + if (forceColor === false) { + return 0; + } + if (hasFlag('color=16m') || + hasFlag('color=full') || + hasFlag('color=truecolor')) { + return 3; + } -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 _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleInnerSubscriber"](_this); - _this.add(innerSubscriber); - _this.innerSubscription = innerSubscriber; - var innerSubscription = Object(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["innerSubscribe"])(notifier, innerSubscriber); - if (innerSubscription !== innerSubscriber) { - _this.add(innerSubscription); - _this.innerSubscription = innerSubscription; - } - return _this; - } - SkipUntilSubscriber.prototype._next = function (value) { - if (this.hasValue) { - _super.prototype._next.call(this, value); - } - }; - SkipUntilSubscriber.prototype.notifyNext = function () { - this.hasValue = true; - if (this.innerSubscription) { - this.innerSubscription.unsubscribe(); - } - }; - SkipUntilSubscriber.prototype.notifyComplete = function () { - }; - return SkipUntilSubscriber; -}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleOuterSubscriber"])); -//# sourceMappingURL=skipUntil.js.map + if (hasFlag('color=256')) { + return 2; + } + if (stream && !stream.isTTY && forceColor !== true) { + return 0; + } -/***/ }), -/* 480 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + const min = forceColor ? 1 : 0; -"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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + 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; + } -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 + if ('CI' in env) { + if (['TRAVIS', 'CIRCLECI', 'APPVEYOR', 'GITLAB_CI'].some(sign => sign in env) || env.CI_NAME === 'codeship') { + return 1; + } + return min; + } -/***/ }), -/* 481 */ -/***/ (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__, "startWith", function() { return startWith; }); -/* harmony import */ var _observable_concat__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(79); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(45); -/** PURE_IMPORTS_START _observable_concat,_util_isScheduler PURE_IMPORTS_END */ + if (env.COLORTERM === 'truecolor') { + return 3; + } + if ('TERM_PROGRAM' in env) { + const version = parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10); -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 + 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; + } -/***/ }), -/* 482 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) { + return 1; + } -"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__(483); -/** PURE_IMPORTS_START _observable_SubscribeOnObservable PURE_IMPORTS_END */ + if ('COLORTERM' in env) { + return 1; + } -function subscribeOn(scheduler, delay) { - if (delay === void 0) { - delay = 0; - } - return function subscribeOnOperatorFunction(source) { - return source.lift(new SubscribeOnOperator(scheduler, delay)); - }; + if (env.TERM === 'dumb') { + return min; + } + + return min; } -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 +function getSupportLevel(stream) { + const level = supportsColor(stream); + return translateLevel(level); +} -/***/ }), -/* 483 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +module.exports = { + supportsColor: getSupportLevel, + stdout: getSupportLevel(process.stdout), + stderr: getSupportLevel(process.stderr) +}; -"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__(12); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(9); -/* harmony import */ var _scheduler_asap__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(51); -/* harmony import */ var _util_isNumeric__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(98); -/** PURE_IMPORTS_START tslib,_Observable,_scheduler_asap,_util_isNumeric PURE_IMPORTS_END */ +/***/ }), +/* 498 */ +/***/ (function(module, exports, __webpack_require__) { +"use strict"; +module.exports = (flag, argv) => { + argv = argv || process.argv; + const prefix = flag.startsWith('-') ? '' : (flag.length === 1 ? '-' : '--'); + const pos = argv.indexOf(prefix + flag); + const terminatorPos = argv.indexOf('--'); + return pos !== -1 && (terminatorPos === -1 ? true : pos < terminatorPos); +}; -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 +/***/ }), +/* 499 */ +/***/ (function(module, exports, __webpack_require__) { +"use strict"; -/***/ }), -/* 484 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +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; -"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__(485); -/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(25); -/** PURE_IMPORTS_START _switchMap,_util_identity PURE_IMPORTS_END */ +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)); + } -function switchAll() { - return Object(_switchMap__WEBPACK_IMPORTED_MODULE_0__["switchMap"])(_util_identity__WEBPACK_IMPORTED_MODULE_1__["identity"]); + return ESCAPES.get(c) || c; } -//# sourceMappingURL=switchAll.js.map +function parseArguments(name, args) { + const results = []; + const chunks = args.trim().split(/\s*,\s*/g); + let matches; -/***/ }), -/* 485 */ -/***/ (function(module, __webpack_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}')`); + } + } -"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__(12); -/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(66); -/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(83); -/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(90); -/** PURE_IMPORTS_START tslib,_map,_observable_from,_innerSubscribe PURE_IMPORTS_END */ + return results; +} +function parseStyle(style) { + STYLE_REGEX.lastIndex = 0; + const results = []; + let matches; + while ((matches = STYLE_REGEX.exec(style)) !== null) { + const name = matches[1]; -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_2__["from"])(project(a, i)).pipe(Object(_map__WEBPACK_IMPORTED_MODULE_1__["map"])(function (b, ii) { return resultSelector(a, b, i, ii); })); })); }; - } - return function (source) { return source.lift(new SwitchMapOperator(project)); }; + if (matches[2]) { + const args = parseArguments(name, matches[2]); + results.push([name].concat(args)); + } else { + results.push([name]); + } + } + + return results; } -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); - }; - SwitchMapSubscriber.prototype._innerSub = function (result) { - var innerSubscription = this.innerSubscription; - if (innerSubscription) { - innerSubscription.unsubscribe(); - } - var innerSubscriber = new _innerSubscribe__WEBPACK_IMPORTED_MODULE_3__["SimpleInnerSubscriber"](this); - var destination = this.destination; - destination.add(innerSubscriber); - this.innerSubscription = Object(_innerSubscribe__WEBPACK_IMPORTED_MODULE_3__["innerSubscribe"])(result, innerSubscriber); - if (this.innerSubscription !== innerSubscriber) { - destination.add(this.innerSubscription); - } - }; - 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 = undefined; - }; - SwitchMapSubscriber.prototype.notifyComplete = function () { - this.innerSubscription = undefined; - if (this.isStopped) { - _super.prototype._complete.call(this); - } - }; - SwitchMapSubscriber.prototype.notifyNext = function (innerValue) { - this.destination.next(innerValue); - }; - return SwitchMapSubscriber; -}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_3__["SimpleOuterSubscriber"])); -//# sourceMappingURL=switchMap.js.map +function buildStyle(chalk, styles) { + const enabled = {}; -/***/ }), -/* 486 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + for (const layer of styles) { + for (const style of layer.styles) { + enabled[style[0]] = layer.inverse ? null : style.slice(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__(485); -/** PURE_IMPORTS_START _switchMap PURE_IMPORTS_END */ + 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}`); + } -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; }); + if (enabled[styleName].length > 0) { + current = current[styleName].apply(current, enabled[styleName]); + } else { + current = current[styleName]; + } + } + } + + return current; } -//# sourceMappingURL=switchMapTo.js.map +module.exports = (chalk, tmp) => { + const styles = []; + const chunks = []; + let chunk = []; -/***/ }), -/* 487 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + // 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'); + } -"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__(12); -/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(90); -/** PURE_IMPORTS_START tslib,_innerSubscribe PURE_IMPORTS_END */ + chunks.push(buildStyle(chalk, styles)(chunk.join(''))); + chunk = []; + styles.pop(); + } else { + chunk.push(chr); + } + }); + chunks.push(chunk.join('')); -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(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["innerSubscribe"])(this.notifier, new _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleInnerSubscriber"](takeUntilSubscriber)); - 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 () { - this.seenValue = true; - this.complete(); - }; - TakeUntilSubscriber.prototype.notifyComplete = function () { - }; - return TakeUntilSubscriber; -}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleOuterSubscriber"])); -//# sourceMappingURL=takeUntil.js.map + 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(''); +}; /***/ }), -/* 488 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 500 */ +/***/ (function(module, 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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ +const ansiRegex = __webpack_require__(501); -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 +module.exports = string => typeof string === 'string' ? string.replace(ansiRegex(), '') : string; /***/ }), -/* 489 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 501 */ +/***/ (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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/* harmony import */ var _util_noop__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(60); -/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(13); -/** PURE_IMPORTS_START tslib,_Subscriber,_util_noop,_util_isFunction PURE_IMPORTS_END */ - +module.exports = ({onlyFirst = false} = {}) => { + const pattern = [ + '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)', + '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))' + ].join('|'); -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 + return new RegExp(pattern, onlyFirst ? undefined : 'g'); +}; /***/ }), -/* 490 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 502 */ +/***/ (function(module, 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__(12); -/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(90); -/** PURE_IMPORTS_START tslib,_innerSubscribe 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 defaults = __webpack_require__(503) +var combining = __webpack_require__(505) + +var DEFAULTS = { + nul: 0, + control: 0 } -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 = undefined; - }; - ThrottleSubscriber.prototype.throttle = function (value) { - var duration = this.tryDurationSelector(value); - if (!!duration) { - this.add(this._throttled = Object(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["innerSubscribe"])(duration, new _innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleInnerSubscriber"](this))); - } - }; - 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 = undefined; - if (_trailing) { - this.send(); - } - }; - ThrottleSubscriber.prototype.notifyNext = function () { - this.throttlingDone(); - }; - ThrottleSubscriber.prototype.notifyComplete = function () { - this.throttlingDone(); - }; - return ThrottleSubscriber; -}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_1__["SimpleOuterSubscriber"])); -//# sourceMappingURL=throttle.js.map +module.exports = function wcwidth(str) { + return wcswidth(str, DEFAULTS) +} + +module.exports.config = function(opts) { + opts = defaults(opts || {}, DEFAULTS) + return function wcwidth(str) { + return wcswidth(str, opts) + } +} + +/* + * The following functions define the column width of an ISO 10646 + * character as follows: + * - The null character (U+0000) has a column width of 0. + * - Other C0/C1 control characters and DEL will lead to a return value + * of -1. + * - Non-spacing and enclosing combining characters (general category + * code Mn or Me in the + * Unicode database) have a column width of 0. + * - SOFT HYPHEN (U+00AD) has a column width of 1. + * - Other format characters (general category code Cf in the Unicode + * database) and ZERO WIDTH + * SPACE (U+200B) have a column width of 0. + * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF) + * have a column width of 0. + * - Spacing characters in the East Asian Wide (W) or East Asian + * Full-width (F) category as + * defined in Unicode Technical Report #11 have a column width of 2. + * - All remaining characters (including all printable ISO 8859-1 and + * WGL4 characters, Unicode control characters, etc.) have a column + * width of 1. + * This implementation assumes that characters are encoded in ISO 10646. +*/ + +function wcswidth(str, opts) { + if (typeof str !== 'string') return wcwidth(str, opts) -/***/ }), -/* 491 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + var s = 0 + for (var i = 0; i < str.length; i++) { + var n = wcwidth(str.charCodeAt(i), opts) + if (n < 0) return -1 + s += n + } -"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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(55); -/* harmony import */ var _throttle__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(490); -/** PURE_IMPORTS_START tslib,_Subscriber,_scheduler_async,_throttle PURE_IMPORTS_END */ + return s +} +function wcwidth(ucs, opts) { + // test for 8-bit control characters + if (ucs === 0) return opts.nul + if (ucs < 32 || (ucs >= 0x7f && ucs < 0xa0)) return opts.control + // binary search in table of non-spacing characters + if (bisearch(ucs)) return 0 + // if we arrive here, ucs is not a combining or C0/C1 control character + return 1 + + (ucs >= 0x1100 && + (ucs <= 0x115f || // Hangul Jamo init. consonants + ucs == 0x2329 || ucs == 0x232a || + (ucs >= 0x2e80 && ucs <= 0xa4cf && + ucs != 0x303f) || // CJK ... Yi + (ucs >= 0xac00 && ucs <= 0xd7a3) || // Hangul Syllables + (ucs >= 0xf900 && ucs <= 0xfaff) || // CJK Compatibility Ideographs + (ucs >= 0xfe10 && ucs <= 0xfe19) || // Vertical forms + (ucs >= 0xfe30 && ucs <= 0xfe6f) || // CJK Compatibility Forms + (ucs >= 0xff00 && ucs <= 0xff60) || // Fullwidth Forms + (ucs >= 0xffe0 && ucs <= 0xffe6) || + (ucs >= 0x20000 && ucs <= 0x2fffd) || + (ucs >= 0x30000 && ucs <= 0x3fffd))); +} -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)); }; +function bisearch(ucs) { + var min = 0 + var max = combining.length - 1 + var mid + + if (ucs < combining[0][0] || ucs > combining[max][1]) return false + + while (max >= min) { + mid = Math.floor((min + max) / 2) + if (ucs > combining[mid][1]) min = mid + 1 + else if (ucs < combining[mid][0]) max = mid - 1 + else return true + } + + return false } -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; + + +/***/ }), +/* 503 */ +/***/ (function(module, exports, __webpack_require__) { + +var clone = __webpack_require__(504); + +module.exports = function(options, defaults) { + options = options || {}; + + Object.keys(defaults).forEach(function(key) { + if (typeof options[key] === 'undefined') { + options[key] = clone(defaults[key]); } - 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 + }); + return options; +}; /***/ }), -/* 492 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 504 */ +/***/ (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__(55); -/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(452); -/* harmony import */ var _observable_defer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(91); -/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(66); -/** PURE_IMPORTS_START _scheduler_async,_scan,_observable_defer,_map PURE_IMPORTS_END */ +var clone = (function() { +'use strict'; + +/** + * Clones (copies) an Object using deep copying. + * + * This function supports circular references by default, but if you are certain + * there are no circular references in your object, you can save some CPU time + * by calling clone(obj, false). + * + * Caution: if `circular` is false and `parent` contains circular references, + * your program may enter an infinite loop and crash. + * + * @param `parent` - the object to be cloned + * @param `circular` - set to true if the object to be cloned may contain + * circular references. (optional - true by default) + * @param `depth` - set to a number if the object is only to be cloned to + * a particular depth. (optional - defaults to Infinity) + * @param `prototype` - sets the prototype to be used when cloning an object. + * (optional - defaults to parent prototype). +*/ +function clone(parent, circular, depth, prototype) { + var filter; + if (typeof circular === 'object') { + depth = circular.depth; + prototype = circular.prototype; + filter = circular.filter; + circular = circular.circular + } + // maintain two arrays for circular references, where corresponding parents + // and children have the same index + var allParents = []; + var allChildren = []; + var useBuffer = typeof Buffer != 'undefined'; + if (typeof circular == 'undefined') + circular = true; + if (typeof depth == 'undefined') + depth = Infinity; -function timeInterval(scheduler) { - if (scheduler === void 0) { - scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_0__["async"]; + // recurse this function so we don't reset allParents and allChildren + function _clone(parent, depth) { + // cloning null always returns null + if (parent === null) + return null; + + if (depth == 0) + return parent; + + var child; + var proto; + if (typeof parent != 'object') { + return parent; } - 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; + + if (clone.__isArray(parent)) { + child = []; + } else if (clone.__isRegExp(parent)) { + child = new RegExp(parent.source, __getRegExpFlags(parent)); + if (parent.lastIndex) child.lastIndex = parent.lastIndex; + } else if (clone.__isDate(parent)) { + child = new Date(parent.getTime()); + } else if (useBuffer && Buffer.isBuffer(parent)) { + if (Buffer.allocUnsafe) { + // Node.js >= 4.5.0 + child = Buffer.allocUnsafe(parent.length); + } else { + // Older Node.js versions + child = new Buffer(parent.length); + } + parent.copy(child); + return child; + } else { + if (typeof prototype == 'undefined') { + proto = Object.getPrototypeOf(parent); + child = Object.create(proto); + } + else { + child = Object.create(prototype); + proto = prototype; + } } - return TimeInterval; -}()); -//# sourceMappingURL=timeInterval.js.map + if (circular) { + var index = allParents.indexOf(parent); + + if (index != -1) { + return allChildren[index]; + } + allParents.push(parent); + allChildren.push(child); + } + for (var i in parent) { + var attrs; + if (proto) { + attrs = Object.getOwnPropertyDescriptor(proto, i); + } -/***/ }), -/* 493 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (attrs && attrs.set == null) { + continue; + } + child[i] = _clone(parent[i], depth - 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__(55); -/* harmony import */ var _util_TimeoutError__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(64); -/* harmony import */ var _timeoutWith__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(494); -/* harmony import */ var _observable_throwError__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(49); -/** PURE_IMPORTS_START _scheduler_async,_util_TimeoutError,_timeoutWith,_observable_throwError PURE_IMPORTS_END */ + return child; + } + + return _clone(parent, depth); +} + +/** + * Simple flat clone using prototype, accepts only objects, usefull for property + * override on FLAT configuration object (no nested props). + * + * USE WITH CAUTION! This may not behave as you wish if you do not know how this + * works. + */ +clone.clonePrototype = function clonePrototype(parent) { + if (parent === null) + return null; + + var c = function () {}; + c.prototype = parent; + return new c(); +}; +// private utility functions + +function __objToStr(o) { + return Object.prototype.toString.call(o); +}; +clone.__objToStr = __objToStr; +function __isDate(o) { + return typeof o === 'object' && __objToStr(o) === '[object Date]'; +}; +clone.__isDate = __isDate; +function __isArray(o) { + return typeof o === 'object' && __objToStr(o) === '[object Array]'; +}; +clone.__isArray = __isArray; -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); +function __isRegExp(o) { + return typeof o === 'object' && __objToStr(o) === '[object RegExp]'; +}; +clone.__isRegExp = __isRegExp; + +function __getRegExpFlags(re) { + var flags = ''; + if (re.global) flags += 'g'; + if (re.ignoreCase) flags += 'i'; + if (re.multiline) flags += 'm'; + return flags; +}; +clone.__getRegExpFlags = __getRegExpFlags; + +return clone; +})(); + +if ( true && module.exports) { + module.exports = clone; } -//# sourceMappingURL=timeout.js.map /***/ }), -/* 494 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 505 */ +/***/ (function(module, exports) { + +module.exports = [ + [ 0x0300, 0x036F ], [ 0x0483, 0x0486 ], [ 0x0488, 0x0489 ], + [ 0x0591, 0x05BD ], [ 0x05BF, 0x05BF ], [ 0x05C1, 0x05C2 ], + [ 0x05C4, 0x05C5 ], [ 0x05C7, 0x05C7 ], [ 0x0600, 0x0603 ], + [ 0x0610, 0x0615 ], [ 0x064B, 0x065E ], [ 0x0670, 0x0670 ], + [ 0x06D6, 0x06E4 ], [ 0x06E7, 0x06E8 ], [ 0x06EA, 0x06ED ], + [ 0x070F, 0x070F ], [ 0x0711, 0x0711 ], [ 0x0730, 0x074A ], + [ 0x07A6, 0x07B0 ], [ 0x07EB, 0x07F3 ], [ 0x0901, 0x0902 ], + [ 0x093C, 0x093C ], [ 0x0941, 0x0948 ], [ 0x094D, 0x094D ], + [ 0x0951, 0x0954 ], [ 0x0962, 0x0963 ], [ 0x0981, 0x0981 ], + [ 0x09BC, 0x09BC ], [ 0x09C1, 0x09C4 ], [ 0x09CD, 0x09CD ], + [ 0x09E2, 0x09E3 ], [ 0x0A01, 0x0A02 ], [ 0x0A3C, 0x0A3C ], + [ 0x0A41, 0x0A42 ], [ 0x0A47, 0x0A48 ], [ 0x0A4B, 0x0A4D ], + [ 0x0A70, 0x0A71 ], [ 0x0A81, 0x0A82 ], [ 0x0ABC, 0x0ABC ], + [ 0x0AC1, 0x0AC5 ], [ 0x0AC7, 0x0AC8 ], [ 0x0ACD, 0x0ACD ], + [ 0x0AE2, 0x0AE3 ], [ 0x0B01, 0x0B01 ], [ 0x0B3C, 0x0B3C ], + [ 0x0B3F, 0x0B3F ], [ 0x0B41, 0x0B43 ], [ 0x0B4D, 0x0B4D ], + [ 0x0B56, 0x0B56 ], [ 0x0B82, 0x0B82 ], [ 0x0BC0, 0x0BC0 ], + [ 0x0BCD, 0x0BCD ], [ 0x0C3E, 0x0C40 ], [ 0x0C46, 0x0C48 ], + [ 0x0C4A, 0x0C4D ], [ 0x0C55, 0x0C56 ], [ 0x0CBC, 0x0CBC ], + [ 0x0CBF, 0x0CBF ], [ 0x0CC6, 0x0CC6 ], [ 0x0CCC, 0x0CCD ], + [ 0x0CE2, 0x0CE3 ], [ 0x0D41, 0x0D43 ], [ 0x0D4D, 0x0D4D ], + [ 0x0DCA, 0x0DCA ], [ 0x0DD2, 0x0DD4 ], [ 0x0DD6, 0x0DD6 ], + [ 0x0E31, 0x0E31 ], [ 0x0E34, 0x0E3A ], [ 0x0E47, 0x0E4E ], + [ 0x0EB1, 0x0EB1 ], [ 0x0EB4, 0x0EB9 ], [ 0x0EBB, 0x0EBC ], + [ 0x0EC8, 0x0ECD ], [ 0x0F18, 0x0F19 ], [ 0x0F35, 0x0F35 ], + [ 0x0F37, 0x0F37 ], [ 0x0F39, 0x0F39 ], [ 0x0F71, 0x0F7E ], + [ 0x0F80, 0x0F84 ], [ 0x0F86, 0x0F87 ], [ 0x0F90, 0x0F97 ], + [ 0x0F99, 0x0FBC ], [ 0x0FC6, 0x0FC6 ], [ 0x102D, 0x1030 ], + [ 0x1032, 0x1032 ], [ 0x1036, 0x1037 ], [ 0x1039, 0x1039 ], + [ 0x1058, 0x1059 ], [ 0x1160, 0x11FF ], [ 0x135F, 0x135F ], + [ 0x1712, 0x1714 ], [ 0x1732, 0x1734 ], [ 0x1752, 0x1753 ], + [ 0x1772, 0x1773 ], [ 0x17B4, 0x17B5 ], [ 0x17B7, 0x17BD ], + [ 0x17C6, 0x17C6 ], [ 0x17C9, 0x17D3 ], [ 0x17DD, 0x17DD ], + [ 0x180B, 0x180D ], [ 0x18A9, 0x18A9 ], [ 0x1920, 0x1922 ], + [ 0x1927, 0x1928 ], [ 0x1932, 0x1932 ], [ 0x1939, 0x193B ], + [ 0x1A17, 0x1A18 ], [ 0x1B00, 0x1B03 ], [ 0x1B34, 0x1B34 ], + [ 0x1B36, 0x1B3A ], [ 0x1B3C, 0x1B3C ], [ 0x1B42, 0x1B42 ], + [ 0x1B6B, 0x1B73 ], [ 0x1DC0, 0x1DCA ], [ 0x1DFE, 0x1DFF ], + [ 0x200B, 0x200F ], [ 0x202A, 0x202E ], [ 0x2060, 0x2063 ], + [ 0x206A, 0x206F ], [ 0x20D0, 0x20EF ], [ 0x302A, 0x302F ], + [ 0x3099, 0x309A ], [ 0xA806, 0xA806 ], [ 0xA80B, 0xA80B ], + [ 0xA825, 0xA826 ], [ 0xFB1E, 0xFB1E ], [ 0xFE00, 0xFE0F ], + [ 0xFE20, 0xFE23 ], [ 0xFEFF, 0xFEFF ], [ 0xFFF9, 0xFFFB ], + [ 0x10A01, 0x10A03 ], [ 0x10A05, 0x10A06 ], [ 0x10A0C, 0x10A0F ], + [ 0x10A38, 0x10A3A ], [ 0x10A3F, 0x10A3F ], [ 0x1D167, 0x1D169 ], + [ 0x1D173, 0x1D182 ], [ 0x1D185, 0x1D18B ], [ 0x1D1AA, 0x1D1AD ], + [ 0x1D242, 0x1D244 ], [ 0xE0001, 0xE0001 ], [ 0xE0020, 0xE007F ], + [ 0xE0100, 0xE01EF ] +] + + +/***/ }), +/* 506 */ +/***/ (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__(12); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(55); -/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(426); -/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(90); -/** PURE_IMPORTS_START tslib,_scheduler_async,_util_isDate,_innerSubscribe PURE_IMPORTS_END */ +module.exports = ({stream = process.stdout} = {}) => { + return Boolean( + stream && stream.isTTY && + process.env.TERM !== 'dumb' && + !('CI' in process.env) + ); +}; -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.scheduleTimeout(); - return _this; - } - TimeoutWithSubscriber.dispatchTimeout = function (subscriber) { - var withObservable = subscriber.withObservable; - subscriber._unsubscribeAndRecycle(); - subscriber.add(Object(_innerSubscribe__WEBPACK_IMPORTED_MODULE_3__["innerSubscribe"])(withObservable, new _innerSubscribe__WEBPACK_IMPORTED_MODULE_3__["SimpleInnerSubscriber"](subscriber))); - }; - 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 = undefined; - this.scheduler = null; - this.withObservable = null; - }; - return TimeoutWithSubscriber; -}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_3__["SimpleOuterSubscriber"])); -//# sourceMappingURL=timeoutWith.js.map +/***/ }), +/* 507 */ +/***/ (function(module, exports, __webpack_require__) { + +var Stream = __webpack_require__(138) + +module.exports = MuteStream +// var out = new MuteStream(process.stdout) +// argument auto-pipes +function MuteStream (opts) { + Stream.apply(this) + opts = opts || {} + this.writable = this.readable = true + this.muted = false + this.on('pipe', this._onpipe) + this.replace = opts.replace -/***/ }), -/* 495 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + // For readline-type situations + // This much at the start of a line being redrawn after a ctrl char + // is seen (such as backspace) won't be redrawn as the replacement + this._prompt = opts.prompt || null + this._hadControl = false +} -"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__(55); -/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(66); -/** PURE_IMPORTS_START _scheduler_async,_map PURE_IMPORTS_END */ +MuteStream.prototype = Object.create(Stream.prototype) +Object.defineProperty(MuteStream.prototype, 'constructor', { + value: MuteStream, + enumerable: false +}) -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()); }); +MuteStream.prototype.mute = function () { + this.muted = true } -var Timestamp = /*@__PURE__*/ (function () { - function Timestamp(value, timestamp) { - this.value = value; - this.timestamp = timestamp; - } - return Timestamp; -}()); -//# sourceMappingURL=timestamp.js.map +MuteStream.prototype.unmute = function () { + this.muted = false +} +Object.defineProperty(MuteStream.prototype, '_onpipe', { + value: onPipe, + enumerable: false, + writable: true, + configurable: true +}) -/***/ }), -/* 496 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +function onPipe (src) { + this._src = src +} -"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__(451); -/** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ +Object.defineProperty(MuteStream.prototype, 'isTTY', { + get: getIsTTY, + set: setIsTTY, + enumerable: true, + configurable: true +}) -function toArrayReducer(arr, item, index) { - if (index === 0) { - return [item]; - } - arr.push(item); - return arr; +function getIsTTY () { + return( (this._dest) ? this._dest.isTTY + : (this._src) ? this._src.isTTY + : false + ) } -function toArray() { - return Object(_reduce__WEBPACK_IMPORTED_MODULE_0__["reduce"])(toArrayReducer, []); + +// basically just get replace the getter/setter with a regular value +function setIsTTY (isTTY) { + Object.defineProperty(this, 'isTTY', { + value: isTTY, + enumerable: true, + writable: true, + configurable: true + }) } -//# sourceMappingURL=toArray.js.map +Object.defineProperty(MuteStream.prototype, 'rows', { + get: function () { + return( this._dest ? this._dest.rows + : this._src ? this._src.rows + : undefined ) + }, enumerable: true, configurable: true }) -/***/ }), -/* 497 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +Object.defineProperty(MuteStream.prototype, 'columns', { + get: function () { + return( this._dest ? this._dest.columns + : this._src ? this._src.columns + : undefined ) + }, enumerable: true, configurable: true }) -"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__(12); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(27); -/* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(90); -/** PURE_IMPORTS_START tslib,_Subject,_innerSubscribe PURE_IMPORTS_END */ +MuteStream.prototype.pipe = function (dest, options) { + this._dest = dest + return Stream.prototype.pipe.call(this, dest, options) +} +MuteStream.prototype.pause = function () { + if (this._src) return this._src.pause() +} -function window(windowBoundaries) { - return function windowOperatorFunction(source) { - return source.lift(new WindowOperator(windowBoundaries)); - }; +MuteStream.prototype.resume = function () { + if (this._src) return this._src.resume() } -var WindowOperator = /*@__PURE__*/ (function () { - function WindowOperator(windowBoundaries) { - this.windowBoundaries = windowBoundaries; + +MuteStream.prototype.write = function (c) { + if (this.muted) { + if (!this.replace) return true + if (c.match(/^\u001b/)) { + if(c.indexOf(this._prompt) === 0) { + c = c.substr(this._prompt.length); + c = c.replace(/./g, this.replace); + c = this._prompt + c; + } + this._hadControl = true + return this.emit('data', c) + } else { + if (this._prompt && this._hadControl && + c.indexOf(this._prompt) === 0) { + this._hadControl = false + this.emit('data', this._prompt) + c = c.substr(this._prompt.length) + } + c = c.toString().replace(/./g, this.replace) } - WindowOperator.prototype.call = function (subscriber, source) { - var windowSubscriber = new WindowSubscriber(subscriber); - var sourceSubscription = source.subscribe(windowSubscriber); - if (!sourceSubscription.closed) { - windowSubscriber.add(Object(_innerSubscribe__WEBPACK_IMPORTED_MODULE_2__["innerSubscribe"])(this.windowBoundaries, new _innerSubscribe__WEBPACK_IMPORTED_MODULE_2__["SimpleInnerSubscriber"](windowSubscriber))); - } - 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; + } + this.emit('data', c) +} + +MuteStream.prototype.end = function (c) { + if (this.muted) { + if (c && this.replace) { + c = c.toString().replace(/./g, this.replace) + } else { + c = null } - WindowSubscriber.prototype.notifyNext = function () { - this.openWindow(); - }; - WindowSubscriber.prototype.notifyError = function (error) { - this._error(error); - }; - WindowSubscriber.prototype.notifyComplete = function () { - 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; -}(_innerSubscribe__WEBPACK_IMPORTED_MODULE_2__["SimpleOuterSubscriber"])); -//# sourceMappingURL=window.js.map + } + if (c) this.emit('data', c) + this.emit('end') +} + +function proxy (fn) { return function () { + var d = this._dest + var s = this._src + if (d && d[fn]) d[fn].apply(d, arguments) + if (s && s[fn]) s[fn].apply(s, arguments) +}} + +MuteStream.prototype.destroy = proxy('destroy') +MuteStream.prototype.destroySoon = proxy('destroySoon') +MuteStream.prototype.close = proxy('close') /***/ }), -/* 498 */ +/* 508 */ /***/ (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__(12); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(27); -/** PURE_IMPORTS_START tslib,_Subscriber,_Subject PURE_IMPORTS_END */ +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ResetCommand", function() { return ResetCommand; }); +/* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); +/* harmony import */ var dedent__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(dedent__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(143); +/* 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__(477); +/* 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__(4); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var _utils_bazel__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(371); +/* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(131); +/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(246); +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ -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 -/***/ }), -/* 499 */ -/***/ (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__(12); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(27); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(55); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(11); -/* harmony import */ var _util_isNumeric__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(98); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(45); -/** PURE_IMPORTS_START tslib,_Subject,_scheduler_async,_Subscriber,_util_isNumeric,_util_isScheduler PURE_IMPORTS_END */ +const ResetCommand = { + description: 'Deletes node_modules and output directories, resets internal and disk caches, and stops Bazel server', + name: 'reset', + async run(projects) { + _utils_log__WEBPACK_IMPORTED_MODULE_6__["log"].warning(dedent__WEBPACK_IMPORTED_MODULE_0___default.a` + In most cases, 'yarn kbn clean' is all that should be needed to recover a consistent state when + problems arise. If you need to use this command, please let us know, as it should not be necessary. + `); + const toDelete = []; + for (const project of projects.values()) { + if (await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_5__["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_5__["isDirectory"])(project.targetLocation)) { + toDelete.push({ + cwd: project.path, + pattern: Object(path__WEBPACK_IMPORTED_MODULE_3__["relative"])(project.path, project.targetLocation) + }); + } + const { + extraPatterns + } = project.getCleanConfig(); -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 = Number(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 = Number(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 + if (extraPatterns) { + toDelete.push({ + cwd: project.path, + pattern: extraPatterns + }); + } + } // Runs Bazel hard clean + + + await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_4__["runBazel"])(['clean', '--expunge']); + _utils_log__WEBPACK_IMPORTED_MODULE_6__["log"].success('Hard cleaned bazel'); // Deletes Bazel Cache Folders + + await del__WEBPACK_IMPORTED_MODULE_1___default()([await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_4__["getBazelDiskCacheFolder"])(), await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_4__["getBazelRepositoryCacheFolder"])()], { + force: 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); + _utils_log__WEBPACK_IMPORTED_MODULE_6__["log"].success('Removed disk caches'); + + if (toDelete.length === 0) { + return; } - 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); + /** + * 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 { + pattern, + cwd + } of toDelete) { + process.chdir(cwd); + const promise = del__WEBPACK_IMPORTED_MODULE_1___default()(pattern); + + if (_utils_log__WEBPACK_IMPORTED_MODULE_6__["log"].wouldLogLevel('info')) { + 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); } - subscriber.closeWindow(window); -} -//# sourceMappingURL=windowTime.js.map + } +}; /***/ }), -/* 500 */ +/* 509 */ /***/ (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__(12); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(27); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(17); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(69); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(70); -/** 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 _utils_errors__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(249); +/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(246); +/* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(247); +/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(248); +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const RunCommand = { + description: 'Run script defined in package.json in each package that contains that script.', + name: 'run', + async run(projects, projectGraph, { + extraArgs, + options + }) { + 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) { + throw new _utils_errors__WEBPACK_IMPORTED_MODULE_0__["CliError"]('No script specified'); } - 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; + + const scriptName = extraArgs[0]; + const scriptArgs = extraArgs.slice(1); + await Object(_utils_parallelize__WEBPACK_IMPORTED_MODULE_2__["parallelizeBatches"])(batchedProjects, async project => { + if (!project.hasScript(scriptName)) { + if (!!options['skip-missing']) { + 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 + throw new _utils_errors__WEBPACK_IMPORTED_MODULE_0__["CliError"](`[${project.name}] no "${scriptName}" script defined. To skip packages without the "${scriptName}" script pass --skip-missing`); + } + + _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].info(`[${project.name}] running "${scriptName}" script`); + await project.runScriptStreaming(scriptName, { + args: scriptArgs + }); + _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].success(`[${project.name}] complete`); + }); + } + +}; /***/ }), -/* 501 */ +/* 510 */ /***/ (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__(12); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(27); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(69); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(70); -/** 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 _utils_errors__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(249); +/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(246); +/* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(247); +/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(248); +/* harmony import */ var _utils_watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(511); +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ -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) { - 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 -/***/ }), -/* 502 */ -/***/ (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__(12); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(69); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(70); -/** 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) { + throw new _utils_errors__WEBPACK_IMPORTED_MODULE_0__["CliError"](`There are no projects to watch found. Make sure that projects define 'kbn:watch' script in 'package.json'.`); } - 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, undefined, i)); - } - return _this; + + const projectNames = Array.from(projectsToWatch.keys()); + _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].info(`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) { - 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, { + debug: false + }).stdout // TypeScript note: As long as the proc stdio[1] is 'pipe', then stdout will not be null + ); + _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].success(`[${pkg.name}] Initial build completed (${completionHint}).`); + }); + } + +}; /***/ }), -/* 503 */ +/* 511 */ /***/ (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__(110); -/** 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__(8); +/* harmony import */ var rxjs_operators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(375); +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ -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. + */ -/***/ }), -/* 504 */ -/***/ (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__(110); -/** 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(); +} /***/ }), -/* 505 */ +/* 512 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -58923,7 +59293,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(246); /* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(248); /* harmony import */ var _utils_projects_tree__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(370); -/* harmony import */ var _utils_kibana__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(506); +/* harmony import */ var _utils_kibana__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(513); 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(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } @@ -58994,7 +59364,7 @@ function toArray(value) { } /***/ }), -/* 506 */ +/* 513 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -59002,13 +59372,13 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Kibana", function() { return Kibana; }); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4); /* 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__(507); +/* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(514); /* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(multimatch__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var is_path_inside__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(239); /* harmony import */ var is_path_inside__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(is_path_inside__WEBPACK_IMPORTED_MODULE_2__); /* harmony import */ var _yarn_lock__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(365); /* harmony import */ var _projects__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(248); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(511); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(518); 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(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } @@ -59159,15 +59529,15 @@ class Kibana { } /***/ }), -/* 507 */ +/* 514 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const minimatch = __webpack_require__(150); -const arrayUnion = __webpack_require__(508); -const arrayDiffer = __webpack_require__(509); -const arrify = __webpack_require__(510); +const arrayUnion = __webpack_require__(515); +const arrayDiffer = __webpack_require__(516); +const arrify = __webpack_require__(517); module.exports = (list, patterns, options = {}) => { list = arrify(list); @@ -59191,7 +59561,7 @@ module.exports = (list, patterns, options = {}) => { /***/ }), -/* 508 */ +/* 515 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59203,7 +59573,7 @@ module.exports = (...arguments_) => { /***/ }), -/* 509 */ +/* 516 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59218,7 +59588,7 @@ module.exports = arrayDiffer; /***/ }), -/* 510 */ +/* 517 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59248,7 +59618,7 @@ module.exports = arrify; /***/ }), -/* 511 */ +/* 518 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -59307,12 +59677,12 @@ function getProjectPaths({ } /***/ }), -/* 512 */ +/* 519 */ /***/ (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__(513); +/* harmony import */ var _build_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(520); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _build_production_projects__WEBPACK_IMPORTED_MODULE_0__["buildProductionProjects"]; }); /* @@ -59325,19 +59695,19 @@ __webpack_require__.r(__webpack_exports__); /***/ }), -/* 513 */ +/* 520 */ /***/ (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__(514); +/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(521); /* 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__(143); /* 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__(4); /* 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__(511); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(518); /* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(131); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(246); /* harmony import */ var _utils_package_json__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(251); @@ -59463,7 +59833,7 @@ async function copyToBuild(project, kibanaRoot, buildRoot) { } /***/ }), -/* 514 */ +/* 521 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59471,14 +59841,14 @@ async function copyToBuild(project, kibanaRoot, buildRoot) { const EventEmitter = __webpack_require__(156); const path = __webpack_require__(4); const os = __webpack_require__(121); -const pMap = __webpack_require__(515); -const arrify = __webpack_require__(510); -const globby = __webpack_require__(516); -const hasGlob = __webpack_require__(712); -const cpFile = __webpack_require__(714); -const junk = __webpack_require__(724); -const pFilter = __webpack_require__(725); -const CpyError = __webpack_require__(727); +const pMap = __webpack_require__(522); +const arrify = __webpack_require__(517); +const globby = __webpack_require__(525); +const hasGlob = __webpack_require__(721); +const cpFile = __webpack_require__(723); +const junk = __webpack_require__(733); +const pFilter = __webpack_require__(734); +const CpyError = __webpack_require__(736); const defaultOptions = { ignoreJunk: true @@ -59629,12 +59999,12 @@ module.exports = (source, destination, { /***/ }), -/* 515 */ +/* 522 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const AggregateError = __webpack_require__(242); +const AggregateError = __webpack_require__(523); module.exports = async ( iterable, @@ -59717,17 +60087,113 @@ module.exports = async ( /***/ }), -/* 516 */ +/* 523 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const indentString = __webpack_require__(524); +const cleanStack = __webpack_require__(244); + +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}`); + } + + errors = [...errors].map(error => { + if (error instanceof Error) { + return error; + } + + if (error !== null && typeof error === 'object') { + // Handle plain error objects with message property and/or possibly other metadata + return Object.assign(new Error(error.message), error); + } + + return new Error(error); + }); + + let message = errors + .map(error => { + // The `stack` property is not standardized, so we can't assume it exists + return typeof error.stack === 'string' ? cleanInternalStack(cleanStack(error.stack)) : String(error); + }) + .join('\n'); + message = '\n' + indentString(message, 4); + super(message); + + this.name = 'AggregateError'; + + Object.defineProperty(this, '_errors', {value: errors}); + } + + * [Symbol.iterator]() { + for (const error of this._errors) { + yield error; + } + } +} + +module.exports = AggregateError; + + +/***/ }), +/* 524 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = (string, count = 1, options) => { + options = { + indent: ' ', + includeEmptyLines: false, + ...options + }; + + if (typeof string !== 'string') { + throw new TypeError( + `Expected \`input\` to be a \`string\`, got \`${typeof string}\`` + ); + } + + 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 string; + } + + const regex = options.includeEmptyLines ? /^/gm : /^(?!\s*$)/gm; + + return string.replace(regex, options.indent.repeat(count)); +}; + + +/***/ }), +/* 525 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); -const arrayUnion = __webpack_require__(517); +const arrayUnion = __webpack_require__(526); const glob = __webpack_require__(147); -const fastGlob = __webpack_require__(519); -const dirGlob = __webpack_require__(705); -const gitignore = __webpack_require__(708); +const fastGlob = __webpack_require__(528); +const dirGlob = __webpack_require__(714); +const gitignore = __webpack_require__(717); const DEFAULT_FILTER = () => false; @@ -59872,12 +60338,12 @@ module.exports.gitignore = gitignore; /***/ }), -/* 517 */ +/* 526 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var arrayUniq = __webpack_require__(518); +var arrayUniq = __webpack_require__(527); module.exports = function () { return arrayUniq([].concat.apply([], arguments)); @@ -59885,7 +60351,7 @@ module.exports = function () { /***/ }), -/* 518 */ +/* 527 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59954,10 +60420,10 @@ if ('Set' in global) { /***/ }), -/* 519 */ +/* 528 */ /***/ (function(module, exports, __webpack_require__) { -const pkg = __webpack_require__(520); +const pkg = __webpack_require__(529); module.exports = pkg.async; module.exports.default = pkg.async; @@ -59970,19 +60436,19 @@ module.exports.generateTasks = pkg.generateTasks; /***/ }), -/* 520 */ +/* 529 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var optionsManager = __webpack_require__(521); -var taskManager = __webpack_require__(522); -var reader_async_1 = __webpack_require__(676); -var reader_stream_1 = __webpack_require__(700); -var reader_sync_1 = __webpack_require__(701); -var arrayUtils = __webpack_require__(703); -var streamUtils = __webpack_require__(704); +var optionsManager = __webpack_require__(530); +var taskManager = __webpack_require__(531); +var reader_async_1 = __webpack_require__(685); +var reader_stream_1 = __webpack_require__(709); +var reader_sync_1 = __webpack_require__(710); +var arrayUtils = __webpack_require__(712); +var streamUtils = __webpack_require__(713); /** * Synchronous API. */ @@ -60048,7 +60514,7 @@ function isString(source) { /***/ }), -/* 521 */ +/* 530 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60086,13 +60552,13 @@ exports.prepare = prepare; /***/ }), -/* 522 */ +/* 531 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var patternUtils = __webpack_require__(523); +var patternUtils = __webpack_require__(532); /** * Generate tasks based on parent directory of each pattern. */ @@ -60183,16 +60649,16 @@ exports.convertPatternGroupToTask = convertPatternGroupToTask; /***/ }), -/* 523 */ +/* 532 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(4); -var globParent = __webpack_require__(524); +var globParent = __webpack_require__(533); var isGlob = __webpack_require__(172); -var micromatch = __webpack_require__(527); +var micromatch = __webpack_require__(536); var GLOBSTAR = '**'; /** * Return true for static pattern. @@ -60338,15 +60804,15 @@ exports.matchAny = matchAny; /***/ }), -/* 524 */ +/* 533 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var path = __webpack_require__(4); -var isglob = __webpack_require__(525); -var pathDirname = __webpack_require__(526); +var isglob = __webpack_require__(534); +var pathDirname = __webpack_require__(535); var isWin32 = __webpack_require__(121).platform() === 'win32'; module.exports = function globParent(str) { @@ -60369,7 +60835,7 @@ module.exports = function globParent(str) { /***/ }), -/* 525 */ +/* 534 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -60400,7 +60866,7 @@ module.exports = function isGlob(str) { /***/ }), -/* 526 */ +/* 535 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60550,7 +61016,7 @@ module.exports.win32 = win32; /***/ }), -/* 527 */ +/* 536 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60561,18 +61027,18 @@ module.exports.win32 = win32; */ var util = __webpack_require__(112); -var braces = __webpack_require__(528); -var toRegex = __webpack_require__(529); -var extend = __webpack_require__(642); +var braces = __webpack_require__(537); +var toRegex = __webpack_require__(538); +var extend = __webpack_require__(651); /** * Local dependencies */ -var compilers = __webpack_require__(644); -var parsers = __webpack_require__(671); -var cache = __webpack_require__(672); -var utils = __webpack_require__(673); +var compilers = __webpack_require__(653); +var parsers = __webpack_require__(680); +var cache = __webpack_require__(681); +var utils = __webpack_require__(682); var MAX_LENGTH = 1024 * 64; /** @@ -61434,7 +61900,7 @@ module.exports = micromatch; /***/ }), -/* 528 */ +/* 537 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61444,18 +61910,18 @@ module.exports = micromatch; * Module dependencies */ -var toRegex = __webpack_require__(529); -var unique = __webpack_require__(551); -var extend = __webpack_require__(552); +var toRegex = __webpack_require__(538); +var unique = __webpack_require__(560); +var extend = __webpack_require__(561); /** * Local dependencies */ -var compilers = __webpack_require__(554); -var parsers = __webpack_require__(567); -var Braces = __webpack_require__(571); -var utils = __webpack_require__(555); +var compilers = __webpack_require__(563); +var parsers = __webpack_require__(576); +var Braces = __webpack_require__(580); +var utils = __webpack_require__(564); var MAX_LENGTH = 1024 * 64; var cache = {}; @@ -61759,16 +62225,16 @@ module.exports = braces; /***/ }), -/* 529 */ +/* 538 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var safe = __webpack_require__(530); -var define = __webpack_require__(536); -var extend = __webpack_require__(544); -var not = __webpack_require__(548); +var safe = __webpack_require__(539); +var define = __webpack_require__(545); +var extend = __webpack_require__(553); +var not = __webpack_require__(557); var MAX_LENGTH = 1024 * 64; /** @@ -61921,10 +62387,10 @@ module.exports.makeRe = makeRe; /***/ }), -/* 530 */ +/* 539 */ /***/ (function(module, exports, __webpack_require__) { -var parse = __webpack_require__(531); +var parse = __webpack_require__(540); var types = parse.types; module.exports = function (re, opts) { @@ -61970,13 +62436,13 @@ function isRegExp (x) { /***/ }), -/* 531 */ +/* 540 */ /***/ (function(module, exports, __webpack_require__) { -var util = __webpack_require__(532); -var types = __webpack_require__(533); -var sets = __webpack_require__(534); -var positions = __webpack_require__(535); +var util = __webpack_require__(541); +var types = __webpack_require__(542); +var sets = __webpack_require__(543); +var positions = __webpack_require__(544); module.exports = function(regexpStr) { @@ -62258,11 +62724,11 @@ module.exports.types = types; /***/ }), -/* 532 */ +/* 541 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(533); -var sets = __webpack_require__(534); +var types = __webpack_require__(542); +var sets = __webpack_require__(543); // All of these are private and only used by randexp. @@ -62375,7 +62841,7 @@ exports.error = function(regexp, msg) { /***/ }), -/* 533 */ +/* 542 */ /***/ (function(module, exports) { module.exports = { @@ -62391,10 +62857,10 @@ module.exports = { /***/ }), -/* 534 */ +/* 543 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(533); +var types = __webpack_require__(542); var INTS = function() { return [{ type: types.RANGE , from: 48, to: 57 }]; @@ -62479,10 +62945,10 @@ exports.anyChar = function() { /***/ }), -/* 535 */ +/* 544 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(533); +var types = __webpack_require__(542); exports.wordBoundary = function() { return { type: types.POSITION, value: 'b' }; @@ -62502,7 +62968,7 @@ exports.end = function() { /***/ }), -/* 536 */ +/* 545 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62515,8 +62981,8 @@ exports.end = function() { -var isobject = __webpack_require__(537); -var isDescriptor = __webpack_require__(538); +var isobject = __webpack_require__(546); +var isDescriptor = __webpack_require__(547); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -62547,7 +63013,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 537 */ +/* 546 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62566,7 +63032,7 @@ module.exports = function isObject(val) { /***/ }), -/* 538 */ +/* 547 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62579,9 +63045,9 @@ module.exports = function isObject(val) { -var typeOf = __webpack_require__(539); -var isAccessor = __webpack_require__(540); -var isData = __webpack_require__(542); +var typeOf = __webpack_require__(548); +var isAccessor = __webpack_require__(549); +var isData = __webpack_require__(551); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -62595,7 +63061,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 539 */ +/* 548 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -62730,7 +63196,7 @@ function isBuffer(val) { /***/ }), -/* 540 */ +/* 549 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62743,7 +63209,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(541); +var typeOf = __webpack_require__(550); // accessor descriptor properties var accessor = { @@ -62806,7 +63272,7 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 541 */ +/* 550 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -62941,7 +63407,7 @@ function isBuffer(val) { /***/ }), -/* 542 */ +/* 551 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62954,7 +63420,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(543); +var typeOf = __webpack_require__(552); module.exports = function isDataDescriptor(obj, prop) { // data descriptor properties @@ -62997,7 +63463,7 @@ module.exports = function isDataDescriptor(obj, prop) { /***/ }), -/* 543 */ +/* 552 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -63132,14 +63598,14 @@ function isBuffer(val) { /***/ }), -/* 544 */ +/* 553 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(545); -var assignSymbols = __webpack_require__(547); +var isExtendable = __webpack_require__(554); +var assignSymbols = __webpack_require__(556); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -63199,7 +63665,7 @@ function isEnum(obj, key) { /***/ }), -/* 545 */ +/* 554 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63212,7 +63678,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(546); +var isPlainObject = __webpack_require__(555); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -63220,7 +63686,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 546 */ +/* 555 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63233,7 +63699,7 @@ module.exports = function isExtendable(val) { -var isObject = __webpack_require__(537); +var isObject = __webpack_require__(546); function isObjectObject(o) { return isObject(o) === true @@ -63264,7 +63730,7 @@ module.exports = function isPlainObject(o) { /***/ }), -/* 547 */ +/* 556 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63311,14 +63777,14 @@ module.exports = function(receiver, objects) { /***/ }), -/* 548 */ +/* 557 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(549); -var safe = __webpack_require__(530); +var extend = __webpack_require__(558); +var safe = __webpack_require__(539); /** * The main export is a function that takes a `pattern` string and an `options` object. @@ -63390,14 +63856,14 @@ module.exports = toRegex; /***/ }), -/* 549 */ +/* 558 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(550); -var assignSymbols = __webpack_require__(547); +var isExtendable = __webpack_require__(559); +var assignSymbols = __webpack_require__(556); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -63457,7 +63923,7 @@ function isEnum(obj, key) { /***/ }), -/* 550 */ +/* 559 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63470,7 +63936,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(546); +var isPlainObject = __webpack_require__(555); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -63478,7 +63944,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 551 */ +/* 560 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63528,13 +63994,13 @@ module.exports.immutable = function uniqueImmutable(arr) { /***/ }), -/* 552 */ +/* 561 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(553); +var isObject = __webpack_require__(562); module.exports = function extend(o/*, objects*/) { if (!isObject(o)) { o = {}; } @@ -63568,7 +64034,7 @@ function hasOwn(obj, key) { /***/ }), -/* 553 */ +/* 562 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63588,13 +64054,13 @@ module.exports = function isExtendable(val) { /***/ }), -/* 554 */ +/* 563 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(555); +var utils = __webpack_require__(564); module.exports = function(braces, options) { braces.compiler @@ -63877,25 +64343,25 @@ function hasQueue(node) { /***/ }), -/* 555 */ +/* 564 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var splitString = __webpack_require__(556); +var splitString = __webpack_require__(565); var utils = module.exports; /** * Module dependencies */ -utils.extend = __webpack_require__(552); -utils.flatten = __webpack_require__(559); -utils.isObject = __webpack_require__(537); -utils.fillRange = __webpack_require__(560); -utils.repeat = __webpack_require__(566); -utils.unique = __webpack_require__(551); +utils.extend = __webpack_require__(561); +utils.flatten = __webpack_require__(568); +utils.isObject = __webpack_require__(546); +utils.fillRange = __webpack_require__(569); +utils.repeat = __webpack_require__(575); +utils.unique = __webpack_require__(560); utils.define = function(obj, key, val) { Object.defineProperty(obj, key, { @@ -64227,7 +64693,7 @@ utils.escapeRegex = function(str) { /***/ }), -/* 556 */ +/* 565 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64240,7 +64706,7 @@ utils.escapeRegex = function(str) { -var extend = __webpack_require__(557); +var extend = __webpack_require__(566); module.exports = function(str, options, fn) { if (typeof str !== 'string') { @@ -64405,14 +64871,14 @@ function keepEscaping(opts, str, idx) { /***/ }), -/* 557 */ +/* 566 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(558); -var assignSymbols = __webpack_require__(547); +var isExtendable = __webpack_require__(567); +var assignSymbols = __webpack_require__(556); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -64472,7 +64938,7 @@ function isEnum(obj, key) { /***/ }), -/* 558 */ +/* 567 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64485,7 +64951,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(546); +var isPlainObject = __webpack_require__(555); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -64493,7 +64959,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 559 */ +/* 568 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64522,7 +64988,7 @@ function flat(arr, res) { /***/ }), -/* 560 */ +/* 569 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64536,10 +65002,10 @@ function flat(arr, res) { var util = __webpack_require__(112); -var isNumber = __webpack_require__(561); -var extend = __webpack_require__(552); -var repeat = __webpack_require__(564); -var toRegex = __webpack_require__(565); +var isNumber = __webpack_require__(570); +var extend = __webpack_require__(561); +var repeat = __webpack_require__(573); +var toRegex = __webpack_require__(574); /** * Return a range of numbers or letters. @@ -64737,7 +65203,7 @@ module.exports = fillRange; /***/ }), -/* 561 */ +/* 570 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64750,7 +65216,7 @@ module.exports = fillRange; -var typeOf = __webpack_require__(562); +var typeOf = __webpack_require__(571); module.exports = function isNumber(num) { var type = typeOf(num); @@ -64766,10 +65232,10 @@ module.exports = function isNumber(num) { /***/ }), -/* 562 */ +/* 571 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(563); +var isBuffer = __webpack_require__(572); var toString = Object.prototype.toString; /** @@ -64888,7 +65354,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 563 */ +/* 572 */ /***/ (function(module, exports) { /*! @@ -64915,7 +65381,7 @@ function isSlowBuffer (obj) { /***/ }), -/* 564 */ +/* 573 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64992,7 +65458,7 @@ function repeat(str, num) { /***/ }), -/* 565 */ +/* 574 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -65005,8 +65471,8 @@ function repeat(str, num) { -var repeat = __webpack_require__(564); -var isNumber = __webpack_require__(561); +var repeat = __webpack_require__(573); +var isNumber = __webpack_require__(570); var cache = {}; function toRegexRange(min, max, options) { @@ -65293,7 +65759,7 @@ module.exports = toRegexRange; /***/ }), -/* 566 */ +/* 575 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -65318,14 +65784,14 @@ module.exports = function repeat(ele, num) { /***/ }), -/* 567 */ +/* 576 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Node = __webpack_require__(568); -var utils = __webpack_require__(555); +var Node = __webpack_require__(577); +var utils = __webpack_require__(564); /** * Braces parsers @@ -65685,15 +66151,15 @@ function concatNodes(pos, node, parent, options) { /***/ }), -/* 568 */ +/* 577 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(537); -var define = __webpack_require__(569); -var utils = __webpack_require__(570); +var isObject = __webpack_require__(546); +var define = __webpack_require__(578); +var utils = __webpack_require__(579); var ownNames; /** @@ -66184,7 +66650,7 @@ exports = module.exports = Node; /***/ }), -/* 569 */ +/* 578 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -66197,7 +66663,7 @@ exports = module.exports = Node; -var isDescriptor = __webpack_require__(538); +var isDescriptor = __webpack_require__(547); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -66222,13 +66688,13 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 570 */ +/* 579 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(562); +var typeOf = __webpack_require__(571); var utils = module.exports; /** @@ -67248,17 +67714,17 @@ function assert(val, message) { /***/ }), -/* 571 */ +/* 580 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(552); -var Snapdragon = __webpack_require__(572); -var compilers = __webpack_require__(554); -var parsers = __webpack_require__(567); -var utils = __webpack_require__(555); +var extend = __webpack_require__(561); +var Snapdragon = __webpack_require__(581); +var compilers = __webpack_require__(563); +var parsers = __webpack_require__(576); +var utils = __webpack_require__(564); /** * Customize Snapdragon parser and renderer @@ -67359,17 +67825,17 @@ module.exports = Braces; /***/ }), -/* 572 */ +/* 581 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Base = __webpack_require__(573); -var define = __webpack_require__(600); -var Compiler = __webpack_require__(610); -var Parser = __webpack_require__(639); -var utils = __webpack_require__(619); +var Base = __webpack_require__(582); +var define = __webpack_require__(609); +var Compiler = __webpack_require__(619); +var Parser = __webpack_require__(648); +var utils = __webpack_require__(628); var regexCache = {}; var cache = {}; @@ -67540,20 +68006,20 @@ module.exports.Parser = Parser; /***/ }), -/* 573 */ +/* 582 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(112); -var define = __webpack_require__(574); -var CacheBase = __webpack_require__(575); -var Emitter = __webpack_require__(576); -var isObject = __webpack_require__(537); -var merge = __webpack_require__(594); -var pascal = __webpack_require__(597); -var cu = __webpack_require__(598); +var define = __webpack_require__(583); +var CacheBase = __webpack_require__(584); +var Emitter = __webpack_require__(585); +var isObject = __webpack_require__(546); +var merge = __webpack_require__(603); +var pascal = __webpack_require__(606); +var cu = __webpack_require__(607); /** * Optionally define a custom `cache` namespace to use. @@ -67982,7 +68448,7 @@ module.exports.namespace = namespace; /***/ }), -/* 574 */ +/* 583 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67995,7 +68461,7 @@ module.exports.namespace = namespace; -var isDescriptor = __webpack_require__(538); +var isDescriptor = __webpack_require__(547); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -68020,21 +68486,21 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 575 */ +/* 584 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(537); -var Emitter = __webpack_require__(576); -var visit = __webpack_require__(577); -var toPath = __webpack_require__(580); -var union = __webpack_require__(581); -var del = __webpack_require__(585); -var get = __webpack_require__(583); -var has = __webpack_require__(590); -var set = __webpack_require__(593); +var isObject = __webpack_require__(546); +var Emitter = __webpack_require__(585); +var visit = __webpack_require__(586); +var toPath = __webpack_require__(589); +var union = __webpack_require__(590); +var del = __webpack_require__(594); +var get = __webpack_require__(592); +var has = __webpack_require__(599); +var set = __webpack_require__(602); /** * Create a `Cache` constructor that when instantiated will @@ -68288,7 +68754,7 @@ module.exports.namespace = namespace; /***/ }), -/* 576 */ +/* 585 */ /***/ (function(module, exports, __webpack_require__) { @@ -68457,7 +68923,7 @@ Emitter.prototype.hasListeners = function(event){ /***/ }), -/* 577 */ +/* 586 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68470,8 +68936,8 @@ Emitter.prototype.hasListeners = function(event){ -var visit = __webpack_require__(578); -var mapVisit = __webpack_require__(579); +var visit = __webpack_require__(587); +var mapVisit = __webpack_require__(588); module.exports = function(collection, method, val) { var result; @@ -68494,7 +68960,7 @@ module.exports = function(collection, method, val) { /***/ }), -/* 578 */ +/* 587 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68507,7 +68973,7 @@ module.exports = function(collection, method, val) { -var isObject = __webpack_require__(537); +var isObject = __webpack_require__(546); module.exports = function visit(thisArg, method, target, val) { if (!isObject(thisArg) && typeof thisArg !== 'function') { @@ -68534,14 +69000,14 @@ module.exports = function visit(thisArg, method, target, val) { /***/ }), -/* 579 */ +/* 588 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(112); -var visit = __webpack_require__(578); +var visit = __webpack_require__(587); /** * Map `visit` over an array of objects. @@ -68578,7 +69044,7 @@ function isObject(val) { /***/ }), -/* 580 */ +/* 589 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68591,7 +69057,7 @@ function isObject(val) { -var typeOf = __webpack_require__(562); +var typeOf = __webpack_require__(571); module.exports = function toPath(args) { if (typeOf(args) !== 'arguments') { @@ -68618,16 +69084,16 @@ function filter(arr) { /***/ }), -/* 581 */ +/* 590 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(553); -var union = __webpack_require__(582); -var get = __webpack_require__(583); -var set = __webpack_require__(584); +var isObject = __webpack_require__(562); +var union = __webpack_require__(591); +var get = __webpack_require__(592); +var set = __webpack_require__(593); module.exports = function unionValue(obj, prop, value) { if (!isObject(obj)) { @@ -68655,7 +69121,7 @@ function arrayify(val) { /***/ }), -/* 582 */ +/* 591 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68691,7 +69157,7 @@ module.exports = function union(init) { /***/ }), -/* 583 */ +/* 592 */ /***/ (function(module, exports) { /*! @@ -68747,7 +69213,7 @@ function toString(val) { /***/ }), -/* 584 */ +/* 593 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68760,10 +69226,10 @@ function toString(val) { -var split = __webpack_require__(556); -var extend = __webpack_require__(552); -var isPlainObject = __webpack_require__(546); -var isObject = __webpack_require__(553); +var split = __webpack_require__(565); +var extend = __webpack_require__(561); +var isPlainObject = __webpack_require__(555); +var isObject = __webpack_require__(562); module.exports = function(obj, prop, val) { if (!isObject(obj)) { @@ -68809,7 +69275,7 @@ function isValidKey(key) { /***/ }), -/* 585 */ +/* 594 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68822,8 +69288,8 @@ function isValidKey(key) { -var isObject = __webpack_require__(537); -var has = __webpack_require__(586); +var isObject = __webpack_require__(546); +var has = __webpack_require__(595); module.exports = function unset(obj, prop) { if (!isObject(obj)) { @@ -68848,7 +69314,7 @@ module.exports = function unset(obj, prop) { /***/ }), -/* 586 */ +/* 595 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68861,9 +69327,9 @@ module.exports = function unset(obj, prop) { -var isObject = __webpack_require__(587); -var hasValues = __webpack_require__(589); -var get = __webpack_require__(583); +var isObject = __webpack_require__(596); +var hasValues = __webpack_require__(598); +var get = __webpack_require__(592); module.exports = function(obj, prop, noZero) { if (isObject(obj)) { @@ -68874,7 +69340,7 @@ module.exports = function(obj, prop, noZero) { /***/ }), -/* 587 */ +/* 596 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68887,7 +69353,7 @@ module.exports = function(obj, prop, noZero) { -var isArray = __webpack_require__(588); +var isArray = __webpack_require__(597); module.exports = function isObject(val) { return val != null && typeof val === 'object' && isArray(val) === false; @@ -68895,7 +69361,7 @@ module.exports = function isObject(val) { /***/ }), -/* 588 */ +/* 597 */ /***/ (function(module, exports) { var toString = {}.toString; @@ -68906,7 +69372,7 @@ module.exports = Array.isArray || function (arr) { /***/ }), -/* 589 */ +/* 598 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68949,7 +69415,7 @@ module.exports = function hasValue(o, noZero) { /***/ }), -/* 590 */ +/* 599 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68962,9 +69428,9 @@ module.exports = function hasValue(o, noZero) { -var isObject = __webpack_require__(537); -var hasValues = __webpack_require__(591); -var get = __webpack_require__(583); +var isObject = __webpack_require__(546); +var hasValues = __webpack_require__(600); +var get = __webpack_require__(592); module.exports = function(val, prop) { return hasValues(isObject(val) && prop ? get(val, prop) : val); @@ -68972,7 +69438,7 @@ module.exports = function(val, prop) { /***/ }), -/* 591 */ +/* 600 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68985,8 +69451,8 @@ module.exports = function(val, prop) { -var typeOf = __webpack_require__(592); -var isNumber = __webpack_require__(561); +var typeOf = __webpack_require__(601); +var isNumber = __webpack_require__(570); module.exports = function hasValue(val) { // is-number checks for NaN and other edge cases @@ -69039,10 +69505,10 @@ module.exports = function hasValue(val) { /***/ }), -/* 592 */ +/* 601 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(563); +var isBuffer = __webpack_require__(572); var toString = Object.prototype.toString; /** @@ -69164,7 +69630,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 593 */ +/* 602 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69177,10 +69643,10 @@ module.exports = function kindOf(val) { -var split = __webpack_require__(556); -var extend = __webpack_require__(552); -var isPlainObject = __webpack_require__(546); -var isObject = __webpack_require__(553); +var split = __webpack_require__(565); +var extend = __webpack_require__(561); +var isPlainObject = __webpack_require__(555); +var isObject = __webpack_require__(562); module.exports = function(obj, prop, val) { if (!isObject(obj)) { @@ -69226,14 +69692,14 @@ function isValidKey(key) { /***/ }), -/* 594 */ +/* 603 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(595); -var forIn = __webpack_require__(596); +var isExtendable = __webpack_require__(604); +var forIn = __webpack_require__(605); function mixinDeep(target, objects) { var len = arguments.length, i = 0; @@ -69297,7 +69763,7 @@ module.exports = mixinDeep; /***/ }), -/* 595 */ +/* 604 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69310,7 +69776,7 @@ module.exports = mixinDeep; -var isPlainObject = __webpack_require__(546); +var isPlainObject = __webpack_require__(555); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -69318,7 +69784,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 596 */ +/* 605 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69341,7 +69807,7 @@ module.exports = function forIn(obj, fn, thisArg) { /***/ }), -/* 597 */ +/* 606 */ /***/ (function(module, exports) { /*! @@ -69368,14 +69834,14 @@ module.exports = pascalcase; /***/ }), -/* 598 */ +/* 607 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(112); -var utils = __webpack_require__(599); +var utils = __webpack_require__(608); /** * Expose class utils @@ -69740,7 +70206,7 @@ cu.bubble = function(Parent, events) { /***/ }), -/* 599 */ +/* 608 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69754,10 +70220,10 @@ var utils = {}; * Lazily required module dependencies */ -utils.union = __webpack_require__(582); -utils.define = __webpack_require__(600); -utils.isObj = __webpack_require__(537); -utils.staticExtend = __webpack_require__(607); +utils.union = __webpack_require__(591); +utils.define = __webpack_require__(609); +utils.isObj = __webpack_require__(546); +utils.staticExtend = __webpack_require__(616); /** @@ -69768,7 +70234,7 @@ module.exports = utils; /***/ }), -/* 600 */ +/* 609 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69781,7 +70247,7 @@ module.exports = utils; -var isDescriptor = __webpack_require__(601); +var isDescriptor = __webpack_require__(610); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -69806,7 +70272,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 601 */ +/* 610 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69819,9 +70285,9 @@ module.exports = function defineProperty(obj, prop, val) { -var typeOf = __webpack_require__(602); -var isAccessor = __webpack_require__(603); -var isData = __webpack_require__(605); +var typeOf = __webpack_require__(611); +var isAccessor = __webpack_require__(612); +var isData = __webpack_require__(614); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -69835,7 +70301,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 602 */ +/* 611 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -69988,7 +70454,7 @@ function isBuffer(val) { /***/ }), -/* 603 */ +/* 612 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70001,7 +70467,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(604); +var typeOf = __webpack_require__(613); // accessor descriptor properties var accessor = { @@ -70064,10 +70530,10 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 604 */ +/* 613 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(563); +var isBuffer = __webpack_require__(572); var toString = Object.prototype.toString; /** @@ -70186,7 +70652,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 605 */ +/* 614 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70199,7 +70665,7 @@ module.exports = function kindOf(val) { -var typeOf = __webpack_require__(606); +var typeOf = __webpack_require__(615); // data descriptor properties var data = { @@ -70248,10 +70714,10 @@ module.exports = isDataDescriptor; /***/ }), -/* 606 */ +/* 615 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(563); +var isBuffer = __webpack_require__(572); var toString = Object.prototype.toString; /** @@ -70370,7 +70836,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 607 */ +/* 616 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70383,8 +70849,8 @@ module.exports = function kindOf(val) { -var copy = __webpack_require__(608); -var define = __webpack_require__(600); +var copy = __webpack_require__(617); +var define = __webpack_require__(609); var util = __webpack_require__(112); /** @@ -70467,15 +70933,15 @@ module.exports = extend; /***/ }), -/* 608 */ +/* 617 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(562); -var copyDescriptor = __webpack_require__(609); -var define = __webpack_require__(600); +var typeOf = __webpack_require__(571); +var copyDescriptor = __webpack_require__(618); +var define = __webpack_require__(609); /** * Copy static properties, prototype properties, and descriptors from one object to another. @@ -70648,7 +71114,7 @@ module.exports.has = has; /***/ }), -/* 609 */ +/* 618 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70736,16 +71202,16 @@ function isObject(val) { /***/ }), -/* 610 */ +/* 619 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(611); -var define = __webpack_require__(600); -var debug = __webpack_require__(613)('snapdragon:compiler'); -var utils = __webpack_require__(619); +var use = __webpack_require__(620); +var define = __webpack_require__(609); +var debug = __webpack_require__(622)('snapdragon:compiler'); +var utils = __webpack_require__(628); /** * Create a new `Compiler` with the given `options`. @@ -70899,7 +71365,7 @@ Compiler.prototype = { // source map support if (opts.sourcemap) { - var sourcemaps = __webpack_require__(638); + var sourcemaps = __webpack_require__(647); sourcemaps(this); this.mapVisit(this.ast.nodes); this.applySourceMaps(); @@ -70920,7 +71386,7 @@ module.exports = Compiler; /***/ }), -/* 611 */ +/* 620 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70933,7 +71399,7 @@ module.exports = Compiler; -var utils = __webpack_require__(612); +var utils = __webpack_require__(621); module.exports = function base(app, opts) { if (!utils.isObject(app) && typeof app !== 'function') { @@ -71048,7 +71514,7 @@ module.exports = function base(app, opts) { /***/ }), -/* 612 */ +/* 621 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -71062,8 +71528,8 @@ var utils = {}; * Lazily required module dependencies */ -utils.define = __webpack_require__(600); -utils.isObject = __webpack_require__(537); +utils.define = __webpack_require__(609); +utils.isObject = __webpack_require__(546); utils.isString = function(val) { @@ -71078,7 +71544,7 @@ module.exports = utils; /***/ }), -/* 613 */ +/* 622 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -71087,14 +71553,14 @@ module.exports = utils; */ if (typeof process !== 'undefined' && process.type === 'renderer') { - module.exports = __webpack_require__(614); + module.exports = __webpack_require__(623); } else { - module.exports = __webpack_require__(617); + module.exports = __webpack_require__(626); } /***/ }), -/* 614 */ +/* 623 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -71103,7 +71569,7 @@ if (typeof process !== 'undefined' && process.type === 'renderer') { * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(615); +exports = module.exports = __webpack_require__(624); exports.log = log; exports.formatArgs = formatArgs; exports.save = save; @@ -71285,7 +71751,7 @@ function localstorage() { /***/ }), -/* 615 */ +/* 624 */ /***/ (function(module, exports, __webpack_require__) { @@ -71301,7 +71767,7 @@ exports.coerce = coerce; exports.disable = disable; exports.enable = enable; exports.enabled = enabled; -exports.humanize = __webpack_require__(616); +exports.humanize = __webpack_require__(625); /** * The currently active debug mode names, and names to skip. @@ -71493,7 +71959,7 @@ function coerce(val) { /***/ }), -/* 616 */ +/* 625 */ /***/ (function(module, exports) { /** @@ -71651,7 +72117,7 @@ function plural(ms, n, name) { /***/ }), -/* 617 */ +/* 626 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -71667,7 +72133,7 @@ var util = __webpack_require__(112); * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(615); +exports = module.exports = __webpack_require__(624); exports.init = init; exports.log = log; exports.formatArgs = formatArgs; @@ -71846,7 +72312,7 @@ function createWritableStdioStream (fd) { case 'PIPE': case 'TCP': - var net = __webpack_require__(618); + var net = __webpack_require__(627); stream = new net.Socket({ fd: fd, readable: false, @@ -71905,13 +72371,13 @@ exports.enable(load()); /***/ }), -/* 618 */ +/* 627 */ /***/ (function(module, exports) { module.exports = require("net"); /***/ }), -/* 619 */ +/* 628 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -71921,9 +72387,9 @@ module.exports = require("net"); * Module dependencies */ -exports.extend = __webpack_require__(552); -exports.SourceMap = __webpack_require__(620); -exports.sourceMapResolve = __webpack_require__(631); +exports.extend = __webpack_require__(561); +exports.SourceMap = __webpack_require__(629); +exports.sourceMapResolve = __webpack_require__(640); /** * Convert backslash in the given string to forward slashes @@ -71966,7 +72432,7 @@ exports.last = function(arr, n) { /***/ }), -/* 620 */ +/* 629 */ /***/ (function(module, exports, __webpack_require__) { /* @@ -71974,13 +72440,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__(621).SourceMapGenerator; -exports.SourceMapConsumer = __webpack_require__(627).SourceMapConsumer; -exports.SourceNode = __webpack_require__(630).SourceNode; +exports.SourceMapGenerator = __webpack_require__(630).SourceMapGenerator; +exports.SourceMapConsumer = __webpack_require__(636).SourceMapConsumer; +exports.SourceNode = __webpack_require__(639).SourceNode; /***/ }), -/* 621 */ +/* 630 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -71990,10 +72456,10 @@ exports.SourceNode = __webpack_require__(630).SourceNode; * http://opensource.org/licenses/BSD-3-Clause */ -var base64VLQ = __webpack_require__(622); -var util = __webpack_require__(624); -var ArraySet = __webpack_require__(625).ArraySet; -var MappingList = __webpack_require__(626).MappingList; +var base64VLQ = __webpack_require__(631); +var util = __webpack_require__(633); +var ArraySet = __webpack_require__(634).ArraySet; +var MappingList = __webpack_require__(635).MappingList; /** * An instance of the SourceMapGenerator represents a source map which is @@ -72402,7 +72868,7 @@ exports.SourceMapGenerator = SourceMapGenerator; /***/ }), -/* 622 */ +/* 631 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -72442,7 +72908,7 @@ exports.SourceMapGenerator = SourceMapGenerator; * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -var base64 = __webpack_require__(623); +var base64 = __webpack_require__(632); // 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, @@ -72548,7 +73014,7 @@ exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) { /***/ }), -/* 623 */ +/* 632 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -72621,7 +73087,7 @@ exports.decode = function (charCode) { /***/ }), -/* 624 */ +/* 633 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -73044,7 +73510,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate /***/ }), -/* 625 */ +/* 634 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -73054,7 +73520,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(624); +var util = __webpack_require__(633); var has = Object.prototype.hasOwnProperty; var hasNativeMap = typeof Map !== "undefined"; @@ -73171,7 +73637,7 @@ exports.ArraySet = ArraySet; /***/ }), -/* 626 */ +/* 635 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -73181,7 +73647,7 @@ exports.ArraySet = ArraySet; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(624); +var util = __webpack_require__(633); /** * Determine whether mappingB is after mappingA with respect to generated @@ -73256,7 +73722,7 @@ exports.MappingList = MappingList; /***/ }), -/* 627 */ +/* 636 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -73266,11 +73732,11 @@ exports.MappingList = MappingList; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(624); -var binarySearch = __webpack_require__(628); -var ArraySet = __webpack_require__(625).ArraySet; -var base64VLQ = __webpack_require__(622); -var quickSort = __webpack_require__(629).quickSort; +var util = __webpack_require__(633); +var binarySearch = __webpack_require__(637); +var ArraySet = __webpack_require__(634).ArraySet; +var base64VLQ = __webpack_require__(631); +var quickSort = __webpack_require__(638).quickSort; function SourceMapConsumer(aSourceMap) { var sourceMap = aSourceMap; @@ -74344,7 +74810,7 @@ exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer; /***/ }), -/* 628 */ +/* 637 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -74461,7 +74927,7 @@ exports.search = function search(aNeedle, aHaystack, aCompare, aBias) { /***/ }), -/* 629 */ +/* 638 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -74581,7 +75047,7 @@ exports.quickSort = function (ary, comparator) { /***/ }), -/* 630 */ +/* 639 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -74591,8 +75057,8 @@ exports.quickSort = function (ary, comparator) { * http://opensource.org/licenses/BSD-3-Clause */ -var SourceMapGenerator = __webpack_require__(621).SourceMapGenerator; -var util = __webpack_require__(624); +var SourceMapGenerator = __webpack_require__(630).SourceMapGenerator; +var util = __webpack_require__(633); // Matches a Windows-style `\r\n` newline or a `\n` newline used by all other // operating systems these days (capturing the result). @@ -75000,17 +75466,17 @@ exports.SourceNode = SourceNode; /***/ }), -/* 631 */ +/* 640 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014, 2015, 2016, 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var sourceMappingURL = __webpack_require__(632) -var resolveUrl = __webpack_require__(633) -var decodeUriComponent = __webpack_require__(634) -var urix = __webpack_require__(636) -var atob = __webpack_require__(637) +var sourceMappingURL = __webpack_require__(641) +var resolveUrl = __webpack_require__(642) +var decodeUriComponent = __webpack_require__(643) +var urix = __webpack_require__(645) +var atob = __webpack_require__(646) @@ -75308,7 +75774,7 @@ module.exports = { /***/ }), -/* 632 */ +/* 641 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;// Copyright 2014 Simon Lydell @@ -75371,7 +75837,7 @@ void (function(root, factory) { /***/ }), -/* 633 */ +/* 642 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -75389,13 +75855,13 @@ module.exports = resolveUrl /***/ }), -/* 634 */ +/* 643 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var decodeUriComponent = __webpack_require__(635) +var decodeUriComponent = __webpack_require__(644) function customDecodeUriComponent(string) { // `decodeUriComponent` turns `+` into ` `, but that's not wanted. @@ -75406,7 +75872,7 @@ module.exports = customDecodeUriComponent /***/ }), -/* 635 */ +/* 644 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75507,7 +75973,7 @@ module.exports = function (encodedURI) { /***/ }), -/* 636 */ +/* 645 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -75530,7 +75996,7 @@ module.exports = urix /***/ }), -/* 637 */ +/* 646 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75544,7 +76010,7 @@ module.exports = atob.atob = atob; /***/ }), -/* 638 */ +/* 647 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75552,8 +76018,8 @@ module.exports = atob.atob = atob; var fs = __webpack_require__(134); var path = __webpack_require__(4); -var define = __webpack_require__(600); -var utils = __webpack_require__(619); +var define = __webpack_require__(609); +var utils = __webpack_require__(628); /** * Expose `mixin()`. @@ -75696,19 +76162,19 @@ exports.comment = function(node) { /***/ }), -/* 639 */ +/* 648 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(611); +var use = __webpack_require__(620); var util = __webpack_require__(112); -var Cache = __webpack_require__(640); -var define = __webpack_require__(600); -var debug = __webpack_require__(613)('snapdragon:parser'); -var Position = __webpack_require__(641); -var utils = __webpack_require__(619); +var Cache = __webpack_require__(649); +var define = __webpack_require__(609); +var debug = __webpack_require__(622)('snapdragon:parser'); +var Position = __webpack_require__(650); +var utils = __webpack_require__(628); /** * Create a new `Parser` with the given `input` and `options`. @@ -76236,7 +76702,7 @@ module.exports = Parser; /***/ }), -/* 640 */ +/* 649 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -76343,13 +76809,13 @@ MapCache.prototype.del = function mapDelete(key) { /***/ }), -/* 641 */ +/* 650 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var define = __webpack_require__(600); +var define = __webpack_require__(609); /** * Store position for a node @@ -76364,14 +76830,14 @@ module.exports = function Position(start, parser) { /***/ }), -/* 642 */ +/* 651 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(643); -var assignSymbols = __webpack_require__(547); +var isExtendable = __webpack_require__(652); +var assignSymbols = __webpack_require__(556); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -76431,7 +76897,7 @@ function isEnum(obj, key) { /***/ }), -/* 643 */ +/* 652 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -76444,7 +76910,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(546); +var isPlainObject = __webpack_require__(555); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -76452,14 +76918,14 @@ module.exports = function isExtendable(val) { /***/ }), -/* 644 */ +/* 653 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var nanomatch = __webpack_require__(645); -var extglob = __webpack_require__(660); +var nanomatch = __webpack_require__(654); +var extglob = __webpack_require__(669); module.exports = function(snapdragon) { var compilers = snapdragon.compiler.compilers; @@ -76536,7 +77002,7 @@ function escapeExtglobs(compiler) { /***/ }), -/* 645 */ +/* 654 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -76547,17 +77013,17 @@ function escapeExtglobs(compiler) { */ var util = __webpack_require__(112); -var toRegex = __webpack_require__(529); -var extend = __webpack_require__(646); +var toRegex = __webpack_require__(538); +var extend = __webpack_require__(655); /** * Local dependencies */ -var compilers = __webpack_require__(648); -var parsers = __webpack_require__(649); -var cache = __webpack_require__(652); -var utils = __webpack_require__(654); +var compilers = __webpack_require__(657); +var parsers = __webpack_require__(658); +var cache = __webpack_require__(661); +var utils = __webpack_require__(663); var MAX_LENGTH = 1024 * 64; /** @@ -77381,14 +77847,14 @@ module.exports = nanomatch; /***/ }), -/* 646 */ +/* 655 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(647); -var assignSymbols = __webpack_require__(547); +var isExtendable = __webpack_require__(656); +var assignSymbols = __webpack_require__(556); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -77448,7 +77914,7 @@ function isEnum(obj, key) { /***/ }), -/* 647 */ +/* 656 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77461,7 +77927,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(546); +var isPlainObject = __webpack_require__(555); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -77469,7 +77935,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 648 */ +/* 657 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77815,15 +78281,15 @@ module.exports = function(nanomatch, options) { /***/ }), -/* 649 */ +/* 658 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var regexNot = __webpack_require__(548); -var toRegex = __webpack_require__(529); -var isOdd = __webpack_require__(650); +var regexNot = __webpack_require__(557); +var toRegex = __webpack_require__(538); +var isOdd = __webpack_require__(659); /** * Characters to use in negation regex (we want to "not" match @@ -78209,7 +78675,7 @@ module.exports.not = NOT_REGEX; /***/ }), -/* 650 */ +/* 659 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78222,7 +78688,7 @@ module.exports.not = NOT_REGEX; -var isNumber = __webpack_require__(651); +var isNumber = __webpack_require__(660); module.exports = function isOdd(i) { if (!isNumber(i)) { @@ -78236,7 +78702,7 @@ module.exports = function isOdd(i) { /***/ }), -/* 651 */ +/* 660 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78264,14 +78730,14 @@ module.exports = function isNumber(num) { /***/ }), -/* 652 */ +/* 661 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(653))(); +module.exports = new (__webpack_require__(662))(); /***/ }), -/* 653 */ +/* 662 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78284,7 +78750,7 @@ module.exports = new (__webpack_require__(653))(); -var MapCache = __webpack_require__(640); +var MapCache = __webpack_require__(649); /** * Create a new `FragmentCache` with an optional object to use for `caches`. @@ -78406,7 +78872,7 @@ exports = module.exports = FragmentCache; /***/ }), -/* 654 */ +/* 663 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78419,14 +78885,14 @@ var path = __webpack_require__(4); * Module dependencies */ -var isWindows = __webpack_require__(655)(); -var Snapdragon = __webpack_require__(572); -utils.define = __webpack_require__(656); -utils.diff = __webpack_require__(657); -utils.extend = __webpack_require__(646); -utils.pick = __webpack_require__(658); -utils.typeOf = __webpack_require__(659); -utils.unique = __webpack_require__(551); +var isWindows = __webpack_require__(664)(); +var Snapdragon = __webpack_require__(581); +utils.define = __webpack_require__(665); +utils.diff = __webpack_require__(666); +utils.extend = __webpack_require__(655); +utils.pick = __webpack_require__(667); +utils.typeOf = __webpack_require__(668); +utils.unique = __webpack_require__(560); /** * Returns true if the given value is effectively an empty string @@ -78792,7 +79258,7 @@ utils.unixify = function(options) { /***/ }), -/* 655 */ +/* 664 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*! @@ -78820,7 +79286,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /***/ }), -/* 656 */ +/* 665 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78833,8 +79299,8 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ -var isobject = __webpack_require__(537); -var isDescriptor = __webpack_require__(538); +var isobject = __webpack_require__(546); +var isDescriptor = __webpack_require__(547); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -78865,7 +79331,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 657 */ +/* 666 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78919,7 +79385,7 @@ function diffArray(one, two) { /***/ }), -/* 658 */ +/* 667 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78932,7 +79398,7 @@ function diffArray(one, two) { -var isObject = __webpack_require__(537); +var isObject = __webpack_require__(546); module.exports = function pick(obj, keys) { if (!isObject(obj) && typeof obj !== 'function') { @@ -78961,7 +79427,7 @@ module.exports = function pick(obj, keys) { /***/ }), -/* 659 */ +/* 668 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -79096,7 +79562,7 @@ function isBuffer(val) { /***/ }), -/* 660 */ +/* 669 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79106,18 +79572,18 @@ function isBuffer(val) { * Module dependencies */ -var extend = __webpack_require__(552); -var unique = __webpack_require__(551); -var toRegex = __webpack_require__(529); +var extend = __webpack_require__(561); +var unique = __webpack_require__(560); +var toRegex = __webpack_require__(538); /** * Local dependencies */ -var compilers = __webpack_require__(661); -var parsers = __webpack_require__(667); -var Extglob = __webpack_require__(670); -var utils = __webpack_require__(669); +var compilers = __webpack_require__(670); +var parsers = __webpack_require__(676); +var Extglob = __webpack_require__(679); +var utils = __webpack_require__(678); var MAX_LENGTH = 1024 * 64; /** @@ -79434,13 +79900,13 @@ module.exports = extglob; /***/ }), -/* 661 */ +/* 670 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(662); +var brackets = __webpack_require__(671); /** * Extglob compilers @@ -79610,7 +80076,7 @@ module.exports = function(extglob) { /***/ }), -/* 662 */ +/* 671 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79620,17 +80086,17 @@ module.exports = function(extglob) { * Local dependencies */ -var compilers = __webpack_require__(663); -var parsers = __webpack_require__(665); +var compilers = __webpack_require__(672); +var parsers = __webpack_require__(674); /** * Module dependencies */ -var debug = __webpack_require__(613)('expand-brackets'); -var extend = __webpack_require__(552); -var Snapdragon = __webpack_require__(572); -var toRegex = __webpack_require__(529); +var debug = __webpack_require__(622)('expand-brackets'); +var extend = __webpack_require__(561); +var Snapdragon = __webpack_require__(581); +var toRegex = __webpack_require__(538); /** * Parses the given POSIX character class `pattern` and returns a @@ -79828,13 +80294,13 @@ module.exports = brackets; /***/ }), -/* 663 */ +/* 672 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var posix = __webpack_require__(664); +var posix = __webpack_require__(673); module.exports = function(brackets) { brackets.compiler @@ -79922,7 +80388,7 @@ module.exports = function(brackets) { /***/ }), -/* 664 */ +/* 673 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79951,14 +80417,14 @@ module.exports = { /***/ }), -/* 665 */ +/* 674 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(666); -var define = __webpack_require__(600); +var utils = __webpack_require__(675); +var define = __webpack_require__(609); /** * Text regex @@ -80177,14 +80643,14 @@ module.exports.TEXT_REGEX = TEXT_REGEX; /***/ }), -/* 666 */ +/* 675 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var toRegex = __webpack_require__(529); -var regexNot = __webpack_require__(548); +var toRegex = __webpack_require__(538); +var regexNot = __webpack_require__(557); var cached; /** @@ -80218,15 +80684,15 @@ exports.createRegex = function(pattern, include) { /***/ }), -/* 667 */ +/* 676 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(662); -var define = __webpack_require__(668); -var utils = __webpack_require__(669); +var brackets = __webpack_require__(671); +var define = __webpack_require__(677); +var utils = __webpack_require__(678); /** * Characters to use in text regex (we want to "not" match @@ -80381,7 +80847,7 @@ module.exports = parsers; /***/ }), -/* 668 */ +/* 677 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80394,7 +80860,7 @@ module.exports = parsers; -var isDescriptor = __webpack_require__(538); +var isDescriptor = __webpack_require__(547); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -80419,14 +80885,14 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 669 */ +/* 678 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var regex = __webpack_require__(548); -var Cache = __webpack_require__(653); +var regex = __webpack_require__(557); +var Cache = __webpack_require__(662); /** * Utils @@ -80495,7 +80961,7 @@ utils.createRegex = function(str) { /***/ }), -/* 670 */ +/* 679 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80505,16 +80971,16 @@ utils.createRegex = function(str) { * Module dependencies */ -var Snapdragon = __webpack_require__(572); -var define = __webpack_require__(668); -var extend = __webpack_require__(552); +var Snapdragon = __webpack_require__(581); +var define = __webpack_require__(677); +var extend = __webpack_require__(561); /** * Local dependencies */ -var compilers = __webpack_require__(661); -var parsers = __webpack_require__(667); +var compilers = __webpack_require__(670); +var parsers = __webpack_require__(676); /** * Customize Snapdragon parser and renderer @@ -80580,16 +81046,16 @@ module.exports = Extglob; /***/ }), -/* 671 */ +/* 680 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extglob = __webpack_require__(660); -var nanomatch = __webpack_require__(645); -var regexNot = __webpack_require__(548); -var toRegex = __webpack_require__(529); +var extglob = __webpack_require__(669); +var nanomatch = __webpack_require__(654); +var regexNot = __webpack_require__(557); +var toRegex = __webpack_require__(538); var not; /** @@ -80670,14 +81136,14 @@ function textRegex(pattern) { /***/ }), -/* 672 */ +/* 681 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(653))(); +module.exports = new (__webpack_require__(662))(); /***/ }), -/* 673 */ +/* 682 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80690,13 +81156,13 @@ var path = __webpack_require__(4); * Module dependencies */ -var Snapdragon = __webpack_require__(572); -utils.define = __webpack_require__(674); -utils.diff = __webpack_require__(657); -utils.extend = __webpack_require__(642); -utils.pick = __webpack_require__(658); -utils.typeOf = __webpack_require__(675); -utils.unique = __webpack_require__(551); +var Snapdragon = __webpack_require__(581); +utils.define = __webpack_require__(683); +utils.diff = __webpack_require__(666); +utils.extend = __webpack_require__(651); +utils.pick = __webpack_require__(667); +utils.typeOf = __webpack_require__(684); +utils.unique = __webpack_require__(560); /** * Returns true if the platform is windows, or `path.sep` is `\\`. @@ -80993,7 +81459,7 @@ utils.unixify = function(options) { /***/ }), -/* 674 */ +/* 683 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81006,8 +81472,8 @@ utils.unixify = function(options) { -var isobject = __webpack_require__(537); -var isDescriptor = __webpack_require__(538); +var isobject = __webpack_require__(546); +var isDescriptor = __webpack_require__(547); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -81038,7 +81504,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 675 */ +/* 684 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -81173,7 +81639,7 @@ function isBuffer(val) { /***/ }), -/* 676 */ +/* 685 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81192,9 +81658,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(677); -var reader_1 = __webpack_require__(690); -var fs_stream_1 = __webpack_require__(694); +var readdir = __webpack_require__(686); +var reader_1 = __webpack_require__(699); +var fs_stream_1 = __webpack_require__(703); var ReaderAsync = /** @class */ (function (_super) { __extends(ReaderAsync, _super); function ReaderAsync() { @@ -81255,15 +81721,15 @@ exports.default = ReaderAsync; /***/ }), -/* 677 */ +/* 686 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readdirSync = __webpack_require__(678); -const readdirAsync = __webpack_require__(686); -const readdirStream = __webpack_require__(689); +const readdirSync = __webpack_require__(687); +const readdirAsync = __webpack_require__(695); +const readdirStream = __webpack_require__(698); module.exports = exports = readdirAsyncPath; exports.readdir = exports.readdirAsync = exports.async = readdirAsyncPath; @@ -81347,7 +81813,7 @@ function readdirStreamStat (dir, options) { /***/ }), -/* 678 */ +/* 687 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81355,11 +81821,11 @@ function readdirStreamStat (dir, options) { module.exports = readdirSync; -const DirectoryReader = __webpack_require__(679); +const DirectoryReader = __webpack_require__(688); let syncFacade = { - fs: __webpack_require__(684), - forEach: __webpack_require__(685), + fs: __webpack_require__(693), + forEach: __webpack_require__(694), sync: true }; @@ -81388,7 +81854,7 @@ function readdirSync (dir, options, internalOptions) { /***/ }), -/* 679 */ +/* 688 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81397,9 +81863,9 @@ function readdirSync (dir, options, internalOptions) { const Readable = __webpack_require__(138).Readable; const EventEmitter = __webpack_require__(156).EventEmitter; const path = __webpack_require__(4); -const normalizeOptions = __webpack_require__(680); -const stat = __webpack_require__(682); -const call = __webpack_require__(683); +const normalizeOptions = __webpack_require__(689); +const stat = __webpack_require__(691); +const call = __webpack_require__(692); /** * Asynchronously reads the contents of a directory and streams the results @@ -81775,14 +82241,14 @@ module.exports = DirectoryReader; /***/ }), -/* 680 */ +/* 689 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const globToRegExp = __webpack_require__(681); +const globToRegExp = __webpack_require__(690); module.exports = normalizeOptions; @@ -81959,7 +82425,7 @@ function normalizeOptions (options, internalOptions) { /***/ }), -/* 681 */ +/* 690 */ /***/ (function(module, exports) { module.exports = function (glob, opts) { @@ -82096,13 +82562,13 @@ module.exports = function (glob, opts) { /***/ }), -/* 682 */ +/* 691 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const call = __webpack_require__(683); +const call = __webpack_require__(692); module.exports = stat; @@ -82177,7 +82643,7 @@ function symlinkStat (fs, path, lstats, callback) { /***/ }), -/* 683 */ +/* 692 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82238,14 +82704,14 @@ function callOnce (fn) { /***/ }), -/* 684 */ +/* 693 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); -const call = __webpack_require__(683); +const call = __webpack_require__(692); /** * A facade around {@link fs.readdirSync} that allows it to be called @@ -82309,7 +82775,7 @@ exports.lstat = function (path, callback) { /***/ }), -/* 685 */ +/* 694 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82338,7 +82804,7 @@ function syncForEach (array, iterator, done) { /***/ }), -/* 686 */ +/* 695 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82346,12 +82812,12 @@ function syncForEach (array, iterator, done) { module.exports = readdirAsync; -const maybe = __webpack_require__(687); -const DirectoryReader = __webpack_require__(679); +const maybe = __webpack_require__(696); +const DirectoryReader = __webpack_require__(688); let asyncFacade = { fs: __webpack_require__(134), - forEach: __webpack_require__(688), + forEach: __webpack_require__(697), async: true }; @@ -82393,7 +82859,7 @@ function readdirAsync (dir, options, callback, internalOptions) { /***/ }), -/* 687 */ +/* 696 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82420,7 +82886,7 @@ module.exports = function maybe (cb, promise) { /***/ }), -/* 688 */ +/* 697 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82456,7 +82922,7 @@ function asyncForEach (array, iterator, done) { /***/ }), -/* 689 */ +/* 698 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82464,11 +82930,11 @@ function asyncForEach (array, iterator, done) { module.exports = readdirStream; -const DirectoryReader = __webpack_require__(679); +const DirectoryReader = __webpack_require__(688); let streamFacade = { fs: __webpack_require__(134), - forEach: __webpack_require__(688), + forEach: __webpack_require__(697), async: true }; @@ -82488,16 +82954,16 @@ function readdirStream (dir, options, internalOptions) { /***/ }), -/* 690 */ +/* 699 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(4); -var deep_1 = __webpack_require__(691); -var entry_1 = __webpack_require__(693); -var pathUtil = __webpack_require__(692); +var deep_1 = __webpack_require__(700); +var entry_1 = __webpack_require__(702); +var pathUtil = __webpack_require__(701); var Reader = /** @class */ (function () { function Reader(options) { this.options = options; @@ -82563,14 +83029,14 @@ exports.default = Reader; /***/ }), -/* 691 */ +/* 700 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(692); -var patternUtils = __webpack_require__(523); +var pathUtils = __webpack_require__(701); +var patternUtils = __webpack_require__(532); var DeepFilter = /** @class */ (function () { function DeepFilter(options, micromatchOptions) { this.options = options; @@ -82653,7 +83119,7 @@ exports.default = DeepFilter; /***/ }), -/* 692 */ +/* 701 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82684,14 +83150,14 @@ exports.makeAbsolute = makeAbsolute; /***/ }), -/* 693 */ +/* 702 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(692); -var patternUtils = __webpack_require__(523); +var pathUtils = __webpack_require__(701); +var patternUtils = __webpack_require__(532); var EntryFilter = /** @class */ (function () { function EntryFilter(options, micromatchOptions) { this.options = options; @@ -82776,7 +83242,7 @@ exports.default = EntryFilter; /***/ }), -/* 694 */ +/* 703 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82796,8 +83262,8 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(138); -var fsStat = __webpack_require__(695); -var fs_1 = __webpack_require__(699); +var fsStat = __webpack_require__(704); +var fs_1 = __webpack_require__(708); var FileSystemStream = /** @class */ (function (_super) { __extends(FileSystemStream, _super); function FileSystemStream() { @@ -82847,14 +83313,14 @@ exports.default = FileSystemStream; /***/ }), -/* 695 */ +/* 704 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const optionsManager = __webpack_require__(696); -const statProvider = __webpack_require__(698); +const optionsManager = __webpack_require__(705); +const statProvider = __webpack_require__(707); /** * Asynchronous API. */ @@ -82885,13 +83351,13 @@ exports.statSync = statSync; /***/ }), -/* 696 */ +/* 705 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsAdapter = __webpack_require__(697); +const fsAdapter = __webpack_require__(706); function prepare(opts) { const options = Object.assign({ fs: fsAdapter.getFileSystemAdapter(opts ? opts.fs : undefined), @@ -82904,7 +83370,7 @@ exports.prepare = prepare; /***/ }), -/* 697 */ +/* 706 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82927,7 +83393,7 @@ exports.getFileSystemAdapter = getFileSystemAdapter; /***/ }), -/* 698 */ +/* 707 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82979,7 +83445,7 @@ exports.isFollowedSymlink = isFollowedSymlink; /***/ }), -/* 699 */ +/* 708 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83010,7 +83476,7 @@ exports.default = FileSystem; /***/ }), -/* 700 */ +/* 709 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83030,9 +83496,9 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(138); -var readdir = __webpack_require__(677); -var reader_1 = __webpack_require__(690); -var fs_stream_1 = __webpack_require__(694); +var readdir = __webpack_require__(686); +var reader_1 = __webpack_require__(699); +var fs_stream_1 = __webpack_require__(703); var TransformStream = /** @class */ (function (_super) { __extends(TransformStream, _super); function TransformStream(reader) { @@ -83100,7 +83566,7 @@ exports.default = ReaderStream; /***/ }), -/* 701 */ +/* 710 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83119,9 +83585,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(677); -var reader_1 = __webpack_require__(690); -var fs_sync_1 = __webpack_require__(702); +var readdir = __webpack_require__(686); +var reader_1 = __webpack_require__(699); +var fs_sync_1 = __webpack_require__(711); var ReaderSync = /** @class */ (function (_super) { __extends(ReaderSync, _super); function ReaderSync() { @@ -83181,7 +83647,7 @@ exports.default = ReaderSync; /***/ }), -/* 702 */ +/* 711 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83200,8 +83666,8 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var fsStat = __webpack_require__(695); -var fs_1 = __webpack_require__(699); +var fsStat = __webpack_require__(704); +var fs_1 = __webpack_require__(708); var FileSystemSync = /** @class */ (function (_super) { __extends(FileSystemSync, _super); function FileSystemSync() { @@ -83247,7 +83713,7 @@ exports.default = FileSystemSync; /***/ }), -/* 703 */ +/* 712 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83263,7 +83729,7 @@ exports.flatten = flatten; /***/ }), -/* 704 */ +/* 713 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83284,13 +83750,13 @@ exports.merge = merge; /***/ }), -/* 705 */ +/* 714 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const pathType = __webpack_require__(706); +const pathType = __webpack_require__(715); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; @@ -83356,13 +83822,13 @@ module.exports.sync = (input, opts) => { /***/ }), -/* 706 */ +/* 715 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); -const pify = __webpack_require__(707); +const pify = __webpack_require__(716); function type(fn, fn2, fp) { if (typeof fp !== 'string') { @@ -83405,7 +83871,7 @@ exports.symlinkSync = typeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 707 */ +/* 716 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83496,17 +83962,17 @@ module.exports = (obj, opts) => { /***/ }), -/* 708 */ +/* 717 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); const path = __webpack_require__(4); -const fastGlob = __webpack_require__(519); -const gitIgnore = __webpack_require__(709); -const pify = __webpack_require__(710); -const slash = __webpack_require__(711); +const fastGlob = __webpack_require__(528); +const gitIgnore = __webpack_require__(718); +const pify = __webpack_require__(719); +const slash = __webpack_require__(720); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -83604,7 +84070,7 @@ module.exports.sync = options => { /***/ }), -/* 709 */ +/* 718 */ /***/ (function(module, exports) { // A simple implementation of make-array @@ -84073,7 +84539,7 @@ module.exports = options => new IgnoreBase(options) /***/ }), -/* 710 */ +/* 719 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84148,7 +84614,7 @@ module.exports = (input, options) => { /***/ }), -/* 711 */ +/* 720 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84166,7 +84632,7 @@ module.exports = input => { /***/ }), -/* 712 */ +/* 721 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84179,7 +84645,7 @@ module.exports = input => { -var isGlob = __webpack_require__(713); +var isGlob = __webpack_require__(722); module.exports = function hasGlob(val) { if (val == null) return false; @@ -84199,7 +84665,7 @@ module.exports = function hasGlob(val) { /***/ }), -/* 713 */ +/* 722 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -84230,17 +84696,17 @@ module.exports = function isGlob(str) { /***/ }), -/* 714 */ +/* 723 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); const {constants: fsConstants} = __webpack_require__(134); -const pEvent = __webpack_require__(715); -const CpFileError = __webpack_require__(718); -const fs = __webpack_require__(720); -const ProgressEmitter = __webpack_require__(723); +const pEvent = __webpack_require__(724); +const CpFileError = __webpack_require__(727); +const fs = __webpack_require__(729); +const ProgressEmitter = __webpack_require__(732); const cpFileAsync = async (source, destination, options, progressEmitter) => { let readError; @@ -84354,12 +84820,12 @@ module.exports.sync = (source, destination, options) => { /***/ }), -/* 715 */ +/* 724 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pTimeout = __webpack_require__(716); +const pTimeout = __webpack_require__(725); const symbolAsyncIterator = Symbol.asyncIterator || '@@asyncIterator'; @@ -84650,12 +85116,12 @@ module.exports.iterator = (emitter, event, options) => { /***/ }), -/* 716 */ +/* 725 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pFinally = __webpack_require__(717); +const pFinally = __webpack_require__(726); class TimeoutError extends Error { constructor(message) { @@ -84701,7 +85167,7 @@ module.exports.TimeoutError = TimeoutError; /***/ }), -/* 717 */ +/* 726 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84723,12 +85189,12 @@ module.exports = (promise, onFinally) => { /***/ }), -/* 718 */ +/* 727 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(719); +const NestedError = __webpack_require__(728); class CpFileError extends NestedError { constructor(message, nested) { @@ -84742,7 +85208,7 @@ module.exports = CpFileError; /***/ }), -/* 719 */ +/* 728 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(112).inherits; @@ -84798,16 +85264,16 @@ module.exports = NestedError; /***/ }), -/* 720 */ +/* 729 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const {promisify} = __webpack_require__(112); const fs = __webpack_require__(133); -const makeDir = __webpack_require__(721); -const pEvent = __webpack_require__(715); -const CpFileError = __webpack_require__(718); +const makeDir = __webpack_require__(730); +const pEvent = __webpack_require__(724); +const CpFileError = __webpack_require__(727); const stat = promisify(fs.stat); const lstat = promisify(fs.lstat); @@ -84904,7 +85370,7 @@ exports.copyFileSync = (source, destination, flags) => { /***/ }), -/* 721 */ +/* 730 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84912,7 +85378,7 @@ exports.copyFileSync = (source, destination, flags) => { const fs = __webpack_require__(134); const path = __webpack_require__(4); const {promisify} = __webpack_require__(112); -const semver = __webpack_require__(722); +const semver = __webpack_require__(731); const useNativeRecursiveOption = semver.satisfies(process.version, '>=10.12.0'); @@ -85067,7 +85533,7 @@ module.exports.sync = (input, options) => { /***/ }), -/* 722 */ +/* 731 */ /***/ (function(module, exports) { exports = module.exports = SemVer @@ -86669,7 +87135,7 @@ function coerce (version, options) { /***/ }), -/* 723 */ +/* 732 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86710,7 +87176,7 @@ module.exports = ProgressEmitter; /***/ }), -/* 724 */ +/* 733 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86756,12 +87222,12 @@ exports.default = module.exports; /***/ }), -/* 725 */ +/* 734 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pMap = __webpack_require__(726); +const pMap = __webpack_require__(735); const pFilter = async (iterable, filterer, options) => { const values = await pMap( @@ -86778,7 +87244,7 @@ module.exports.default = pFilter; /***/ }), -/* 726 */ +/* 735 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86857,12 +87323,12 @@ module.exports.default = pMap; /***/ }), -/* 727 */ +/* 736 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(719); +const NestedError = __webpack_require__(728); class CpyError extends NestedError { constructor(message, nested) { diff --git a/packages/kbn-pm/src/cli.ts b/packages/kbn-pm/src/cli.ts index e6be8d1821d01..e212e146c221b 100644 --- a/packages/kbn-pm/src/cli.ts +++ b/packages/kbn-pm/src/cli.ts @@ -35,7 +35,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 + --no-cache Disable the kbn packages bootstrap cache --no-validate Disable the bootstrap yarn.lock validation --verbose Set log level to verbose --debug Set log level to debug @@ -75,7 +75,7 @@ export async function run(argv: string[]) { cache: true, validate: true, }, - boolean: ['prefer-offline', 'frozen-lockfile', 'cache', 'validate'], + boolean: ['cache', 'validate'], }); const args = options._; diff --git a/packages/kbn-pm/src/commands/bootstrap.ts b/packages/kbn-pm/src/commands/bootstrap.ts index 8cd346a56f278..a72679b734d80 100644 --- a/packages/kbn-pm/src/commands/bootstrap.ts +++ b/packages/kbn-pm/src/commands/bootstrap.ts @@ -26,10 +26,6 @@ export const BootstrapCommand: ICommand = { async run(projects, projectGraph, { options, kbn, rootPath }) { const batchedProjects = topologicallyBatchProjects(projects, projectGraph); const kibanaProjectPath = projects.get('kibana')?.path; - const extraArgs = [ - ...(options['frozen-lockfile'] === true ? ['--frozen-lockfile'] : []), - ...(options['prefer-offline'] === true ? ['--prefer-offline'] : []), - ]; // Install bazel machinery tools if needed await installBazelTools(rootPath); @@ -44,7 +40,7 @@ export const BootstrapCommand: ICommand = { } if (project.isSinglePackageJsonProject || isExternalPlugin) { - await project.installDependencies({ extraArgs }); + await project.installDependencies(); continue; } diff --git a/packages/kbn-pm/src/commands/clean.ts b/packages/kbn-pm/src/commands/clean.ts index e1644a69f2eed..2a0ba28b4a1f1 100644 --- a/packages/kbn-pm/src/commands/clean.ts +++ b/packages/kbn-pm/src/commands/clean.ts @@ -6,19 +6,27 @@ * Public License, v 1. */ +import dedent from 'dedent'; import del from 'del'; import ora from 'ora'; import { join, relative } from 'path'; +import { runBazel } from '../utils/bazel'; import { isDirectory } from '../utils/fs'; import { log } from '../utils/log'; import { ICommand } from './'; export const CleanCommand: ICommand = { - description: 'Remove the node_modules and target directories from all projects.', + description: 'Deletes output directories, node_modules and resets internal caches.', name: 'clean', async run(projects) { + log.warning(dedent` + This command is only necessary for the rare circumstance where you need to recover a consistent + state when problems arise. If you need to run this command often, please let us know by + filling out this form: https://ela.st/yarn-kbn-clean + `); + const toDelete = []; for (const project of projects.values()) { if (await isDirectory(project.nodeModulesLocation)) { @@ -44,6 +52,10 @@ export const CleanCommand: ICommand = { } } + // Runs Bazel soft clean + await runBazel(['clean']); + log.success('Soft cleaned bazel'); + if (toDelete.length === 0) { log.success('Nothing to delete'); } else { diff --git a/packages/kbn-pm/src/commands/index.ts b/packages/kbn-pm/src/commands/index.ts index 06c1074f1e0f9..e2ad1ea70e46c 100644 --- a/packages/kbn-pm/src/commands/index.ts +++ b/packages/kbn-pm/src/commands/index.ts @@ -24,6 +24,7 @@ export interface ICommand { import { BootstrapCommand } from './bootstrap'; import { CleanCommand } from './clean'; +import { ResetCommand } from './reset'; import { RunCommand } from './run'; import { WatchCommand } from './watch'; import { Kibana } from '../utils/kibana'; @@ -31,6 +32,7 @@ import { Kibana } from '../utils/kibana'; export const commands: { [key: string]: ICommand } = { bootstrap: BootstrapCommand, clean: CleanCommand, + reset: ResetCommand, run: RunCommand, watch: WatchCommand, }; diff --git a/packages/kbn-pm/src/commands/reset.ts b/packages/kbn-pm/src/commands/reset.ts new file mode 100644 index 0000000000000..255172fa27ff2 --- /dev/null +++ b/packages/kbn-pm/src/commands/reset.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import dedent from 'dedent'; +import del from 'del'; +import ora from 'ora'; +import { join, relative } from 'path'; + +import { getBazelDiskCacheFolder, getBazelRepositoryCacheFolder, runBazel } from '../utils/bazel'; +import { isDirectory } from '../utils/fs'; +import { log } from '../utils/log'; +import { ICommand } from './'; + +export const ResetCommand: ICommand = { + description: + 'Deletes node_modules and output directories, resets internal and disk caches, and stops Bazel server', + name: 'reset', + + async run(projects) { + log.warning(dedent` + In most cases, 'yarn kbn clean' is all that should be needed to recover a consistent state when + problems arise. If you need to use this command, please let us know, as it should not be necessary. + `); + + const toDelete = []; + for (const project of projects.values()) { + if (await isDirectory(project.nodeModulesLocation)) { + toDelete.push({ + cwd: project.path, + pattern: relative(project.path, project.nodeModulesLocation), + }); + } + + if (await isDirectory(project.targetLocation)) { + toDelete.push({ + cwd: project.path, + pattern: relative(project.path, project.targetLocation), + }); + } + + const { extraPatterns } = project.getCleanConfig(); + if (extraPatterns) { + toDelete.push({ + cwd: project.path, + pattern: extraPatterns, + }); + } + } + + // Runs Bazel hard clean + await runBazel(['clean', '--expunge']); + log.success('Hard cleaned bazel'); + + // Deletes Bazel Cache Folders + await del([await getBazelDiskCacheFolder(), await getBazelRepositoryCacheFolder()], { + force: true, + }); + log.success('Removed disk caches'); + + if (toDelete.length === 0) { + return; + } + + /** + * 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 { pattern, cwd } of toDelete) { + process.chdir(cwd); + const promise = del(pattern); + + if (log.wouldLogLevel('info')) { + ora.promise(promise, relative(originalCwd, join(cwd, String(pattern)))); + } + + await promise; + } + } finally { + process.chdir(originalCwd); + } + }, +}; diff --git a/packages/kbn-pm/src/utils/bazel/get_cache_folders.ts b/packages/kbn-pm/src/utils/bazel/get_cache_folders.ts new file mode 100644 index 0000000000000..0343c967c9d51 --- /dev/null +++ b/packages/kbn-pm/src/utils/bazel/get_cache_folders.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { dirname, resolve } from 'path'; +import { spawn } from '../child_process'; + +async function rawRunBazelInfoRepoCache() { + const { stdout: bazelRepositoryCachePath } = await spawn('bazel', ['info', 'repository_cache'], { + stdio: 'pipe', + }); + return bazelRepositoryCachePath; +} + +export async function getBazelDiskCacheFolder() { + return resolve(dirname(await rawRunBazelInfoRepoCache()), 'disk-cache'); +} + +export async function getBazelRepositoryCacheFolder() { + return await rawRunBazelInfoRepoCache(); +} diff --git a/packages/kbn-pm/src/utils/bazel/index.ts b/packages/kbn-pm/src/utils/bazel/index.ts index 957c4bdf7f6aa..27d1e0118a044 100644 --- a/packages/kbn-pm/src/utils/bazel/index.ts +++ b/packages/kbn-pm/src/utils/bazel/index.ts @@ -6,4 +6,6 @@ * Public License, v 1. */ +export * from './get_cache_folders'; export * from './install_tools'; +export * from './run'; diff --git a/packages/kbn-pm/src/utils/bazel/install_tools.ts b/packages/kbn-pm/src/utils/bazel/install_tools.ts index 4e19974590e83..3440d32ee4b51 100644 --- a/packages/kbn-pm/src/utils/bazel/install_tools.ts +++ b/packages/kbn-pm/src/utils/bazel/install_tools.ts @@ -6,6 +6,7 @@ * Public License, v 1. */ +import dedent from 'dedent'; import { resolve } from 'path'; import { spawn } from '../child_process'; import { readFile } from '../fs'; @@ -25,6 +26,16 @@ async function readBazelToolsVersionFile(repoRootPath: string, versionFilename: return version; } +async function isBazelBinAvailable() { + try { + await spawn('bazel', ['--version'], { stdio: 'pipe' }); + + return true; + } catch { + return false; + } +} + export async function installBazelTools(repoRootPath: string) { log.debug(`[bazel_tools] reading bazel tools versions from version files`); const bazeliskVersion = await readBazelToolsVersionFile(repoRootPath, '.bazeliskversion'); @@ -32,10 +43,17 @@ export async function installBazelTools(repoRootPath: string) { // Check what globals are installed log.debug(`[bazel_tools] verify if bazelisk is installed`); - const { stdout } = await spawn('yarn', ['global', 'list'], { stdio: 'pipe' }); + const { stdout: bazeliskPkgInstallStdout } = await spawn('yarn', ['global', 'list'], { + stdio: 'pipe', + }); + + const isBazelBinAlreadyAvailable = await isBazelBinAvailable(); // Install bazelisk if not installed - if (!stdout.includes(`@bazel/bazelisk@${bazeliskVersion}`)) { + if ( + !bazeliskPkgInstallStdout.includes(`@bazel/bazelisk@${bazeliskVersion}`) || + !isBazelBinAlreadyAvailable + ) { log.info(`[bazel_tools] installing Bazel tools`); log.debug( @@ -47,6 +65,13 @@ export async function installBazelTools(repoRootPath: string) { }, stdio: 'pipe', }); + + const isBazelBinAvailableAfterInstall = await isBazelBinAvailable(); + if (!isBazelBinAvailableAfterInstall) { + throw new Error(dedent` + [bazel_tools] an error occurred when installing the Bazel tools. Please make sure 'yarn global bin' is on your $PATH, otherwise just add it there + `); + } } log.success(`[bazel_tools] all bazel tools are correctly installed`); diff --git a/packages/kbn-pm/src/utils/bazel/run.ts b/packages/kbn-pm/src/utils/bazel/run.ts new file mode 100644 index 0000000000000..0ee7c4376729d --- /dev/null +++ b/packages/kbn-pm/src/utils/bazel/run.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import chalk from 'chalk'; +import execa from 'execa'; +import * as Rx from 'rxjs'; +import { tap } from 'rxjs/operators'; +import { observeLines } from '@kbn/dev-utils/stdio'; +import { spawn } from '../child_process'; +import { log } from '../log'; + +export async function runBazel(bazelArgs: string[], runOpts: execa.Options = {}) { + // Force logs to pipe in order to control the output of them + const bazelOpts: execa.Options = { + ...runOpts, + stdio: 'pipe', + }; + + const bazelProc = spawn('bazel', bazelArgs, bazelOpts); + + const bazelLogs$ = new Rx.Subject(); + + // Bazel outputs machine readable output into stdout and human readable output goes to stderr. + // Therefore we need to get both. In order to get errors we need to parse the actual text line + const bazelLogSubscription = Rx.merge( + observeLines(bazelProc.stdout!).pipe( + tap((line) => log.info(`${chalk.cyan('[bazel]')} ${line}`)) + ), + observeLines(bazelProc.stderr!).pipe( + tap((line) => log.info(`${chalk.cyan('[bazel]')} ${line}`)) + ) + ).subscribe(bazelLogs$); + + // Wait for process and logs to finish, unsubscribing in the end + await bazelProc; + await bazelLogs$.toPromise(); + await bazelLogSubscription.unsubscribe(); +} diff --git a/packages/kbn-pm/src/utils/project.ts b/packages/kbn-pm/src/utils/project.ts index 9c551a32017e8..83eb105380c1b 100644 --- a/packages/kbn-pm/src/utils/project.ts +++ b/packages/kbn-pm/src/utils/project.ts @@ -197,11 +197,11 @@ export class Project { return Object.values(this.allDependencies).every((dep) => isLinkDependency(dep)); } - public async installDependencies({ extraArgs }: { extraArgs: string[] }) { + public async installDependencies(options: { extraArgs?: string[] } = {}) { log.info(`[${this.name}] running yarn`); log.write(''); - await installInDir(this.path, extraArgs); + await installInDir(this.path, options?.extraArgs); log.write(''); } } diff --git a/packages/kbn-test/src/failed_tests_reporter/report_failure.test.ts b/packages/kbn-test/src/failed_tests_reporter/report_failure.test.ts index 96de75618fb8f..5b25c1b5c1ce1 100644 --- a/packages/kbn-test/src/failed_tests_reporter/report_failure.test.ts +++ b/packages/kbn-test/src/failed_tests_reporter/report_failure.test.ts @@ -8,7 +8,7 @@ import dedent from 'dedent'; -import { createFailureIssue, getCiType, updateFailureIssue } from './report_failure'; +import { createFailureIssue, updateFailureIssue } from './report_failure'; jest.mock('./github_api'); const { GithubApi } = jest.requireMock('./github_api'); @@ -40,7 +40,7 @@ describe('createFailureIssue()', () => { this is the failure text \`\`\` - First failure: [${getCiType()} Build](https://build-url) + First failure: [Jenkins Build](https://build-url) ", Array [ @@ -100,7 +100,7 @@ describe('updateFailureIssue()', () => { "calls": Array [ Array [ 1234, - "New failure: [${getCiType()} Build](https://build-url)", + "New failure: [Jenkins Build](https://build-url)", ], ], "results": Array [ diff --git a/packages/kbn-test/src/failed_tests_reporter/report_failure.ts b/packages/kbn-test/src/failed_tests_reporter/report_failure.ts index af9cc0d989183..58ac45cb9b87f 100644 --- a/packages/kbn-test/src/failed_tests_reporter/report_failure.ts +++ b/packages/kbn-test/src/failed_tests_reporter/report_failure.ts @@ -10,10 +10,6 @@ import { TestFailure } from './get_failures'; import { GithubIssueMini, GithubApi } from './github_api'; import { getIssueMetadata, updateIssueMetadata } from './issue_metadata'; -export function getCiType() { - return process.env.TEAMCITY_CI ? 'TeamCity' : 'Jenkins'; -} - export async function createFailureIssue(buildUrl: string, failure: TestFailure, api: GithubApi) { const title = `Failing test: ${failure.classname} - ${failure.name}`; @@ -25,7 +21,7 @@ export async function createFailureIssue(buildUrl: string, failure: TestFailure, failure.failure, '```', '', - `First failure: [${getCiType()} Build](${buildUrl})`, + `First failure: [Jenkins Build](${buildUrl})`, ].join('\n'), { 'test.class': failure.classname, @@ -45,7 +41,7 @@ export async function updateFailureIssue(buildUrl: string, issue: GithubIssueMin }); await api.editIssueBodyAndEnsureOpen(issue.number, newBody); - await api.addIssueComment(issue.number, `New failure: [${getCiType()} Build](${buildUrl})`); + await api.addIssueComment(issue.number, `New failure: [Jenkins Build](${buildUrl})`); return newCount; } diff --git a/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts b/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts index 00dcc9c2c9aba..6f4075388094b 100644 --- a/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts +++ b/packages/kbn-test/src/failed_tests_reporter/run_failed_tests_reporter_cli.ts @@ -22,17 +22,6 @@ import { getReportMessageIter } from './report_metadata'; const DEFAULT_PATTERNS = [Path.resolve(REPO_ROOT, 'target/junit/**/*.xml')]; -const getBranch = () => { - if (process.env.TEAMCITY_CI) { - return (process.env.GIT_BRANCH || '').replace(/^refs\/heads\//, ''); - } else { - // JOB_NAME is formatted as `elastic+kibana+7.x` in some places and `elastic+kibana+7.x/JOB=kibana-intake,node=immutable` in others - const jobNameSplit = (process.env.JOB_NAME || '').split(/\+|\//); - const branch = jobNameSplit.length >= 3 ? jobNameSplit[2] : process.env.GIT_BRANCH; - return branch; - } -}; - export function runFailedTestsReporterCli() { run( async ({ log, flags }) => { @@ -44,15 +33,16 @@ export function runFailedTestsReporterCli() { } if (updateGithub) { - const branch = getBranch(); + // JOB_NAME is formatted as `elastic+kibana+7.x` in some places and `elastic+kibana+7.x/JOB=kibana-intake,node=immutable` in others + const jobNameSplit = (process.env.JOB_NAME || '').split(/\+|\//); + const branch = jobNameSplit.length >= 3 ? jobNameSplit[2] : process.env.GIT_BRANCH; if (!branch) { throw createFailError( 'Unable to determine originating branch from job name or other environment variables' ); } - // ghprbPullId check can be removed once there are no PR jobs running on Jenkins - const isPr = !!process.env.GITHUB_PR_NUMBER || !!process.env.ghprbPullId; + const isPr = !!process.env.ghprbPullId; const isMasterOrVersion = branch === 'master' || branch.match(/^\d+\.(x|\d+)$/); if (!isMasterOrVersion || isPr) { log.info('Failure issues only created on master/version branch jobs'); @@ -68,9 +58,7 @@ export function runFailedTestsReporterCli() { const buildUrl = flags['build-url'] || (updateGithub ? '' : 'http://buildUrl'); if (typeof buildUrl !== 'string' || !buildUrl) { - throw createFlagError( - 'Missing --build-url, process.env.TEAMCITY_BUILD_URL, or process.env.BUILD_URL' - ); + throw createFlagError('Missing --build-url or process.env.BUILD_URL'); } const patterns = flags._.length ? flags._ : DEFAULT_PATTERNS; @@ -162,12 +150,12 @@ export function runFailedTestsReporterCli() { default: { 'github-update': true, 'report-update': true, - 'build-url': process.env.TEAMCITY_BUILD_URL || process.env.BUILD_URL, + 'build-url': process.env.BUILD_URL, }, help: ` --no-github-update Execute the CLI without writing to Github --no-report-update Execute the CLI without writing to the JUnit reports - --build-url URL of the failed build, defaults to process.env.TEAMCITY_BUILD_URL or process.env.BUILD_URL + --build-url URL of the failed build, defaults to process.env.BUILD_URL `, }, } diff --git a/packages/kbn-test/src/functional_test_runner/fake_mocha_types.d.ts b/packages/kbn-test/src/functional_test_runner/fake_mocha_types.d.ts index e78cbbb3aed02..f13703811f58a 100644 --- a/packages/kbn-test/src/functional_test_runner/fake_mocha_types.d.ts +++ b/packages/kbn-test/src/functional_test_runner/fake_mocha_types.d.ts @@ -18,10 +18,11 @@ export interface Suite { suites: Suite[]; tests: Test[]; title: string; - file?: string; + file: string; parent?: Suite; eachTest: (cb: (test: Test) => void) => void; root: boolean; + suiteTag: string; } export interface Test { diff --git a/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts b/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts index 8372d4bd41c4c..b4b5066eb2857 100644 --- a/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts +++ b/packages/kbn-test/src/functional_test_runner/functional_test_runner.ts @@ -62,7 +62,7 @@ export class FunctionalTestRunner { } const mocha = await setupMocha(this.lifecycle, this.log, config, providers); - await this.lifecycle.beforeTests.trigger(); + await this.lifecycle.beforeTests.trigger(mocha.suite); this.log.info('Starting tests'); return await runTests(this.lifecycle, mocha); diff --git a/packages/kbn-test/src/functional_test_runner/integration_tests/failure_hooks.test.js b/packages/kbn-test/src/functional_test_runner/integration_tests/failure_hooks.test.js index 9a003722671c3..2b5990085f2b3 100644 --- a/packages/kbn-test/src/functional_test_runner/integration_tests/failure_hooks.test.js +++ b/packages/kbn-test/src/functional_test_runner/integration_tests/failure_hooks.test.js @@ -19,9 +19,11 @@ describe('failure hooks', function () { it('runs and prints expected output', () => { const proc = spawnSync(process.execPath, [SCRIPT, '--config', FAILURE_HOOKS_CONFIG]); const lines = stripAnsi(proc.stdout.toString('utf8')).split(/\r?\n/); + const linesCopy = [...lines]; + const tests = [ { - flag: '$FAILING_BEFORE_HOOK$', + flag: '"before all" hook: $FAILING_BEFORE_HOOK$', assert(lines) { expect(lines.shift()).toMatch(/info\s+testHookFailure\s+\$FAILING_BEFORE_ERROR\$/); expect(lines.shift()).toMatch( @@ -30,7 +32,7 @@ describe('failure hooks', function () { }, }, { - flag: '$FAILING_TEST$', + flag: '└-> $FAILING_TEST$', assert(lines) { expect(lines.shift()).toMatch(/global before each/); expect(lines.shift()).toMatch(/info\s+testFailure\s+\$FAILING_TEST_ERROR\$/); @@ -38,7 +40,7 @@ describe('failure hooks', function () { }, }, { - flag: '$FAILING_AFTER_HOOK$', + flag: '"after all" hook: $FAILING_AFTER_HOOK$', assert(lines) { expect(lines.shift()).toMatch(/info\s+testHookFailure\s+\$FAILING_AFTER_ERROR\$/); expect(lines.shift()).toMatch( @@ -48,14 +50,19 @@ describe('failure hooks', function () { }, ]; - while (lines.length && tests.length) { - const line = lines.shift(); - if (line.includes(tests[0].flag)) { - const test = tests.shift(); - test.assert(lines); + try { + while (lines.length && tests.length) { + const line = lines.shift(); + if (line.includes(tests[0].flag)) { + const test = tests.shift(); + test.assert(lines); + } } - } - expect(tests).toHaveLength(0); + expect(tests).toHaveLength(0); + } catch (error) { + console.error('full log output', linesCopy.join('\n')); + throw error; + } }); }); diff --git a/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.test.ts b/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.test.ts index e3a66ece404d5..e246eb595ef20 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.test.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.test.ts @@ -8,12 +8,13 @@ import { Lifecycle } from './lifecycle'; import { FailureMetadata } from './failure_metadata'; +import { Test } from '../fake_mocha_types'; it('collects metadata for the current test', async () => { const lifecycle = new Lifecycle(); const failureMetadata = new FailureMetadata(lifecycle); - const test1 = {}; + const test1 = {} as Test; await lifecycle.beforeEachRunnable.trigger(test1); failureMetadata.add({ foo: 'bar' }); @@ -23,7 +24,7 @@ it('collects metadata for the current test', async () => { } `); - const test2 = {}; + const test2 = {} as Test; await lifecycle.beforeEachRunnable.trigger(test2); failureMetadata.add({ test: 2 }); @@ -43,7 +44,7 @@ it('adds messages to the messages state', () => { const lifecycle = new Lifecycle(); const failureMetadata = new FailureMetadata(lifecycle); - const test1 = {}; + const test1 = {} as Test; lifecycle.beforeEachRunnable.trigger(test1); failureMetadata.addMessages(['foo', 'bar']); failureMetadata.addMessages(['baz']); diff --git a/packages/kbn-test/src/functional_test_runner/lib/lifecycle.ts b/packages/kbn-test/src/functional_test_runner/lib/lifecycle.ts index 2d1cafae51622..e0afa0af856c0 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/lifecycle.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/lifecycle.ts @@ -8,21 +8,18 @@ import { LifecyclePhase } from './lifecycle_phase'; -// mocha's global types mean we can't import Mocha or it will override the global jest types.............. -type ItsASuite = any; -type ItsATest = any; -type ItsARunnable = any; +import { Suite, Test } from '../fake_mocha_types'; export class Lifecycle { - public readonly beforeTests = new LifecyclePhase<[]>({ + public readonly beforeTests = new LifecyclePhase<[Suite]>({ singular: true, }); - public readonly beforeEachRunnable = new LifecyclePhase<[ItsARunnable]>(); - public readonly beforeTestSuite = new LifecyclePhase<[ItsASuite]>(); - public readonly beforeEachTest = new LifecyclePhase<[ItsATest]>(); - public readonly afterTestSuite = new LifecyclePhase<[ItsASuite]>(); - public readonly testFailure = new LifecyclePhase<[Error, ItsATest]>(); - public readonly testHookFailure = new LifecyclePhase<[Error, ItsATest]>(); + public readonly beforeEachRunnable = new LifecyclePhase<[Test]>(); + public readonly beforeTestSuite = new LifecyclePhase<[Suite]>(); + public readonly beforeEachTest = new LifecyclePhase<[Test]>(); + public readonly afterTestSuite = new LifecyclePhase<[Suite]>(); + public readonly testFailure = new LifecyclePhase<[Error, Test]>(); + public readonly testHookFailure = new LifecyclePhase<[Error, Test]>(); public readonly cleanup = new LifecyclePhase<[]>({ singular: true, }); diff --git a/packages/kbn-test/src/functional_test_runner/lib/mocha/filter_suites_by_tags.test.js b/packages/kbn-test/src/functional_test_runner/lib/mocha/filter_suites_by_tags.test.js index 6c6ad90d0c6e4..3ffb06b624056 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/mocha/filter_suites_by_tags.test.js +++ b/packages/kbn-test/src/functional_test_runner/lib/mocha/filter_suites_by_tags.test.js @@ -89,8 +89,8 @@ it('only runs hooks of parents and tests in level1a', async () => { "suite: ", "suite: level 1", "suite: level 1 level 1a", - "hook: \\"before each\\" hook: rootBeforeEach", - "hook: level 1 \\"before each\\" hook: level1BeforeEach", + "hook: \\"before each\\" hook: rootBeforeEach for \\"test 1a\\"", + "hook: level 1 \\"before each\\" hook: level1BeforeEach for \\"test 1a\\"", "test: level 1 level 1a test 1a", ] `); @@ -108,8 +108,8 @@ it('only runs hooks of parents and tests in level1b', async () => { "suite: ", "suite: level 1", "suite: level 1 level 1b", - "hook: \\"before each\\" hook: rootBeforeEach", - "hook: level 1 \\"before each\\" hook: level1BeforeEach", + "hook: \\"before each\\" hook: rootBeforeEach for \\"test 1b\\"", + "hook: level 1 \\"before each\\" hook: level1BeforeEach for \\"test 1b\\"", "test: level 1 level 1b test 1b", ] `); @@ -127,12 +127,12 @@ it('only runs hooks of parents and tests in level1a and level1b', async () => { "suite: ", "suite: level 1", "suite: level 1 level 1a", - "hook: \\"before each\\" hook: rootBeforeEach", - "hook: level 1 \\"before each\\" hook: level1BeforeEach", + "hook: \\"before each\\" hook: rootBeforeEach for \\"test 1a\\"", + "hook: level 1 \\"before each\\" hook: level1BeforeEach for \\"test 1a\\"", "test: level 1 level 1a test 1a", "suite: level 1 level 1b", - "hook: \\"before each\\" hook: rootBeforeEach", - "hook: level 1 \\"before each\\" hook: level1BeforeEach", + "hook: \\"before each\\" hook: rootBeforeEach for \\"test 1b\\"", + "hook: level 1 \\"before each\\" hook: level1BeforeEach for \\"test 1b\\"", "test: level 1 level 1b test 1b", ] `); @@ -151,8 +151,8 @@ it('only runs level1a if including level1 and excluding level1b', async () => { "suite: ", "suite: level 1", "suite: level 1 level 1a", - "hook: \\"before each\\" hook: rootBeforeEach", - "hook: level 1 \\"before each\\" hook: level1BeforeEach", + "hook: \\"before each\\" hook: rootBeforeEach for \\"test 1a\\"", + "hook: level 1 \\"before each\\" hook: level1BeforeEach for \\"test 1a\\"", "test: level 1 level 1a test 1a", ] `); @@ -171,8 +171,8 @@ it('only runs level1b if including level1 and excluding level1a', async () => { "suite: ", "suite: level 1", "suite: level 1 level 1b", - "hook: \\"before each\\" hook: rootBeforeEach", - "hook: level 1 \\"before each\\" hook: level1BeforeEach", + "hook: \\"before each\\" hook: rootBeforeEach for \\"test 1b\\"", + "hook: level 1 \\"before each\\" hook: level1BeforeEach for \\"test 1b\\"", "test: level 1 level 1b test 1b", ] `); @@ -190,7 +190,7 @@ it('only runs level2 if excluding level1', async () => { "suite: ", "suite: level 2", "suite: level 2 level 2a", - "hook: \\"before each\\" hook: rootBeforeEach", + "hook: \\"before each\\" hook: rootBeforeEach for \\"test 2a\\"", "test: level 2 level 2a test 2a", ] `); diff --git a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.test.ts b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.test.ts index a8b0252e6f51c..5e16e038f14db 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.test.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.test.ts @@ -12,36 +12,36 @@ import { decorateSnapshotUi, expectSnapshot } from './decorate_snapshot_ui'; import path from 'path'; import fs from 'fs'; -const createMockSuite = ({ tests, root = true }: { tests: Test[]; root?: boolean }) => { +const createRootSuite = () => { const suite = { - tests, - root, - eachTest: (cb: (test: Test) => void) => { + tests: [] as Test[], + root: true, + eachTest: (cb) => { suite.tests.forEach((test) => cb(test)); }, + parent: undefined, } as Suite; return suite; }; -const createMockTest = ({ +const registerTest = ({ + parent, title = 'Test', passed = true, - filename = __filename, - parent, -}: { title?: string; passed?: boolean; filename?: string; parent?: Suite } = {}) => { +}: { + parent: Suite; + title?: string; + passed?: boolean; +}) => { const test = ({ - file: filename, + file: __filename, fullTitle: () => title, isPassed: () => passed, } as unknown) as Test; - if (parent) { - parent.tests.push(test); - test.parent = parent; - } else { - test.parent = createMockSuite({ tests: [test] }); - } + parent.tests.push(test); + test.parent = parent; return test; }; @@ -63,34 +63,41 @@ describe('decorateSnapshotUi', () => { describe('when running a test', () => { let lifecycle: Lifecycle; - beforeEach(() => { + let rootSuite: Suite; + beforeEach(async () => { lifecycle = new Lifecycle(); + rootSuite = createRootSuite(); decorateSnapshotUi({ lifecycle, updateSnapshots: false, isCi: false }); + + await lifecycle.beforeTests.trigger(rootSuite); }); it('passes when the snapshot matches the actual value', async () => { - const test = createMockTest(); + const test = registerTest({ parent: rootSuite }); await lifecycle.beforeEachTest.trigger(test); expect(() => { expectSnapshot('foo').toMatchInline(`"foo"`); }).not.toThrow(); + + await lifecycle.cleanup.trigger(); }); it('throws when the snapshot does not match the actual value', async () => { - const test = createMockTest(); + const test = registerTest({ parent: rootSuite }); await lifecycle.beforeEachTest.trigger(test); expect(() => { expectSnapshot('foo').toMatchInline(`"bar"`); }).toThrow(); + + await lifecycle.cleanup.trigger(); }); it('writes a snapshot to an external file if it does not exist', async () => { - const test: Test = createMockTest(); - + const test = registerTest({ parent: rootSuite }); await lifecycle.beforeEachTest.trigger(test); expect(fs.existsSync(snapshotFile)).toBe(false); @@ -99,7 +106,7 @@ describe('decorateSnapshotUi', () => { expectSnapshot('foo').toMatch(); }).not.toThrow(); - await lifecycle.afterTestSuite.trigger(test.parent); + await lifecycle.cleanup.trigger(); expect(fs.existsSync(snapshotFile)).toBe(true); }); @@ -107,9 +114,13 @@ describe('decorateSnapshotUi', () => { describe('when writing multiple snapshots to a single file', () => { let lifecycle: Lifecycle; - beforeEach(() => { + let rootSuite: Suite; + beforeEach(async () => { lifecycle = new Lifecycle(); + rootSuite = createRootSuite(); decorateSnapshotUi({ lifecycle, updateSnapshots: false, isCi: false }); + + await lifecycle.beforeTests.trigger(rootSuite); }); beforeEach(() => { @@ -127,7 +138,7 @@ exports[\`Test2 1\`] = \`"bar"\`; }); it('compares to an existing snapshot', async () => { - const test1 = createMockTest({ title: 'Test1' }); + const test1 = registerTest({ parent: rootSuite, title: 'Test1' }); await lifecycle.beforeEachTest.trigger(test1); @@ -135,7 +146,7 @@ exports[\`Test2 1\`] = \`"bar"\`; expectSnapshot('foo').toMatch(); }).not.toThrow(); - const test2 = createMockTest({ title: 'Test2' }); + const test2 = registerTest({ parent: rootSuite, title: 'Test2' }); await lifecycle.beforeEachTest.trigger(test2); @@ -143,19 +154,23 @@ exports[\`Test2 1\`] = \`"bar"\`; expectSnapshot('foo').toMatch(); }).toThrow(); - await lifecycle.afterTestSuite.trigger(test1.parent); + await lifecycle.cleanup.trigger(); }); }); describe('when updating snapshots', () => { let lifecycle: Lifecycle; - beforeEach(() => { + let rootSuite: Suite; + beforeEach(async () => { lifecycle = new Lifecycle(); + rootSuite = createRootSuite(); decorateSnapshotUi({ lifecycle, updateSnapshots: true, isCi: false }); + + await lifecycle.beforeTests.trigger(rootSuite); }); it("doesn't throw if the value does not match", async () => { - const test = createMockTest(); + const test = registerTest({ parent: rootSuite }); await lifecycle.beforeEachTest.trigger(test); @@ -163,23 +178,64 @@ exports[\`Test2 1\`] = \`"bar"\`; expectSnapshot('bar').toMatchInline(`"foo"`); }).not.toThrow(); }); + + describe('writing to disk', () => { + beforeEach(() => { + fs.mkdirSync(path.resolve(__dirname, '__snapshots__')); + fs.writeFileSync( + snapshotFile, + `// Jest Snapshot v1, https://goo.gl/fbAQLP + + exports[\`Test 1\`] = \`"foo"\`; + `, + { encoding: 'utf-8' } + ); + }); + + it('updates existing external snapshots', async () => { + const test = registerTest({ parent: rootSuite }); + + await lifecycle.beforeEachTest.trigger(test); + + expect(() => { + expectSnapshot('bar').toMatch(); + }).not.toThrow(); + + await lifecycle.cleanup.trigger(); + + const file = fs.readFileSync(snapshotFile, { encoding: 'utf-8' }); + + expect(file).toMatchInlineSnapshot(` + "// Jest Snapshot v1, https://goo.gl/fbAQLP + + exports[\`Test 1\`] = \`\\"bar\\"\`; + " + `); + }); + }); }); describe('when running on ci', () => { let lifecycle: Lifecycle; - beforeEach(() => { + let rootSuite: Suite; + beforeEach(async () => { lifecycle = new Lifecycle(); + rootSuite = createRootSuite(); decorateSnapshotUi({ lifecycle, updateSnapshots: false, isCi: true }); + + await lifecycle.beforeTests.trigger(rootSuite); }); it('throws on new snapshots', async () => { - const test = createMockTest(); + const test = registerTest({ parent: rootSuite }); await lifecycle.beforeEachTest.trigger(test); expect(() => { expectSnapshot('bar').toMatchInline(); }).toThrow(); + + await lifecycle.cleanup.trigger(); }); describe('when adding to an existing file', () => { @@ -198,17 +254,27 @@ exports[\`Test2 1\`] = \`"bar"\`; }); it('does not throw on an existing test', async () => { - const test = createMockTest({ title: 'Test' }); + const test = registerTest({ parent: rootSuite }); await lifecycle.beforeEachTest.trigger(test); expect(() => { expectSnapshot('foo').toMatch(); }).not.toThrow(); + + const test2 = registerTest({ parent: rootSuite, title: 'Test2' }); + + await lifecycle.beforeEachTest.trigger(test2); + + expect(() => { + expectSnapshot('bar').toMatch(); + }).not.toThrow(); + + await lifecycle.cleanup.trigger(); }); it('throws on a new test', async () => { - const test = createMockTest({ title: 'New test' }); + const test = registerTest({ parent: rootSuite, title: 'New test' }); await lifecycle.beforeEachTest.trigger(test); @@ -217,8 +283,8 @@ exports[\`Test2 1\`] = \`"bar"\`; }).toThrow(); }); - it('does not throw when all snapshots are used ', async () => { - const test = createMockTest({ title: 'Test' }); + it('does not throw when all snapshots are used', async () => { + const test = registerTest({ parent: rootSuite }); await lifecycle.beforeEachTest.trigger(test); @@ -226,7 +292,7 @@ exports[\`Test2 1\`] = \`"bar"\`; expectSnapshot('foo').toMatch(); }).not.toThrow(); - const test2 = createMockTest({ title: 'Test2' }); + const test2 = registerTest({ parent: rootSuite, title: 'Test2' }); await lifecycle.beforeEachTest.trigger(test2); @@ -234,13 +300,13 @@ exports[\`Test2 1\`] = \`"bar"\`; expectSnapshot('bar').toMatch(); }).not.toThrow(); - const afterTestSuite = lifecycle.afterTestSuite.trigger(test.parent); + const afterCleanup = lifecycle.cleanup.trigger(); - await expect(afterTestSuite).resolves.toBe(undefined); + await expect(afterCleanup).resolves.toBe(undefined); }); it('throws on unused snapshots', async () => { - const test = createMockTest({ title: 'Test' }); + const test = registerTest({ parent: rootSuite }); await lifecycle.beforeEachTest.trigger(test); @@ -248,9 +314,9 @@ exports[\`Test2 1\`] = \`"bar"\`; expectSnapshot('foo').toMatch(); }).not.toThrow(); - const afterTestSuite = lifecycle.afterTestSuite.trigger(test.parent); + const afterCleanup = lifecycle.cleanup.trigger(); - await expect(afterTestSuite).rejects.toMatchInlineSnapshot(` + await expect(afterCleanup).rejects.toMatchInlineSnapshot(` [Error: 1 obsolete snapshot(s) found: Test2 1. @@ -259,17 +325,11 @@ exports[\`Test2 1\`] = \`"bar"\`; }); it('does not throw on unused when some tests are skipped', async () => { - const root = createMockSuite({ tests: [] }); - - const test = createMockTest({ - title: 'Test', - parent: root, - passed: true, - }); + const test = registerTest({ parent: rootSuite, passed: true }); - createMockTest({ + registerTest({ title: 'Test2', - parent: root, + parent: rootSuite, passed: false, }); @@ -279,9 +339,9 @@ exports[\`Test2 1\`] = \`"bar"\`; expectSnapshot('foo').toMatch(); }).not.toThrow(); - const afterTestSuite = lifecycle.afterTestSuite.trigger(root); + const afterCleanup = lifecycle.cleanup.trigger(); - await expect(afterTestSuite).resolves.toBeUndefined(); + await expect(afterCleanup).resolves.toBeUndefined(); }); }); }); diff --git a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts index 4a52299ecf6d1..e64132c989fb3 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts @@ -71,43 +71,51 @@ export function decorateSnapshotUi({ updateSnapshots: boolean; isCi: boolean; }) { - globalState.registered = true; - globalState.snapshotStates = {}; - globalState.currentTest = null; - - if (isCi) { - // make sure snapshots that have not been committed - // are not written to file on CI, passing the test - globalState.updateSnapshot = 'none'; - } else { - globalState.updateSnapshot = updateSnapshots ? 'all' : 'new'; - } + let rootSuite: Suite | undefined; - modifyStackTracePrepareOnce(); + lifecycle.beforeTests.add((root) => { + if (!root) { + throw new Error('Root suite was not set'); + } + rootSuite = root; - addSerializer({ - serialize: (num: number) => { - return String(parseFloat(num.toPrecision(15))); - }, - test: (value: any) => { - return typeof value === 'number'; - }, - }); + globalState.registered = true; + globalState.snapshotStates = {}; + globalState.currentTest = null; + + if (isCi) { + // make sure snapshots that have not been committed + // are not written to file on CI, passing the test + globalState.updateSnapshot = 'none'; + } else { + globalState.updateSnapshot = updateSnapshots ? 'all' : 'new'; + } - // @ts-expect-error - global.expectSnapshot = expectSnapshot; + modifyStackTracePrepareOnce(); + + addSerializer({ + serialize: (num: number) => { + return String(parseFloat(num.toPrecision(15))); + }, + test: (value: any) => { + return typeof value === 'number'; + }, + }); + + // @ts-expect-error + global.expectSnapshot = expectSnapshot; + }); lifecycle.beforeEachTest.add((test: Test) => { globalState.currentTest = test; }); - lifecycle.afterTestSuite.add(function (testSuite: Suite) { - // save snapshot & check unused after top-level test suite completes - if (!testSuite.root) { + lifecycle.cleanup.add(() => { + if (!rootSuite) { return; } - testSuite.eachTest((test) => { + rootSuite.eachTest((test) => { const file = test.file; if (!file) { diff --git a/packages/kbn-test/src/functional_test_runner/lib/suite_tracker.test.ts b/packages/kbn-test/src/functional_test_runner/lib/suite_tracker.test.ts index 8545a6df46c30..c0945ed92f7d6 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/suite_tracker.test.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/suite_tracker.test.ts @@ -17,6 +17,7 @@ jest.mock('@kbn/utils', () => { import { REPO_ROOT } from '@kbn/dev-utils'; import { Lifecycle } from './lifecycle'; import { SuiteTracker } from './suite_tracker'; +import { Suite } from '../fake_mocha_types'; const DEFAULT_TEST_METADATA_PATH = join(REPO_ROOT, 'target', 'test_metadata.json'); const MOCK_CONFIG_PATH = join('test', 'config.js'); @@ -47,18 +48,18 @@ describe('SuiteTracker', () => { jest.resetAllMocks(); }); - let MOCKS: Record; + let MOCKS: Record; const createMock = (overrides = {}) => { - return { + return ({ file: resolve(REPO_ROOT, MOCK_TEST_PATH), title: 'A Test', suiteTag: MOCK_TEST_PATH, ...overrides, - }; + } as unknown) as Suite; }; - const runLifecycleWithMocks = async (mocks: object[], fn: (objs: any) => any = () => {}) => { + const runLifecycleWithMocks = async (mocks: Suite[], fn: (objs: any) => any = () => {}) => { const lifecycle = new Lifecycle(); const suiteTracker = SuiteTracker.startTracking( lifecycle, diff --git a/packages/kbn-test/types/ftr.d.ts b/packages/kbn-test/types/ftr.d.ts index 3d19e45b81bea..4ba242a41d806 100644 --- a/packages/kbn-test/types/ftr.d.ts +++ b/packages/kbn-test/types/ftr.d.ts @@ -13,6 +13,7 @@ import { FailureMetadata, DockerServersService, } from '../src/functional_test_runner/lib'; +import { Test, Suite } from '../src/functional_test_runner/fake_mocha_types'; export { Lifecycle, Config, FailureMetadata }; @@ -91,3 +92,5 @@ export interface FtrConfigProviderContext { log: ToolingLog; readConfigFile(path: string): Promise; } + +export { Test, Suite }; diff --git a/packages/kbn-ui-shared-deps/flot_charts/index.js b/packages/kbn-ui-shared-deps/flot_charts/index.js index 8d6c0e5c8facc..4f6468262e3b4 100644 --- a/packages/kbn-ui-shared-deps/flot_charts/index.js +++ b/packages/kbn-ui-shared-deps/flot_charts/index.js @@ -1,11 +1,3 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. -*/ - /* @notice * * This product includes code that is based on flot-charts, which was available diff --git a/preinstall_check.js b/preinstall_check.js index e761afa91022c..e508f9ecb10c6 100644 --- a/preinstall_check.js +++ b/preinstall_check.js @@ -6,34 +6,37 @@ * Public License, v 1. */ -const isUsingNpm = process.env.npm_config_git !== undefined; +(() => { + const isUsingNpm = process.env.npm_config_git !== undefined; -if (isUsingNpm) { - throw `Use Yarn instead of npm, see Kibana's contributing guidelines`; -} - -// The value of the `npm_config_argv` env for each command: -// -// - `npm install`: '{"remain":[],"cooked":["install"],"original":[]}' -// - `yarn`: '{"remain":[],"cooked":["install"],"original":[]}' -// - `yarn kbn bootstrap`: '{"remain":[],"cooked":["run","kbn"],"original":["kbn","bootstrap"]}' -const rawArgv = process.env.npm_config_argv; - -if (rawArgv === undefined) { - return; -} + if (isUsingNpm) { + throw `Use Yarn instead of npm, see Kibana's contributing guidelines`; + } -try { - const argv = JSON.parse(rawArgv); + // The value of the `npm_config_argv` env for each command: + // + // - `npm install`: '{"remain":[],"cooked":["install"],"original":[]}' + // - `yarn`: '{"remain":[],"cooked":["install"],"original":[]}' + // - `yarn kbn bootstrap`: '{"remain":[],"cooked":["run","kbn"],"original":["kbn","bootstrap"]}' + const rawArgv = process.env.npm_config_argv; - if (argv.cooked.includes('kbn')) { - // all good, trying to install deps using `kbn` + if (rawArgv === undefined) { return; } - if (argv.cooked.includes('install')) { - console.log('\nWARNING: When installing dependencies, prefer `yarn kbn bootstrap`\n'); + try { + const argv = JSON.parse(rawArgv); + + // allow dependencies to be installed with `yarn kbn bootstrap` or `bazel run @nodejs//:yarn` (called under the hood by bazel) + if (argv.cooked.includes('kbn') || !!process.env.BAZEL_YARN_INSTALL) { + // all good, trying to install deps using `kbn` or bazel directly + return; + } + + if (argv.cooked.includes('install')) { + console.log('\nWARNING: When installing dependencies, prefer `yarn kbn bootstrap`\n'); + } + } catch (e) { + // if it fails we do nothing, as this is just intended to be a helpful message } -} catch (e) { - // if it fails we do nothing, as this is just intended to be a helpful message -} +})(); diff --git a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap index 6301583c130a5..9e7906250949e 100644 --- a/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap +++ b/src/core/public/chrome/ui/header/__snapshots__/header.test.tsx.snap @@ -4271,55 +4271,6 @@ exports[`Header renders 1`] = ` "thrownError": null, } } - breadcrumbsAppendExtension$={ - BehaviorSubject { - "_isScalar": false, - "_value": undefined, - "closed": false, - "hasError": false, - "isStopped": false, - "observers": Array [ - Subscriber { - "_parentOrParents": null, - "_subscriptions": Array [ - SubjectSubscription { - "_parentOrParents": [Circular], - "_subscriptions": null, - "closed": false, - "subject": [Circular], - "subscriber": [Circular], - }, - ], - "closed": false, - "destination": SafeSubscriber { - "_complete": undefined, - "_context": [Circular], - "_error": undefined, - "_next": [Function], - "_parentOrParents": null, - "_parentSubscriber": [Circular], - "_subscriptions": null, - "closed": false, - "destination": Object { - "closed": true, - "complete": [Function], - "error": [Function], - "next": [Function], - }, - "isStopped": false, - "syncErrorThrowable": false, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - "isStopped": false, - "syncErrorThrowable": true, - "syncErrorThrown": false, - "syncErrorValue": null, - }, - ], - "thrownError": null, - } - } > ({ htmlIdGenerator: () => () => 'mockId', @@ -71,6 +72,9 @@ describe('Header', () => { const recentlyAccessed$ = new BehaviorSubject([ { link: '', label: 'dashboard', id: 'dashboard' }, ]); + const breadcrumbsAppendExtension$ = new BehaviorSubject< + undefined | ChromeBreadcrumbsAppendExtension + >(undefined); const component = mountWithIntl(
{ recentlyAccessed$={recentlyAccessed$} isLocked$={isLocked$} customNavLink$={customNavLink$} + breadcrumbsAppendExtension$={breadcrumbsAppendExtension$} /> ); expect(component.find('EuiHeader').exists()).toBeFalsy(); @@ -93,5 +98,19 @@ describe('Header', () => { component.update(); expect(component.find('nav[aria-label="Primary"]').exists()).toBeTruthy(); expect(component).toMatchSnapshot(); + + act(() => + breadcrumbsAppendExtension$.next({ + content: (root: HTMLDivElement) => { + root.innerHTML = '
__render__
'; + return () => (root.innerHTML = ''); + }, + }) + ); + component.update(); + expect(component.find('HeaderExtension').exists()).toBeTruthy(); + expect( + component.find('HeaderExtension').getDOMNode().querySelector('.my-extension') + ).toBeTruthy(); }); }); diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx index ad13ada3adfb8..8bb3de8c6708a 100644 --- a/src/core/public/chrome/ui/header/header.tsx +++ b/src/core/public/chrome/ui/header/header.tsx @@ -7,6 +7,7 @@ */ import { + EuiFlexGroup, EuiHeader, EuiHeaderSection, EuiHeaderSectionItem, @@ -40,6 +41,7 @@ import { HeaderHelpMenu } from './header_help_menu'; import { HeaderLogo } from './header_logo'; import { HeaderNavControls } from './header_nav_controls'; import { HeaderActionMenu } from './header_action_menu'; +import { HeaderExtension } from './header_extension'; export interface HeaderProps { kibanaVersion: string; @@ -73,11 +75,13 @@ export function Header({ basePath, onIsLockedUpdate, homeHref, + breadcrumbsAppendExtension$, ...observables }: HeaderProps) { const isVisible = useObservable(observables.isVisible$, false); const isLocked = useObservable(observables.isLocked$, false); const [isNavOpen, setIsNavOpen] = useState(false); + const breadcrumbsAppendExtension = useObservable(breadcrumbsAppendExtension$); if (!isVisible) { return ; @@ -87,6 +91,10 @@ export function Header({ const navId = htmlIdGenerator()(); const className = classnames('hide-for-sharing', 'headerGlobalNav'); + const Breadcrumbs = ( + + ); + return ( <>
@@ -157,11 +165,23 @@ export function Header({ - + {!breadcrumbsAppendExtension ? ( + Breadcrumbs + ) : ( + + {Breadcrumbs} + + + )} diff --git a/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx b/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx index 33314f69278fb..34f741caf850c 100644 --- a/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx +++ b/src/core/public/chrome/ui/header/header_breadcrumbs.test.tsx @@ -11,17 +11,12 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { BehaviorSubject } from 'rxjs'; import { HeaderBreadcrumbs } from './header_breadcrumbs'; -import { ChromeBreadcrumbsAppendExtension } from '../../chrome_service'; describe('HeaderBreadcrumbs', () => { it('renders updates to the breadcrumbs$ observable', () => { const breadcrumbs$ = new BehaviorSubject([{ text: 'First' }]); const wrapper = mount( - + ); expect(wrapper.find('.euiBreadcrumb')).toMatchSnapshot(); @@ -33,29 +28,4 @@ describe('HeaderBreadcrumbs', () => { wrapper.update(); expect(wrapper.find('.euiBreadcrumb')).toMatchSnapshot(); }); - - it('renders breadcrumbs extension', () => { - const breadcrumbs$ = new BehaviorSubject([{ text: 'First' }]); - const breadcrumbsAppendExtension$ = new BehaviorSubject< - undefined | ChromeBreadcrumbsAppendExtension - >({ - content: (root: HTMLDivElement) => { - root.innerHTML = '
__render__
'; - return () => (root.innerHTML = ''); - }, - }); - - const wrapper = mount( - - ); - - expect(wrapper.find('.euiBreadcrumb').getDOMNode().querySelector('my-extension')).toBeDefined(); - act(() => breadcrumbsAppendExtension$.next(undefined)); - wrapper.update(); - expect(wrapper.find('.euiBreadcrumb').getDOMNode().querySelector('my-extension')).toBeNull(); - }); }); diff --git a/src/core/public/chrome/ui/header/header_breadcrumbs.tsx b/src/core/public/chrome/ui/header/header_breadcrumbs.tsx index 3b9cb22f56d81..3797389fe4842 100644 --- a/src/core/public/chrome/ui/header/header_breadcrumbs.tsx +++ b/src/core/public/chrome/ui/header/header_breadcrumbs.tsx @@ -6,24 +6,21 @@ * Public License, v 1. */ -import { EuiFlexGroup, EuiHeaderBreadcrumbs } from '@elastic/eui'; +import { EuiHeaderBreadcrumbs } from '@elastic/eui'; import classNames from 'classnames'; import React from 'react'; import useObservable from 'react-use/lib/useObservable'; import { Observable } from 'rxjs'; -import { ChromeBreadcrumb, ChromeBreadcrumbsAppendExtension } from '../../chrome_service'; -import { HeaderExtension } from './header_extension'; +import { ChromeBreadcrumb } from '../../chrome_service'; interface Props { appTitle$: Observable; breadcrumbs$: Observable; - breadcrumbsAppendExtension$: Observable; } -export function HeaderBreadcrumbs({ appTitle$, breadcrumbs$, breadcrumbsAppendExtension$ }: Props) { +export function HeaderBreadcrumbs({ appTitle$, breadcrumbs$ }: Props) { const appTitle = useObservable(appTitle$, 'Kibana'); const breadcrumbs = useObservable(breadcrumbs$, []); - const breadcrumbsAppendExtension = useObservable(breadcrumbsAppendExtension$); let crumbs = breadcrumbs; if (breadcrumbs.length === 0 && appTitle) { @@ -40,14 +37,5 @@ export function HeaderBreadcrumbs({ appTitle$, breadcrumbs$, breadcrumbsAppendEx ), })); - if (breadcrumbsAppendExtension && crumbs[crumbs.length - 1]) { - const lastCrumb = crumbs[crumbs.length - 1]; - lastCrumb.text = ( - -
{lastCrumb.text}
- -
- ); - } return ; } diff --git a/src/core/public/chrome/ui/header/header_extension.tsx b/src/core/public/chrome/ui/header/header_extension.tsx index 53f21d42c534f..c5a101476bdb4 100644 --- a/src/core/public/chrome/ui/header/header_extension.tsx +++ b/src/core/public/chrome/ui/header/header_extension.tsx @@ -12,6 +12,7 @@ import { MountPoint } from '../../../types'; interface Props { extension?: MountPoint; display?: 'block' | 'inlineBlock'; + containerClassName?: string; } export class HeaderExtension extends React.Component { @@ -39,6 +40,7 @@ export class HeaderExtension extends React.Component { return (
); diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index c732fc7823b62..885e3e2382433 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -226,6 +226,7 @@ export class DocLinksService { createPipeline: `${ELASTICSEARCH_DOCS}put-pipeline-api.html`, createTransformRequest: `${ELASTICSEARCH_DOCS}put-transform.html#put-transform-request-body`, executeWatchActionModes: `${ELASTICSEARCH_DOCS}watcher-api-execute-watch.html#watcher-api-execute-watch-action-mode`, + indexExists: `${ELASTICSEARCH_DOCS}indices-exists.html`, openIndex: `${ELASTICSEARCH_DOCS}indices-open-close.html`, putComponentTemplate: `${ELASTICSEARCH_DOCS}indices-component-template.html`, painlessExecute: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/painless/${DOC_LINK_VERSION}/painless-execute-api.html`, @@ -357,6 +358,7 @@ export interface DocLinksStart { createPipeline: string; createTransformRequest: string; executeWatchActionModes: string; + indexExists: string; openIndex: string; putComponentTemplate: string; painlessExecute: string; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 0097127924a5c..37ebbcaa752af 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -575,6 +575,7 @@ export interface DocLinksStart { createPipeline: string; createTransformRequest: string; executeWatchActionModes: string; + indexExists: string; openIndex: string; putComponentTemplate: string; painlessExecute: string; diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts index dac93ff29b68f..e6fd5004dd7d5 100644 --- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts +++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts @@ -14,6 +14,16 @@ import { loggingSystemMock } from '../../../logging/logging_system.mock'; import { SavedObjectTypeRegistry } from '../../saved_objects_type_registry'; import { SavedObjectsType } from '../../types'; import { errors as esErrors } from '@elastic/elasticsearch'; +import { DocumentMigrator } from '../core/document_migrator'; +jest.mock('../core/document_migrator', () => { + return { + // Create a mock for spying on the constructor + DocumentMigrator: jest.fn().mockImplementation((...args) => { + const { DocumentMigrator: RealDocMigrator } = jest.requireActual('../core/document_migrator'); + return new RealDocMigrator(args[0]); + }), + }; +}); const createRegistry = (types: Array>) => { const registry = new SavedObjectTypeRegistry(); @@ -31,12 +41,16 @@ const createRegistry = (types: Array>) => { }; describe('KibanaMigrator', () => { + beforeEach(() => { + (DocumentMigrator as jest.Mock).mockClear(); + }); describe('constructor', () => { it('coerces the current Kibana version if it has a hyphen', () => { const options = mockOptions(); options.kibanaVersion = '3.2.1-SNAPSHOT'; const migrator = new KibanaMigrator(options); expect(migrator.kibanaVersion).toEqual('3.2.1'); + expect((DocumentMigrator as jest.Mock).mock.calls[0][0].kibanaVersion).toEqual('3.2.1'); }); }); describe('getActiveMappings', () => { @@ -105,8 +119,8 @@ describe('KibanaMigrator', () => { const migrator = new KibanaMigrator(options); - expect(() => migrator.runMigrations()).rejects.toThrow( - /Migrations are not ready. Make sure prepareMigrations is called first./i + await expect(() => migrator.runMigrations()).toThrowErrorMatchingInlineSnapshot( + `"Migrations are not ready. Make sure prepareMigrations is called first."` ); }); diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts index ecef84a6e297c..1a4611b491419 100644 --- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts +++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts @@ -12,6 +12,7 @@ */ import { BehaviorSubject } from 'rxjs'; +import Semver from 'semver'; import { KibanaConfigType } from '../../../kibana_config'; import { ElasticsearchClient } from '../../../elasticsearch'; import { Logger } from '../../../logging'; @@ -97,7 +98,7 @@ export class KibanaMigrator { this.log = logger; this.kibanaVersion = kibanaVersion.split('-')[0]; // coerce a semver-like string (x.y.z-SNAPSHOT) or prerelease version (x.y.z-alpha) to a regular semver (x.y.z); this.documentMigrator = new DocumentMigrator({ - kibanaVersion, + kibanaVersion: this.kibanaVersion, typeRegistry, log: this.log, }); @@ -163,6 +164,15 @@ export class KibanaMigrator { registry: this.typeRegistry, }); + this.log.debug('Applying registered migrations for the following saved object types:'); + Object.entries(this.documentMigrator.migrationVersion) + .sort(([t1, v1], [t2, v2]) => { + return Semver.compare(v1, v2); + }) + .forEach(([type, migrationVersion]) => { + this.log.debug(`migrationVersion: ${migrationVersion} saved object type: ${type}`); + }); + const migrators = Object.keys(indexMap).map((index) => { // TODO migrationsV2: remove old migrations algorithm if (this.savedObjectsConfig.enableV2) { diff --git a/src/core/server/saved_objects/migrationsv2/model.test.ts b/src/core/server/saved_objects/migrationsv2/model.test.ts index a9aa69960b1c2..d5ab85c54a728 100644 --- a/src/core/server/saved_objects/migrationsv2/model.test.ts +++ b/src/core/server/saved_objects/migrationsv2/model.test.ts @@ -182,21 +182,6 @@ describe('migrations v2 model', () => { versionAlias: '.kibana_7.11.0', versionIndex: '.kibana_7.11.0_001', }; - const mappingsWithUnknownType = { - properties: { - disabled_saved_object_type: { - properties: { - value: { type: 'keyword' }, - }, - }, - }, - _meta: { - migrationMappingPropertyHashes: { - disabled_saved_object_type: '7997cf5a56cc02bdc9c93361bde732b0', - }, - }, - }; - test('INIT -> OUTDATED_DOCUMENTS_SEARCH if .kibana is already pointing to the target index', () => { const res: ResponseType<'INIT'> = Either.right({ '.kibana_7.11.0_001': { @@ -204,27 +189,38 @@ describe('migrations v2 model', () => { '.kibana': {}, '.kibana_7.11.0': {}, }, - mappings: mappingsWithUnknownType, + mappings: { + properties: { + disabled_saved_object_type: { + properties: { + value: { type: 'keyword' }, + }, + }, + }, + _meta: { + migrationMappingPropertyHashes: { + disabled_saved_object_type: '7997cf5a56cc02bdc9c93361bde732b0', + }, + }, + }, settings: {}, }, }); const newState = model(initState, res); expect(newState.controlState).toEqual('OUTDATED_DOCUMENTS_SEARCH'); - // This snapshot asserts that we merge the - // migrationMappingPropertyHashes of the existing index, but we leave - // the mappings for the disabled_saved_object_type untouched. There - // might be another Kibana instance that knows about this type and - // needs these mappings in place. expect(newState.targetIndexMappings).toMatchInlineSnapshot(` Object { "_meta": Object { "migrationMappingPropertyHashes": Object { - "disabled_saved_object_type": "7997cf5a56cc02bdc9c93361bde732b0", "new_saved_object_type": "4a11183eee21e6fbad864f7a30b39ad0", }, }, "properties": Object { + "disabled_saved_object_type": Object { + "dynamic": false, + "properties": Object {}, + }, "new_saved_object_type": Object { "properties": Object { "value": Object { @@ -275,7 +271,7 @@ describe('migrations v2 model', () => { '.kibana': {}, '.kibana_7.12.0': {}, }, - mappings: mappingsWithUnknownType, + mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, settings: {}, }, '.kibana_7.11.0_001': { @@ -292,37 +288,12 @@ describe('migrations v2 model', () => { sourceIndex: Option.some('.kibana_7.invalid.0_001'), targetIndex: '.kibana_7.11.0_001', }); - // This snapshot asserts that we disable the unknown saved object - // type. Because it's mappings are disabled, we also don't copy the - // `_meta.migrationMappingPropertyHashes` for the disabled type. - expect(newState.targetIndexMappings).toMatchInlineSnapshot(` - Object { - "_meta": Object { - "migrationMappingPropertyHashes": Object { - "new_saved_object_type": "4a11183eee21e6fbad864f7a30b39ad0", - }, - }, - "properties": Object { - "disabled_saved_object_type": Object { - "dynamic": false, - "properties": Object {}, - }, - "new_saved_object_type": Object { - "properties": Object { - "value": Object { - "type": "text", - }, - }, - }, - }, - } - `); }); test('INIT -> SET_SOURCE_WRITE_BLOCK when migrating from a v2 migrations index (>= 7.11.0)', () => { const res: ResponseType<'INIT'> = Either.right({ '.kibana_7.11.0_001': { aliases: { '.kibana': {}, '.kibana_7.11.0': {} }, - mappings: mappingsWithUnknownType, + mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, settings: {}, }, '.kibana_3': { @@ -348,31 +319,6 @@ describe('migrations v2 model', () => { sourceIndex: Option.some('.kibana_7.11.0_001'), targetIndex: '.kibana_7.12.0_001', }); - // This snapshot asserts that we disable the unknown saved object - // type. Because it's mappings are disabled, we also don't copy the - // `_meta.migrationMappingPropertyHashes` for the disabled type. - expect(newState.targetIndexMappings).toMatchInlineSnapshot(` - Object { - "_meta": Object { - "migrationMappingPropertyHashes": Object { - "new_saved_object_type": "4a11183eee21e6fbad864f7a30b39ad0", - }, - }, - "properties": Object { - "disabled_saved_object_type": Object { - "dynamic": false, - "properties": Object {}, - }, - "new_saved_object_type": Object { - "properties": Object { - "value": Object { - "type": "text", - }, - }, - }, - }, - } - `); expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); @@ -382,7 +328,7 @@ describe('migrations v2 model', () => { aliases: { '.kibana': {}, }, - mappings: mappingsWithUnknownType, + mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, settings: {}, }, }); @@ -393,31 +339,6 @@ describe('migrations v2 model', () => { sourceIndex: Option.some('.kibana_3'), targetIndex: '.kibana_7.11.0_001', }); - // This snapshot asserts that we disable the unknown saved object - // type. Because it's mappings are disabled, we also don't copy the - // `_meta.migrationMappingPropertyHashes` for the disabled type. - expect(newState.targetIndexMappings).toMatchInlineSnapshot(` - Object { - "_meta": Object { - "migrationMappingPropertyHashes": Object { - "new_saved_object_type": "4a11183eee21e6fbad864f7a30b39ad0", - }, - }, - "properties": Object { - "disabled_saved_object_type": Object { - "dynamic": false, - "properties": Object {}, - }, - "new_saved_object_type": Object { - "properties": Object { - "value": Object { - "type": "text", - }, - }, - }, - }, - } - `); expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); @@ -425,7 +346,7 @@ describe('migrations v2 model', () => { const res: ResponseType<'INIT'> = Either.right({ '.kibana': { aliases: {}, - mappings: mappingsWithUnknownType, + mappings: { properties: {}, _meta: {} }, settings: {}, }, }); @@ -436,31 +357,6 @@ describe('migrations v2 model', () => { sourceIndex: Option.some('.kibana_pre6.5.0_001'), targetIndex: '.kibana_7.11.0_001', }); - // This snapshot asserts that we disable the unknown saved object - // type. Because it's mappings are disabled, we also don't copy the - // `_meta.migrationMappingPropertyHashes` for the disabled type. - expect(newState.targetIndexMappings).toMatchInlineSnapshot(` - Object { - "_meta": Object { - "migrationMappingPropertyHashes": Object { - "new_saved_object_type": "4a11183eee21e6fbad864f7a30b39ad0", - }, - }, - "properties": Object { - "disabled_saved_object_type": Object { - "dynamic": false, - "properties": Object {}, - }, - "new_saved_object_type": Object { - "properties": Object { - "value": Object { - "type": "text", - }, - }, - }, - }, - } - `); expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); @@ -470,7 +366,7 @@ describe('migrations v2 model', () => { aliases: { 'my-saved-objects': {}, }, - mappings: mappingsWithUnknownType, + mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, settings: {}, }, }); @@ -490,31 +386,6 @@ describe('migrations v2 model', () => { sourceIndex: Option.some('my-saved-objects_3'), targetIndex: 'my-saved-objects_7.11.0_001', }); - // This snapshot asserts that we disable the unknown saved object - // type. Because it's mappings are disabled, we also don't copy the - // `_meta.migrationMappingPropertyHashes` for the disabled type. - expect(newState.targetIndexMappings).toMatchInlineSnapshot(` - Object { - "_meta": Object { - "migrationMappingPropertyHashes": Object { - "new_saved_object_type": "4a11183eee21e6fbad864f7a30b39ad0", - }, - }, - "properties": Object { - "disabled_saved_object_type": Object { - "dynamic": false, - "properties": Object {}, - }, - "new_saved_object_type": Object { - "properties": Object { - "value": Object { - "type": "text", - }, - }, - }, - }, - } - `); expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); @@ -524,7 +395,7 @@ describe('migrations v2 model', () => { aliases: { 'my-saved-objects': {}, }, - mappings: mappingsWithUnknownType, + mappings: { properties: {}, _meta: { migrationMappingPropertyHashes: {} } }, settings: {}, }, }); @@ -545,31 +416,6 @@ describe('migrations v2 model', () => { sourceIndex: Option.some('my-saved-objects_7.11.0'), targetIndex: 'my-saved-objects_7.12.0_001', }); - // This snapshot asserts that we disable the unknown saved object - // type. Because it's mappings are disabled, we also don't copy the - // `_meta.migrationMappingPropertyHashes` for the disabled type. - expect(newState.targetIndexMappings).toMatchInlineSnapshot(` - Object { - "_meta": Object { - "migrationMappingPropertyHashes": Object { - "new_saved_object_type": "4a11183eee21e6fbad864f7a30b39ad0", - }, - }, - "properties": Object { - "disabled_saved_object_type": Object { - "dynamic": false, - "properties": Object {}, - }, - "new_saved_object_type": Object { - "properties": Object { - "value": Object { - "type": "text", - }, - }, - }, - }, - } - `); expect(newState.retryCount).toEqual(0); expect(newState.retryDelay).toEqual(0); }); diff --git a/src/core/server/saved_objects/migrationsv2/model.ts b/src/core/server/saved_objects/migrationsv2/model.ts index 3556bb611bb67..1119edde8e268 100644 --- a/src/core/server/saved_objects/migrationsv2/model.ts +++ b/src/core/server/saved_objects/migrationsv2/model.ts @@ -60,13 +60,13 @@ function throwBadResponse(state: State, res: any): never { * Merge the _meta.migrationMappingPropertyHashes mappings of an index with * the given target mappings. * - * @remarks When another instance already completed a migration, the existing - * target index might contain documents and mappings created by a plugin that - * is disabled in the current Kibana instance performing this migration. - * Mapping updates are commutative (deeply merged) by Elasticsearch, except - * for the `_meta` key. By merging the `_meta.migrationMappingPropertyHashes` - * mappings from the existing target index index into the targetMappings we - * ensure that any `migrationPropertyHashes` for disabled plugins aren't lost. + * @remarks Mapping updates are commutative (deeply merged) by Elasticsearch, + * except for the _meta key. The source index we're migrating from might + * contain documents created by a plugin that is disabled in the Kibana + * instance performing this migration. We merge the + * _meta.migrationMappingPropertyHashes mappings from the source index into + * the targetMappings to ensure that any `migrationPropertyHashes` for + * disabled plugins aren't lost. * * Right now we don't use these `migrationPropertyHashes` but it could be used * in the future to detect if mappings were changed. If mappings weren't @@ -209,7 +209,7 @@ export const model = (currentState: State, resW: ResponseType): // index sourceIndex: Option.none, targetIndex: `${stateP.indexPrefix}_${stateP.kibanaVersion}_001`, - targetIndexMappings: mergeMigrationMappingPropertyHashes( + targetIndexMappings: disableUnknownTypeMappingFields( stateP.targetIndexMappings, indices[aliases[stateP.currentAlias]].mappings ), @@ -242,7 +242,7 @@ export const model = (currentState: State, resW: ResponseType): controlState: 'SET_SOURCE_WRITE_BLOCK', sourceIndex: Option.some(source) as Option.Some, targetIndex: target, - targetIndexMappings: disableUnknownTypeMappingFields( + targetIndexMappings: mergeMigrationMappingPropertyHashes( stateP.targetIndexMappings, indices[source].mappings ), @@ -279,7 +279,7 @@ export const model = (currentState: State, resW: ResponseType): controlState: 'LEGACY_SET_WRITE_BLOCK', sourceIndex: Option.some(legacyReindexTarget) as Option.Some, targetIndex: target, - targetIndexMappings: disableUnknownTypeMappingFields( + targetIndexMappings: mergeMigrationMappingPropertyHashes( stateP.targetIndexMappings, indices[stateP.legacyIndex].mappings ), diff --git a/src/core/server/saved_objects/routes/bulk_create.ts b/src/core/server/saved_objects/routes/bulk_create.ts index b85747985e523..6d57eaa3777e6 100644 --- a/src/core/server/saved_objects/routes/bulk_create.ts +++ b/src/core/server/saved_objects/routes/bulk_create.ts @@ -9,7 +9,6 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from '../../http'; import { CoreUsageDataSetup } from '../../core_usage_data'; -import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { coreUsageData: CoreUsageDataSetup; @@ -45,7 +44,7 @@ export const registerBulkCreateRoute = (router: IRouter, { coreUsageData }: Rout ), }, }, - catchAndReturnBoomErrors(async (context, req, res) => { + router.handleLegacyErrors(async (context, req, res) => { const { overwrite } = req.query; const usageStatsClient = coreUsageData.getClient(); diff --git a/src/core/server/saved_objects/routes/bulk_get.ts b/src/core/server/saved_objects/routes/bulk_get.ts index 580bf26a4e529..a260301633668 100644 --- a/src/core/server/saved_objects/routes/bulk_get.ts +++ b/src/core/server/saved_objects/routes/bulk_get.ts @@ -9,7 +9,6 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from '../../http'; import { CoreUsageDataSetup } from '../../core_usage_data'; -import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { coreUsageData: CoreUsageDataSetup; @@ -29,7 +28,7 @@ export const registerBulkGetRoute = (router: IRouter, { coreUsageData }: RouteDe ), }, }, - catchAndReturnBoomErrors(async (context, req, res) => { + router.handleLegacyErrors(async (context, req, res) => { const usageStatsClient = coreUsageData.getClient(); usageStatsClient.incrementSavedObjectsBulkGet({ request: req }).catch(() => {}); diff --git a/src/core/server/saved_objects/routes/bulk_update.ts b/src/core/server/saved_objects/routes/bulk_update.ts index e592adc72a244..f9b8d4a2f567f 100644 --- a/src/core/server/saved_objects/routes/bulk_update.ts +++ b/src/core/server/saved_objects/routes/bulk_update.ts @@ -9,7 +9,6 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from '../../http'; import { CoreUsageDataSetup } from '../../core_usage_data'; -import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { coreUsageData: CoreUsageDataSetup; @@ -40,7 +39,7 @@ export const registerBulkUpdateRoute = (router: IRouter, { coreUsageData }: Rout ), }, }, - catchAndReturnBoomErrors(async (context, req, res) => { + router.handleLegacyErrors(async (context, req, res) => { const usageStatsClient = coreUsageData.getClient(); usageStatsClient.incrementSavedObjectsBulkUpdate({ request: req }).catch(() => {}); diff --git a/src/core/server/saved_objects/routes/create.ts b/src/core/server/saved_objects/routes/create.ts index f6043ca96398d..fd256abac3526 100644 --- a/src/core/server/saved_objects/routes/create.ts +++ b/src/core/server/saved_objects/routes/create.ts @@ -9,7 +9,6 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from '../../http'; import { CoreUsageDataSetup } from '../../core_usage_data'; -import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { coreUsageData: CoreUsageDataSetup; @@ -44,7 +43,7 @@ export const registerCreateRoute = (router: IRouter, { coreUsageData }: RouteDep }), }, }, - catchAndReturnBoomErrors(async (context, req, res) => { + router.handleLegacyErrors(async (context, req, res) => { const { type, id } = req.params; const { overwrite } = req.query; const { diff --git a/src/core/server/saved_objects/routes/delete.ts b/src/core/server/saved_objects/routes/delete.ts index b127f64b74a0c..a7846c3dc845b 100644 --- a/src/core/server/saved_objects/routes/delete.ts +++ b/src/core/server/saved_objects/routes/delete.ts @@ -9,7 +9,6 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from '../../http'; import { CoreUsageDataSetup } from '../../core_usage_data'; -import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { coreUsageData: CoreUsageDataSetup; @@ -29,7 +28,7 @@ export const registerDeleteRoute = (router: IRouter, { coreUsageData }: RouteDep }), }, }, - catchAndReturnBoomErrors(async (context, req, res) => { + router.handleLegacyErrors(async (context, req, res) => { const { type, id } = req.params; const { force } = req.query; diff --git a/src/core/server/saved_objects/routes/export.ts b/src/core/server/saved_objects/routes/export.ts index f064cf1ca0ec1..9b40855afec2e 100644 --- a/src/core/server/saved_objects/routes/export.ts +++ b/src/core/server/saved_objects/routes/export.ts @@ -18,7 +18,7 @@ import { SavedObjectsExportByObjectOptions, SavedObjectsExportError, } from '../export'; -import { validateTypes, validateObjects, catchAndReturnBoomErrors } from './utils'; +import { validateTypes, validateObjects } from './utils'; interface RouteDependencies { config: SavedObjectConfig; @@ -163,7 +163,7 @@ export const registerExportRoute = ( }), }, }, - catchAndReturnBoomErrors(async (context, req, res) => { + router.handleLegacyErrors(async (context, req, res) => { const cleaned = cleanOptions(req.body); const supportedTypes = context.core.savedObjects.typeRegistry .getImportableAndExportableTypes() diff --git a/src/core/server/saved_objects/routes/find.ts b/src/core/server/saved_objects/routes/find.ts index c814fd310dc52..747070e54e5ad 100644 --- a/src/core/server/saved_objects/routes/find.ts +++ b/src/core/server/saved_objects/routes/find.ts @@ -9,7 +9,6 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from '../../http'; import { CoreUsageDataSetup } from '../../core_usage_data'; -import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { coreUsageData: CoreUsageDataSetup; @@ -50,7 +49,7 @@ export const registerFindRoute = (router: IRouter, { coreUsageData }: RouteDepen }), }, }, - catchAndReturnBoomErrors(async (context, req, res) => { + router.handleLegacyErrors(async (context, req, res) => { const query = req.query; const namespaces = diff --git a/src/core/server/saved_objects/routes/get.ts b/src/core/server/saved_objects/routes/get.ts index 2dd812f35cefd..c66a11dcf0cdd 100644 --- a/src/core/server/saved_objects/routes/get.ts +++ b/src/core/server/saved_objects/routes/get.ts @@ -9,7 +9,6 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from '../../http'; import { CoreUsageDataSetup } from '../../core_usage_data'; -import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { coreUsageData: CoreUsageDataSetup; @@ -26,7 +25,7 @@ export const registerGetRoute = (router: IRouter, { coreUsageData }: RouteDepend }), }, }, - catchAndReturnBoomErrors(async (context, req, res) => { + router.handleLegacyErrors(async (context, req, res) => { const { type, id } = req.params; const usageStatsClient = coreUsageData.getClient(); diff --git a/src/core/server/saved_objects/routes/import.ts b/src/core/server/saved_objects/routes/import.ts index 5fd132acafbed..6c4c759460ce3 100644 --- a/src/core/server/saved_objects/routes/import.ts +++ b/src/core/server/saved_objects/routes/import.ts @@ -13,7 +13,7 @@ import { IRouter } from '../../http'; import { CoreUsageDataSetup } from '../../core_usage_data'; import { SavedObjectConfig } from '../saved_objects_config'; import { SavedObjectsImportError } from '../import'; -import { catchAndReturnBoomErrors, createSavedObjectsStreamFromNdJson } from './utils'; +import { createSavedObjectsStreamFromNdJson } from './utils'; interface RouteDependencies { config: SavedObjectConfig; @@ -61,7 +61,7 @@ export const registerImportRoute = ( }), }, }, - catchAndReturnBoomErrors(async (context, req, res) => { + router.handleLegacyErrors(async (context, req, res) => { const { overwrite, createNewCopies } = req.query; const usageStatsClient = coreUsageData.getClient(); diff --git a/src/core/server/saved_objects/routes/migrate.ts b/src/core/server/saved_objects/routes/migrate.ts index 7c2f4bfb06710..8b347d4725b08 100644 --- a/src/core/server/saved_objects/routes/migrate.ts +++ b/src/core/server/saved_objects/routes/migrate.ts @@ -8,7 +8,6 @@ import { IRouter } from '../../http'; import { IKibanaMigrator } from '../migrations'; -import { catchAndReturnBoomErrors } from './utils'; export const registerMigrateRoute = ( router: IRouter, @@ -22,7 +21,7 @@ export const registerMigrateRoute = ( tags: ['access:migrateSavedObjects'], }, }, - catchAndReturnBoomErrors(async (context, req, res) => { + router.handleLegacyErrors(async (context, req, res) => { const migrator = await migratorPromise; await migrator.runMigrations({ rerun: true }); return res.ok({ diff --git a/src/core/server/saved_objects/routes/resolve_import_errors.ts b/src/core/server/saved_objects/routes/resolve_import_errors.ts index 6f0a3d028baf9..0cf976c30b311 100644 --- a/src/core/server/saved_objects/routes/resolve_import_errors.ts +++ b/src/core/server/saved_objects/routes/resolve_import_errors.ts @@ -13,7 +13,8 @@ import { IRouter } from '../../http'; import { CoreUsageDataSetup } from '../../core_usage_data'; import { SavedObjectConfig } from '../saved_objects_config'; import { SavedObjectsImportError } from '../import'; -import { catchAndReturnBoomErrors, createSavedObjectsStreamFromNdJson } from './utils'; +import { createSavedObjectsStreamFromNdJson } from './utils'; + interface RouteDependencies { config: SavedObjectConfig; coreUsageData: CoreUsageDataSetup; @@ -68,7 +69,7 @@ export const registerResolveImportErrorsRoute = ( }), }, }, - catchAndReturnBoomErrors(async (context, req, res) => { + router.handleLegacyErrors(async (context, req, res) => { const { createNewCopies } = req.query; const usageStatsClient = coreUsageData.getClient(); diff --git a/src/core/server/saved_objects/routes/update.ts b/src/core/server/saved_objects/routes/update.ts index dbc69f743df76..17cfd438d47bf 100644 --- a/src/core/server/saved_objects/routes/update.ts +++ b/src/core/server/saved_objects/routes/update.ts @@ -9,7 +9,6 @@ import { schema } from '@kbn/config-schema'; import { IRouter } from '../../http'; import { CoreUsageDataSetup } from '../../core_usage_data'; -import { catchAndReturnBoomErrors } from './utils'; interface RouteDependencies { coreUsageData: CoreUsageDataSetup; @@ -39,7 +38,7 @@ export const registerUpdateRoute = (router: IRouter, { coreUsageData }: RouteDep }), }, }, - catchAndReturnBoomErrors(async (context, req, res) => { + router.handleLegacyErrors(async (context, req, res) => { const { type, id } = req.params; const { attributes, version, references } = req.body; const options = { version, references }; diff --git a/src/core/server/saved_objects/routes/utils.test.ts b/src/core/server/saved_objects/routes/utils.test.ts index 1d7e86e288b18..ade7b03f6a8c2 100644 --- a/src/core/server/saved_objects/routes/utils.test.ts +++ b/src/core/server/saved_objects/routes/utils.test.ts @@ -9,15 +9,6 @@ import { createSavedObjectsStreamFromNdJson, validateTypes, validateObjects } from './utils'; import { Readable } from 'stream'; import { createPromiseFromStreams, createConcatStream } from '@kbn/utils'; -import { catchAndReturnBoomErrors } from './utils'; -import Boom from '@hapi/boom'; -import { - KibanaRequest, - RequestHandler, - RequestHandlerContext, - KibanaResponseFactory, - kibanaResponseFactory, -} from '../../'; async function readStreamToCompletion(stream: Readable) { return createPromiseFromStreams([stream, createConcatStream([])]); @@ -152,69 +143,3 @@ describe('validateObjects', () => { ).toBeUndefined(); }); }); - -describe('catchAndReturnBoomErrors', () => { - let context: RequestHandlerContext; - let request: KibanaRequest; - let response: KibanaResponseFactory; - - const createHandler = (handler: () => any): RequestHandler => () => { - return handler(); - }; - - beforeEach(() => { - context = {} as any; - request = {} as any; - response = kibanaResponseFactory; - }); - - it('should pass-though call parameters to the handler', async () => { - const handler = jest.fn(); - const wrapped = catchAndReturnBoomErrors(handler); - await wrapped(context, request, response); - expect(handler).toHaveBeenCalledWith(context, request, response); - }); - - it('should pass-though result from the handler', async () => { - const handler = createHandler(() => { - return 'handler-response'; - }); - const wrapped = catchAndReturnBoomErrors(handler); - const result = await wrapped(context, request, response); - expect(result).toBe('handler-response'); - }); - - it('should intercept and convert thrown Boom errors', async () => { - const handler = createHandler(() => { - throw Boom.notFound('not there'); - }); - const wrapped = catchAndReturnBoomErrors(handler); - const result = await wrapped(context, request, response); - expect(result.status).toBe(404); - expect(result.payload).toEqual({ - error: 'Not Found', - message: 'not there', - statusCode: 404, - }); - }); - - it('should re-throw non-Boom errors', async () => { - const handler = createHandler(() => { - throw new Error('something went bad'); - }); - const wrapped = catchAndReturnBoomErrors(handler); - await expect(wrapped(context, request, response)).rejects.toMatchInlineSnapshot( - `[Error: something went bad]` - ); - }); - - it('should re-throw Boom internal/500 errors', async () => { - const handler = createHandler(() => { - throw Boom.internal(); - }); - const wrapped = catchAndReturnBoomErrors(handler); - await expect(wrapped(context, request, response)).rejects.toMatchInlineSnapshot( - `[Error: Internal Server Error]` - ); - }); -}); diff --git a/src/core/server/saved_objects/routes/utils.ts b/src/core/server/saved_objects/routes/utils.ts index 269f3f0698561..b9e7df48a4b4c 100644 --- a/src/core/server/saved_objects/routes/utils.ts +++ b/src/core/server/saved_objects/routes/utils.ts @@ -7,11 +7,7 @@ */ import { Readable } from 'stream'; -import { - RequestHandlerWrapper, - SavedObject, - SavedObjectsExportResultDetails, -} from 'src/core/server'; +import { SavedObject, SavedObjectsExportResultDetails } from 'src/core/server'; import { createSplitStream, createMapStream, @@ -20,7 +16,6 @@ import { createListStream, createConcatStream, } from '@kbn/utils'; -import Boom from '@hapi/boom'; export async function createSavedObjectsStreamFromNdJson(ndJsonStream: Readable) { const savedObjects = await createPromiseFromStreams([ @@ -57,30 +52,3 @@ export function validateObjects( .join(', ')}`; } } - -/** - * Catches errors thrown by saved object route handlers and returns an error - * with the payload and statusCode of the boom error. - * - * This is very close to the core `router.handleLegacyErrors` except that it - * throws internal errors (statusCode: 500) so that the internal error's - * message get logged by Core. - * - * TODO: Remove once https://github.com/elastic/kibana/issues/65291 is fixed. - */ -export const catchAndReturnBoomErrors: RequestHandlerWrapper = (handler) => { - return async (context, request, response) => { - try { - return await handler(context, request, response); - } catch (e) { - if (Boom.isBoom(e) && e.output.statusCode !== 500) { - return response.customError({ - body: e.output.payload, - statusCode: e.output.statusCode, - headers: e.output.headers as { [key: string]: string }, - }); - } - throw e; - } - }; -}; diff --git a/src/core/server/saved_objects/service/lib/decorate_es_error.test.ts b/src/core/server/saved_objects/service/lib/decorate_es_error.test.ts index da1ebec2c0f7d..cc497ca6348b8 100644 --- a/src/core/server/saved_objects/service/lib/decorate_es_error.test.ts +++ b/src/core/server/saved_objects/service/lib/decorate_es_error.test.ts @@ -109,27 +109,6 @@ describe('savedObjectsClient/decorateEsError', () => { expect(SavedObjectsErrorHelpers.isNotFoundError(genericError)).toBe(true); }); - it('if saved objects index does not exist makes NotFound a SavedObjectsClient/generalError', () => { - const error = new esErrors.ResponseError( - elasticsearchClientMock.createApiResponse({ - statusCode: 404, - body: { - error: { - reason: - 'no such index [.kibana_8.0.0] and [require_alias] request flag is [true] and [.kibana_8.0.0] is not an alias', - }, - }, - }) - ); - expect(SavedObjectsErrorHelpers.isGeneralError(error)).toBe(false); - const genericError = decorateEsError(error); - expect(genericError.message).toEqual( - `Saved object index alias [.kibana_8.0.0] not found: Response Error` - ); - expect(genericError.output.statusCode).toBe(500); - expect(SavedObjectsErrorHelpers.isGeneralError(error)).toBe(true); - }); - it('makes BadRequest a SavedObjectsClient/BadRequest error', () => { const error = new esErrors.ResponseError( elasticsearchClientMock.createApiResponse({ statusCode: 400 }) diff --git a/src/core/server/saved_objects/service/lib/decorate_es_error.ts b/src/core/server/saved_objects/service/lib/decorate_es_error.ts index aabca2d602cb3..40f18c9c94c25 100644 --- a/src/core/server/saved_objects/service/lib/decorate_es_error.ts +++ b/src/core/server/saved_objects/service/lib/decorate_es_error.ts @@ -63,12 +63,6 @@ export function decorateEsError(error: EsErrors) { } if (responseErrors.isNotFound(error.statusCode)) { - const match = error?.meta?.body?.error?.reason?.match( - /no such index \[(.+)\] and \[require_alias\] request flag is \[true\] and \[.+\] is not an alias/ - ); - if (match?.length > 0) { - return SavedObjectsErrorHelpers.decorateIndexAliasNotFoundError(error, match[1]); - } return SavedObjectsErrorHelpers.createGenericNotFoundError(); } diff --git a/src/core/server/saved_objects/service/lib/errors.ts b/src/core/server/saved_objects/service/lib/errors.ts index c348196aaba21..f216e72efbcf8 100644 --- a/src/core/server/saved_objects/service/lib/errors.ts +++ b/src/core/server/saved_objects/service/lib/errors.ts @@ -135,19 +135,6 @@ export class SavedObjectsErrorHelpers { return decorate(Boom.notFound(), CODE_NOT_FOUND, 404); } - public static createIndexAliasNotFoundError(alias: string) { - return SavedObjectsErrorHelpers.decorateIndexAliasNotFoundError(Boom.internal(), alias); - } - - public static decorateIndexAliasNotFoundError(error: Error, alias: string) { - return decorate( - error, - CODE_GENERAL_ERROR, - 500, - `Saved object index alias [${alias}] not found` - ); - } - public static isNotFoundError(error: Error | DecoratedError) { return isSavedObjectsClientError(error) && error[code] === CODE_NOT_FOUND; } @@ -198,8 +185,4 @@ export class SavedObjectsErrorHelpers { public static decorateGeneralError(error: Error, reason?: string) { return decorate(error, CODE_GENERAL_ERROR, 500, reason); } - - public static isGeneralError(error: Error | DecoratedError) { - return isSavedObjectsClientError(error) && error[code] === CODE_GENERAL_ERROR; - } } diff --git a/src/core/server/saved_objects/service/lib/repository.test.js b/src/core/server/saved_objects/service/lib/repository.test.js index 68fdea0f9eb25..216e1c4bd2d3c 100644 --- a/src/core/server/saved_objects/service/lib/repository.test.js +++ b/src/core/server/saved_objects/service/lib/repository.test.js @@ -18,7 +18,6 @@ import { DocumentMigrator } from '../../migrations/core/document_migrator'; import { mockKibanaMigrator } from '../../migrations/kibana/kibana_migrator.mock'; import { elasticsearchClientMock } from '../../../elasticsearch/client/mocks'; import { esKuery } from '../../es_query'; -import { errors as EsErrors } from '@elastic/elasticsearch'; const { nodeTypes } = esKuery; jest.mock('./search_dsl/search_dsl', () => ({ getSearchDsl: jest.fn() })); @@ -4342,14 +4341,8 @@ describe('SavedObjectsRepository', () => { }); it(`throws when ES is unable to find the document during update`, async () => { - const notFoundError = new EsErrors.ResponseError( - elasticsearchClientMock.createApiResponse({ - statusCode: 404, - body: { error: { type: 'es_type', reason: 'es_reason' } }, - }) - ); client.update.mockResolvedValueOnce( - elasticsearchClientMock.createErrorTransportRequestPromise(notFoundError) + elasticsearchClientMock.createSuccessTransportRequestPromise({}, { statusCode: 404 }) ); await expectNotFoundError(type, id); expect(client.update).toHaveBeenCalledTimes(1); diff --git a/src/core/server/saved_objects/service/lib/repository.ts b/src/core/server/saved_objects/service/lib/repository.ts index da80971635a93..2993d4234bd2e 100644 --- a/src/core/server/saved_objects/service/lib/repository.ts +++ b/src/core/server/saved_objects/service/lib/repository.ts @@ -299,7 +299,6 @@ export class SavedObjectsRepository { refresh, body: raw._source, ...(overwrite && version ? decodeRequestVersion(version) : {}), - require_alias: true, }; const { body } = @@ -470,7 +469,6 @@ export class SavedObjectsRepository { const bulkResponse = bulkCreateParams.length ? await this.client.bulk({ refresh, - require_alias: true, body: bulkCreateParams, }) : undefined; @@ -1119,8 +1117,8 @@ export class SavedObjectsRepository { ...(Array.isArray(references) && { references }), }; - const { body } = await this.client - .update({ + const { body, statusCode } = await this.client.update( + { id: this._serializer.generateRawId(namespace, type, id), index: this.getIndexForType(type), ...getExpectedVersionProperties(version, preflightResult), @@ -1130,15 +1128,14 @@ export class SavedObjectsRepository { doc, }, _source_includes: ['namespace', 'namespaces', 'originId'], - require_alias: true, - }) - .catch((err) => { - if (SavedObjectsErrorHelpers.isNotFoundError(err)) { - // see "404s from missing index" above - throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); - } - throw err; - }); + }, + { ignore: [404] } + ); + + if (statusCode === 404) { + // see "404s from missing index" above + throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); + } const { originId } = body.get._source; let namespaces = []; @@ -1499,7 +1496,6 @@ export class SavedObjectsRepository { refresh, body: bulkUpdateParams, _source_includes: ['originId'], - require_alias: true, }) : undefined; @@ -1716,7 +1712,6 @@ export class SavedObjectsRepository { id: raw._id, index: this.getIndexForType(type), refresh, - require_alias: true, _source: 'true', body: { script: { @@ -1938,18 +1933,12 @@ export class SavedObjectsRepository { } } -function getBulkOperationError( - error: { type: string; reason?: string; index?: string }, - type: string, - id: string -) { +function getBulkOperationError(error: { type: string; reason?: string }, type: string, id: string) { switch (error.type) { case 'version_conflict_engine_exception': return errorContent(SavedObjectsErrorHelpers.createConflictError(type, id)); case 'document_missing_exception': return errorContent(SavedObjectsErrorHelpers.createGenericNotFoundError(type, id)); - case 'index_not_found_exception': - return errorContent(SavedObjectsErrorHelpers.createIndexAliasNotFoundError(error.index!)); default: return { message: error.reason || JSON.stringify(error), diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index f3191c5625f8d..40a12290be31b 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -2335,8 +2335,6 @@ export class SavedObjectsErrorHelpers { // (undocumented) static createGenericNotFoundError(type?: string | null, id?: string | null): DecoratedError; // (undocumented) - static createIndexAliasNotFoundError(alias: string): DecoratedError; - // (undocumented) static createInvalidVersionError(versionInput?: string): DecoratedError; // (undocumented) static createTooManyRequestsError(type: string, id: string): DecoratedError; @@ -2355,8 +2353,6 @@ export class SavedObjectsErrorHelpers { // (undocumented) static decorateGeneralError(error: Error, reason?: string): DecoratedError; // (undocumented) - static decorateIndexAliasNotFoundError(error: Error, alias: string): DecoratedError; - // (undocumented) static decorateNotAuthorizedError(error: Error, reason?: string): DecoratedError; // (undocumented) static decorateRequestEntityTooLargeError(error: Error, reason?: string): DecoratedError; @@ -2373,8 +2369,6 @@ export class SavedObjectsErrorHelpers { // (undocumented) static isForbiddenError(error: Error | DecoratedError): boolean; // (undocumented) - static isGeneralError(error: Error | DecoratedError): boolean; - // (undocumented) static isInvalidVersionError(error: Error | DecoratedError): boolean; // (undocumented) static isNotAuthorizedError(error: Error | DecoratedError): boolean; diff --git a/src/core/server/ui_settings/integration_tests/doc_exists.ts b/src/core/server/ui_settings/integration_tests/doc_exists.ts index d100b89af9609..aa6f98ddf2d03 100644 --- a/src/core/server/ui_settings/integration_tests/doc_exists.ts +++ b/src/core/server/ui_settings/integration_tests/doc_exists.ts @@ -8,7 +8,7 @@ import { getServices, chance } from './lib'; -export const docExistsSuite = (savedObjectsIndex: string) => () => { +export function docExistsSuite() { async function setup(options: any = {}) { const { initialSettings } = options; @@ -16,7 +16,7 @@ export const docExistsSuite = (savedObjectsIndex: string) => () => { // delete the kibana index to ensure we start fresh await callCluster('deleteByQuery', { - index: savedObjectsIndex, + index: kbnServer.config.get('kibana.index'), body: { conflicts: 'proceed', query: { match_all: {} }, @@ -212,4 +212,4 @@ export const docExistsSuite = (savedObjectsIndex: string) => () => { }); }); }); -}; +} diff --git a/src/core/server/ui_settings/integration_tests/doc_missing.ts b/src/core/server/ui_settings/integration_tests/doc_missing.ts index 822ffe398b87d..501976e3823f1 100644 --- a/src/core/server/ui_settings/integration_tests/doc_missing.ts +++ b/src/core/server/ui_settings/integration_tests/doc_missing.ts @@ -8,7 +8,7 @@ import { getServices, chance } from './lib'; -export const docMissingSuite = (savedObjectsIndex: string) => () => { +export function docMissingSuite() { // ensure the kibana index has no documents beforeEach(async () => { const { kbnServer, callCluster } = getServices(); @@ -22,7 +22,7 @@ export const docMissingSuite = (savedObjectsIndex: string) => () => { // delete all docs from kibana index to ensure savedConfig is not found await callCluster('deleteByQuery', { - index: savedObjectsIndex, + index: kbnServer.config.get('kibana.index'), body: { query: { match_all: {} }, }, @@ -136,4 +136,4 @@ export const docMissingSuite = (savedObjectsIndex: string) => () => { }); }); }); -}; +} diff --git a/src/core/server/ui_settings/integration_tests/doc_missing_and_index_read_only.ts b/src/core/server/ui_settings/integration_tests/doc_missing_and_index_read_only.ts index 997d51e36abdc..b2ff1b2f1d4ab 100644 --- a/src/core/server/ui_settings/integration_tests/doc_missing_and_index_read_only.ts +++ b/src/core/server/ui_settings/integration_tests/doc_missing_and_index_read_only.ts @@ -8,7 +8,7 @@ import { getServices, chance } from './lib'; -export const docMissingAndIndexReadOnlySuite = (savedObjectsIndex: string) => () => { +export function docMissingAndIndexReadOnlySuite() { // ensure the kibana index has no documents beforeEach(async () => { const { kbnServer, callCluster } = getServices(); @@ -22,7 +22,7 @@ export const docMissingAndIndexReadOnlySuite = (savedObjectsIndex: string) => () // delete all docs from kibana index to ensure savedConfig is not found await callCluster('deleteByQuery', { - index: savedObjectsIndex, + index: kbnServer.config.get('kibana.index'), body: { query: { match_all: {} }, }, @@ -30,7 +30,7 @@ export const docMissingAndIndexReadOnlySuite = (savedObjectsIndex: string) => () // set the index to read only await callCluster('indices.putSettings', { - index: savedObjectsIndex, + index: kbnServer.config.get('kibana.index'), body: { index: { blocks: { @@ -42,11 +42,11 @@ export const docMissingAndIndexReadOnlySuite = (savedObjectsIndex: string) => () }); afterEach(async () => { - const { callCluster } = getServices(); + const { kbnServer, callCluster } = getServices(); // disable the read only block await callCluster('indices.putSettings', { - index: savedObjectsIndex, + index: kbnServer.config.get('kibana.index'), body: { index: { blocks: { @@ -142,4 +142,4 @@ export const docMissingAndIndexReadOnlySuite = (savedObjectsIndex: string) => () }); }); }); -}; +} diff --git a/src/core/server/ui_settings/integration_tests/index.test.ts b/src/core/server/ui_settings/integration_tests/index.test.ts index e27e6c4e46874..f415f1d73de7d 100644 --- a/src/core/server/ui_settings/integration_tests/index.test.ts +++ b/src/core/server/ui_settings/integration_tests/index.test.ts @@ -6,25 +6,20 @@ * Public License, v 1. */ -import { Env } from '@kbn/config'; -import { REPO_ROOT } from '@kbn/dev-utils'; -import { getEnvOptions } from '@kbn/config/target/mocks'; import { startServers, stopServers } from './lib'; + import { docExistsSuite } from './doc_exists'; import { docMissingSuite } from './doc_missing'; import { docMissingAndIndexReadOnlySuite } from './doc_missing_and_index_read_only'; -const kibanaVersion = Env.createDefault(REPO_ROOT, getEnvOptions()).packageInfo.version; -const savedObjectIndex = `.kibana_${kibanaVersion}_001`; - describe('uiSettings/routes', function () { jest.setTimeout(10000); beforeAll(startServers); /* eslint-disable jest/valid-describe */ - describe('doc missing', docMissingSuite(savedObjectIndex)); - describe('doc missing and index readonly', docMissingAndIndexReadOnlySuite(savedObjectIndex)); - describe('doc exists', docExistsSuite(savedObjectIndex)); + describe('doc missing', docMissingSuite); + describe('doc missing and index readonly', docMissingAndIndexReadOnlySuite); + describe('doc exists', docExistsSuite); /* eslint-enable jest/valid-describe */ afterAll(stopServers); }); diff --git a/src/core/server/ui_settings/integration_tests/lib/servers.ts b/src/core/server/ui_settings/integration_tests/lib/servers.ts index f181272030ae1..b5198b19007d0 100644 --- a/src/core/server/ui_settings/integration_tests/lib/servers.ts +++ b/src/core/server/ui_settings/integration_tests/lib/servers.ts @@ -37,6 +37,9 @@ export async function startServers() { adjustTimeout: (t) => jest.setTimeout(t), settings: { kbn: { + migrations: { + enableV2: false, + }, uiSettings: { overrides: { foo: 'bar', diff --git a/src/core/test_helpers/kbn_server.ts b/src/core/test_helpers/kbn_server.ts index 6fe6819a0981a..0007e1fcca0a5 100644 --- a/src/core/test_helpers/kbn_server.ts +++ b/src/core/test_helpers/kbn_server.ts @@ -40,7 +40,7 @@ const DEFAULTS_SETTINGS = { }, logging: { silent: true }, plugins: {}, - migrations: { skip: false }, + migrations: { skip: true }, }; const DEFAULT_SETTINGS_WITH_CORE_PLUGINS = { diff --git a/src/dev/build/args.test.ts b/src/dev/build/args.test.ts index 745b9d0b910c8..798f7db056232 100644 --- a/src/dev/build/args.test.ts +++ b/src/dev/build/args.test.ts @@ -31,7 +31,7 @@ it('build default and oss dist for current platform, without packages, by defaul "createArchives": true, "createDebPackage": false, "createDockerCentOS": false, - "createDockerContexts": false, + "createDockerContexts": true, "createDockerUBI": false, "createRpmPackage": false, "downloadFreshNode": true, @@ -79,7 +79,7 @@ it('limits packages if --rpm passed with --all-platforms', () => { "createArchives": true, "createDebPackage": false, "createDockerCentOS": false, - "createDockerContexts": false, + "createDockerContexts": true, "createDockerUBI": false, "createRpmPackage": true, "downloadFreshNode": true, @@ -103,7 +103,7 @@ it('limits packages if --deb passed with --all-platforms', () => { "createArchives": true, "createDebPackage": true, "createDockerCentOS": false, - "createDockerContexts": false, + "createDockerContexts": true, "createDockerUBI": false, "createRpmPackage": false, "downloadFreshNode": true, @@ -128,7 +128,7 @@ it('limits packages if --docker passed with --all-platforms', () => { "createArchives": true, "createDebPackage": false, "createDockerCentOS": true, - "createDockerContexts": false, + "createDockerContexts": true, "createDockerUBI": true, "createRpmPackage": false, "downloadFreshNode": true, @@ -160,7 +160,7 @@ it('limits packages if --docker passed with --skip-docker-ubi and --all-platform "createArchives": true, "createDebPackage": false, "createDockerCentOS": true, - "createDockerContexts": false, + "createDockerContexts": true, "createDockerUBI": false, "createRpmPackage": false, "downloadFreshNode": true, diff --git a/src/dev/build/args.ts b/src/dev/build/args.ts index 2d26d7db3a5e3..e51043ea40fec 100644 --- a/src/dev/build/args.ts +++ b/src/dev/build/args.ts @@ -22,7 +22,7 @@ export function readCliArgs(argv: string[]) { 'rpm', 'deb', 'docker-images', - 'docker-contexts', + 'skip-docker-contexts', 'skip-docker-ubi', 'skip-docker-centos', 'release', @@ -45,7 +45,6 @@ export function readCliArgs(argv: string[]) { rpm: null, deb: null, 'docker-images': null, - 'docker-contexts': null, oss: null, 'version-qualifier': '', }, @@ -72,7 +71,7 @@ export function readCliArgs(argv: string[]) { // In order to build a docker image we always need // to generate all the platforms - if (flags['docker-images'] || flags['docker-contexts']) { + if (flags['docker-images']) { flags['all-platforms'] = true; } @@ -82,12 +81,7 @@ export function readCliArgs(argv: string[]) { } // build all if no flags specified - if ( - flags.rpm === null && - flags.deb === null && - flags['docker-images'] === null && - flags['docker-contexts'] === null - ) { + if (flags.rpm === null && flags.deb === null && flags['docker-images'] === null) { return true; } @@ -106,7 +100,7 @@ export function readCliArgs(argv: string[]) { createDockerCentOS: isOsPackageDesired('docker-images') && !Boolean(flags['skip-docker-centos']), createDockerUBI: isOsPackageDesired('docker-images') && !Boolean(flags['skip-docker-ubi']), - createDockerContexts: isOsPackageDesired('docker-contexts'), + createDockerContexts: !Boolean(flags['skip-docker-contexts']), targetAllPlatforms: Boolean(flags['all-platforms']), }; diff --git a/src/dev/build/build_distributables.ts b/src/dev/build/build_distributables.ts index df4ba45517cc1..771401a49a243 100644 --- a/src/dev/build/build_distributables.ts +++ b/src/dev/build/build_distributables.ts @@ -107,7 +107,7 @@ export async function buildDistributables(log: ToolingLog, options: BuildOptions } if (options.createDockerContexts) { - // control w/ --docker-contexts or --skip-os-packages + // control w/ --skip-docker-contexts await run(Tasks.CreateDockerContexts); } diff --git a/src/dev/build/tasks/clean_tasks.ts b/src/dev/build/tasks/clean_tasks.ts index 588e7a6eb8950..1edd67320c6f0 100644 --- a/src/dev/build/tasks/clean_tasks.ts +++ b/src/dev/build/tasks/clean_tasks.ts @@ -63,7 +63,6 @@ export const CleanExtraFilesFromModules: Task = { '**/test', '**/tests', '**/__tests__', - '**/mocha.opts', '**/*.test.js', '**/*.snap', '**/coverage', diff --git a/src/dev/code_coverage/ingest_coverage/integration_tests/team_assignment.test.js b/src/dev/code_coverage/ingest_coverage/integration_tests/team_assignment.test.js index bf4c55871598e..13ef28def4080 100644 --- a/src/dev/code_coverage/ingest_coverage/integration_tests/team_assignment.test.js +++ b/src/dev/code_coverage/ingest_coverage/integration_tests/team_assignment.test.js @@ -8,7 +8,6 @@ import { resolve } from 'path'; import execa from 'execa'; -import expect from '@kbn/expect'; import shell from 'shelljs'; const ROOT_DIR = resolve(__dirname, '../../../../..'); @@ -38,11 +37,14 @@ describe('Team Assignment', () => { describe(`when the codeowners file contains #CC#`, () => { it(`should strip the prefix and still drill down through the fs`, async () => { const { stdout } = await execa('grep', ['tre', teamAssignmentsPath], { cwd: ROOT_DIR }); - expect(stdout).to.be(`x-pack/plugins/code/jest.config.js kibana-tre -x-pack/plugins/code/server/config.ts kibana-tre -x-pack/plugins/code/server/index.ts kibana-tre -x-pack/plugins/code/server/plugin.test.ts kibana-tre -x-pack/plugins/code/server/plugin.ts kibana-tre`); + const lines = stdout.split('\n').filter((line) => !line.includes('/target')); + expect(lines).toEqual([ + 'x-pack/plugins/code/jest.config.js kibana-tre', + 'x-pack/plugins/code/server/config.ts kibana-tre', + 'x-pack/plugins/code/server/index.ts kibana-tre', + 'x-pack/plugins/code/server/plugin.test.ts kibana-tre', + 'x-pack/plugins/code/server/plugin.ts kibana-tre', + ]); }); }); }); diff --git a/src/dev/precommit_hook/casing_check_config.js b/src/dev/precommit_hook/casing_check_config.js index d18e2e49d6b83..616d3cb4f3ed7 100644 --- a/src/dev/precommit_hook/casing_check_config.js +++ b/src/dev/precommit_hook/casing_check_config.js @@ -60,12 +60,9 @@ export const IGNORE_FILE_GLOBS = [ 'x-pack/plugins/apm/e2e/**/*', 'x-pack/plugins/maps/server/fonts/**/*', - // packages for the ingest manager's api integration tests could be valid semver which has dashes 'x-pack/test/fleet_api_integration/apis/fixtures/test_packages/**/*', - '.teamcity/**/*', - // Bazel default files '**/WORKSPACE.bazel', '**/BUILD.bazel', diff --git a/src/plugins/dashboard/public/application/_dashboard_app.scss b/src/plugins/dashboard/public/application/_dashboard_app.scss index f969f936ddebc..30253afff391f 100644 --- a/src/plugins/dashboard/public/application/_dashboard_app.scss +++ b/src/plugins/dashboard/public/application/_dashboard_app.scss @@ -33,3 +33,37 @@ margin-left: $euiSizeS; text-align: center; } + +.dshUnsavedListingItem { + margin-top: $euiSizeM; +} + +.dshUnsavedListingItem__icon { + margin-right: $euiSizeM; +} + +.dshUnsavedListingItem__title { + margin-bottom: 0 !important; +} + +.dshUnsavedListingItem__loading { + color: $euiTextSubduedColor !important; +} + +.dshUnsavedListingItem__actions { + margin-left: $euiSizeL + $euiSizeXS; +} + +@include euiBreakpoint('xs', 's') { + .dshUnsavedListingItem { + margin-top: $euiSize; + } + + .dshUnsavedListingItem__heading { + margin-bottom: $euiSizeXS; + } + + .dshUnsavedListingItem__actions { + flex-direction: column; + } +} \ No newline at end of file diff --git a/src/plugins/dashboard/public/application/dashboard_router.tsx b/src/plugins/dashboard/public/application/dashboard_router.tsx index 5206c76f50be2..d49871b853731 100644 --- a/src/plugins/dashboard/public/application/dashboard_router.tsx +++ b/src/plugins/dashboard/public/application/dashboard_router.tsx @@ -15,12 +15,17 @@ import { Switch, Route, RouteComponentProps, HashRouter, Redirect } from 'react- import { DashboardListing } from './listing'; import { DashboardApp } from './dashboard_app'; -import { addHelpMenuToAppChrome } from './lib'; +import { addHelpMenuToAppChrome, DashboardPanelStorage } from './lib'; import { createDashboardListingFilterUrl } from '../dashboard_constants'; import { getDashboardPageTitle, dashboardReadonlyBadge } from '../dashboard_strings'; import { createDashboardEditUrl, DashboardConstants } from '../dashboard_constants'; import { DashboardAppServices, DashboardEmbedSettings, RedirectToProps } from './types'; -import { DashboardSetupDependencies, DashboardStart, DashboardStartDependencies } from '../plugin'; +import { + DashboardFeatureFlagConfig, + DashboardSetupDependencies, + DashboardStart, + DashboardStartDependencies, +} from '../plugin'; import { createKbnUrlStateStorage, withNotifyOnErrors } from '../services/kibana_utils'; import { KibanaContextProvider } from '../services/kibana_react'; @@ -94,8 +99,11 @@ export async function mountApp({ indexPatterns: dataStart.indexPatterns, savedQueryService: dataStart.query.savedQueries, savedObjectsClient: coreStart.savedObjects.client, + dashboardPanelStorage: new DashboardPanelStorage(core.notifications.toasts), savedDashboards: dashboardStart.getSavedDashboardLoader(), savedObjectsTagging: savedObjectsTaggingOss?.getTaggingApi(), + allowByValueEmbeddables: initializerContext.config.get() + .allowByValueEmbeddables, dashboardCapabilities: { hideWriteControls: dashboardConfig.getHideWriteControls(), show: Boolean(coreStart.application.capabilities.dashboard.show), @@ -122,7 +130,7 @@ export async function mountApp({ let destination; if (redirectTo.destination === 'dashboard') { destination = redirectTo.id - ? createDashboardEditUrl(redirectTo.id) + ? createDashboardEditUrl(redirectTo.id, redirectTo.editMode) : DashboardConstants.CREATE_NEW_DASHBOARD_URL; } else { destination = createDashboardListingFilterUrl(redirectTo.filter); diff --git a/src/plugins/dashboard/public/application/dashboard_state.test.ts b/src/plugins/dashboard/public/application/dashboard_state.test.ts index e973a0180cab8..a96788532def5 100644 --- a/src/plugins/dashboard/public/application/dashboard_state.test.ts +++ b/src/plugins/dashboard/public/application/dashboard_state.test.ts @@ -41,6 +41,7 @@ describe('DashboardState', function () { dashboardState = new DashboardStateManager({ savedDashboard, hideWriteControls: false, + allowByValueEmbeddables: false, kibanaVersion: '7.0.0', kbnUrlStateStorage: createKbnUrlStateStorage(), history: createBrowserHistory(), diff --git a/src/plugins/dashboard/public/application/dashboard_state_manager.ts b/src/plugins/dashboard/public/application/dashboard_state_manager.ts index c52bd1b4d47b8..562fbc7aab3ba 100644 --- a/src/plugins/dashboard/public/application/dashboard_state_manager.ts +++ b/src/plugins/dashboard/public/application/dashboard_state_manager.ts @@ -16,7 +16,12 @@ import { FilterUtils } from './lib/filter_utils'; import { DashboardContainer } from './embeddable'; import { DashboardSavedObject } from '../saved_dashboards'; import { migrateLegacyQuery } from './lib/migrate_legacy_query'; -import { getAppStateDefaults, migrateAppState, getDashboardIdFromUrl } from './lib'; +import { + getAppStateDefaults, + migrateAppState, + getDashboardIdFromUrl, + DashboardPanelStorage, +} from './lib'; import { convertPanelStateToSavedDashboardPanel } from '../../common/embeddable/embeddable_saved_object_converters'; import { DashboardAppState, @@ -37,6 +42,7 @@ import { ReduxLikeStateContainer, syncState, } from '../services/kibana_utils'; +import { STATE_STORAGE_KEY } from '../url_generator'; /** * Dashboard state manager handles connecting angular and redux state between the angular and react portions of the @@ -71,10 +77,11 @@ export class DashboardStateManager { DashboardAppStateTransitions >; private readonly stateContainerChangeSub: Subscription; - private readonly STATE_STORAGE_KEY = '_a'; + private readonly dashboardPanelStorage?: DashboardPanelStorage; public readonly kbnUrlStateStorage: IKbnUrlStateStorage; private readonly stateSyncRef: ISyncStateRef; - private readonly history: History; + private readonly allowByValueEmbeddables: boolean; + private readonly usageCollection: UsageCollectionSetup | undefined; public readonly hasTaggingCapabilities: SavedObjectTagDecoratorTypeGuard; @@ -86,28 +93,32 @@ export class DashboardStateManager { * @param */ constructor({ + history, + kibanaVersion, savedDashboard, + usageCollection, hideWriteControls, - kibanaVersion, kbnUrlStateStorage, - history, - usageCollection, + dashboardPanelStorage, hasTaggingCapabilities, + allowByValueEmbeddables, }: { - savedDashboard: DashboardSavedObject; - hideWriteControls: boolean; - kibanaVersion: string; - kbnUrlStateStorage: IKbnUrlStateStorage; history: History; + kibanaVersion: string; + hideWriteControls: boolean; + allowByValueEmbeddables: boolean; + savedDashboard: DashboardSavedObject; usageCollection?: UsageCollectionSetup; + kbnUrlStateStorage: IKbnUrlStateStorage; + dashboardPanelStorage?: DashboardPanelStorage; hasTaggingCapabilities: SavedObjectTagDecoratorTypeGuard; }) { - this.history = history; this.kibanaVersion = kibanaVersion; this.savedDashboard = savedDashboard; this.hideWriteControls = hideWriteControls; this.usageCollection = usageCollection; this.hasTaggingCapabilities = hasTaggingCapabilities; + this.allowByValueEmbeddables = allowByValueEmbeddables; // get state defaults from saved dashboard, make sure it is migrated this.stateDefaults = migrateAppState( @@ -115,20 +126,29 @@ export class DashboardStateManager { kibanaVersion, usageCollection ); - + this.dashboardPanelStorage = dashboardPanelStorage; this.kbnUrlStateStorage = kbnUrlStateStorage; - // setup initial state by merging defaults with state from url + // setup initial state by merging defaults with state from url & panels storage // also run migration, as state in url could be of older version + const initialUrlState = this.kbnUrlStateStorage.get(STATE_STORAGE_KEY); const initialState = migrateAppState( { ...this.stateDefaults, - ...this.kbnUrlStateStorage.get(this.STATE_STORAGE_KEY), + ...this.getUnsavedPanelState(), + ...initialUrlState, }, kibanaVersion, usageCollection ); + this.isDirty = false; + + if (initialUrlState?.panels && !_.isEqual(initialUrlState.panels, this.stateDefaults.panels)) { + this.isDirty = true; + this.setUnsavedPanels(initialState.panels); + } + // setup state container using initial state both from defaults and from url this.stateContainer = createStateContainer( initialState, @@ -144,8 +164,6 @@ export class DashboardStateManager { } ); - this.isDirty = false; - // We can't compare the filters stored on this.appState to this.savedDashboard because in order to apply // the filters to the visualizations, we need to save it on the dashboard. We keep track of the original // filter state in order to let the user know if their filters changed and provide this specific information @@ -159,16 +177,16 @@ export class DashboardStateManager { this.changeListeners.forEach((listener) => listener({ dirty: this.isDirty })); }); - // setup state syncing utils. state container will be synced with url into `this.STATE_STORAGE_KEY` query param + // setup state syncing utils. state container will be synced with url into `STATE_STORAGE_KEY` query param this.stateSyncRef = syncState({ - storageKey: this.STATE_STORAGE_KEY, + storageKey: STATE_STORAGE_KEY, stateContainer: { ...this.stateContainer, get: () => this.toUrlState(this.stateContainer.get()), - set: (state: DashboardAppStateInUrl | null) => { + set: (stateFromUrl: DashboardAppStateInUrl | null) => { // sync state required state container to be able to handle null // overriding set() so it could handle null coming from url - if (state) { + if (stateFromUrl) { // Skip this update if current dashboardId in the url is different from what we have in the current instance of state manager // As dashboard is driven by angular at the moment, the destroy cycle happens async, // If the dashboardId has changed it means this instance @@ -177,9 +195,15 @@ export class DashboardStateManager { const currentDashboardIdInUrl = getDashboardIdFromUrl(history.location.pathname); if (currentDashboardIdInUrl !== this.savedDashboard.id) return; + // set View mode before the rest of the state so unsaved panels can be added correctly. + if (this.appState.viewMode !== stateFromUrl.viewMode) { + this.switchViewMode(stateFromUrl.viewMode); + } + this.stateContainer.set({ ...this.stateDefaults, - ...state, + ...this.getUnsavedPanelState(), + ...stateFromUrl, }); } else { // Do nothing in case when state from url is empty, @@ -261,6 +285,13 @@ export class DashboardStateManager { if (dirtyBecauseOfInitialStateMigration) { this.saveState({ replace: true }); } + + // If a panel has been changed, and the state is now equal to the state in the saved object, remove the unsaved panels + if (!this.isDirty && this.getIsEditMode()) { + this.clearUnsavedPanels(); + } else { + this.setUnsavedPanels(this.getPanels()); + } } if (input.isFullScreenMode !== this.getFullScreenMode()) { @@ -483,7 +514,16 @@ export class DashboardStateManager { } public getViewMode() { - return this.hideWriteControls ? ViewMode.VIEW : this.appState.viewMode; + if (this.hideWriteControls) { + return ViewMode.VIEW; + } + if (this.stateContainer) { + return this.appState.viewMode; + } + // get viewMode should work properly even before the state container is created + return this.savedDashboard.id + ? this.kbnUrlStateStorage.get(STATE_STORAGE_KEY)?.viewMode ?? ViewMode.VIEW + : ViewMode.EDIT; } public getIsViewMode() { @@ -592,29 +632,13 @@ export class DashboardStateManager { private saveState({ replace }: { replace: boolean }): boolean { // schedules setting current state to url this.kbnUrlStateStorage.set( - this.STATE_STORAGE_KEY, + STATE_STORAGE_KEY, this.toUrlState(this.stateContainer.get()) ); // immediately forces scheduled updates and changes location return !!this.kbnUrlStateStorage.kbnUrlControls.flush(replace); } - // TODO: find nicer solution for this - // this function helps to make just 1 browser history update, when we imperatively changing the dashboard url - // It could be that there is pending *dashboardStateManager* updates, which aren't flushed yet to the url. - // So to prevent 2 browser updates: - // 1. Force flush any pending state updates (syncing state to query) - // 2. If url was updated, then apply path change with replace - public changeDashboardUrl(pathname: string) { - // synchronously persist current state to url with push() - const updated = this.saveState({ replace: false }); - // change pathname - this.history[updated ? 'replace' : 'push']({ - ...this.history.location, - pathname, - }); - } - public setQuery(query: Query) { this.stateContainer.transitions.set('query', query); } @@ -644,6 +668,59 @@ export class DashboardStateManager { } } + public restorePanels() { + const unsavedState = this.getUnsavedPanelState(); + if (!unsavedState || unsavedState.panels?.length === 0) { + return; + } + this.stateContainer.set( + migrateAppState( + { + ...this.stateDefaults, + ...unsavedState, + ...this.kbnUrlStateStorage.get(STATE_STORAGE_KEY), + }, + this.kibanaVersion, + this.usageCollection + ) + ); + } + + public clearUnsavedPanels() { + if (!this.allowByValueEmbeddables || !this.dashboardPanelStorage) { + return; + } + this.dashboardPanelStorage.clearPanels(this.savedDashboard?.id); + } + + private getUnsavedPanelState(): { panels?: SavedDashboardPanel[] } { + if (!this.allowByValueEmbeddables || this.getIsViewMode() || !this.dashboardPanelStorage) { + return {}; + } + const panels = this.dashboardPanelStorage.getPanels(this.savedDashboard?.id); + return panels ? { panels } : {}; + } + + private setUnsavedPanels(newPanels: SavedDashboardPanel[]) { + if ( + !this.allowByValueEmbeddables || + this.getIsViewMode() || + !this.getIsDirty() || + !this.dashboardPanelStorage + ) { + return; + } + this.dashboardPanelStorage.setPanels(this.savedDashboard?.id, newPanels); + } + + private toUrlState(state: DashboardAppState): DashboardAppStateInUrl { + if (this.getIsEditMode() && !this.allowByValueEmbeddables) { + return state; + } + const { panels, ...stateWithoutPanels } = state; + return stateWithoutPanels; + } + private checkIsDirty() { // Filters need to be compared manually because they sometimes have a $$hashkey stored on the object. // Query needs to be compared manually because saved legacy queries get migrated in app state automatically @@ -653,13 +730,4 @@ export class DashboardStateManager { const current = _.omit(this.stateContainer.get(), propsToIgnore); return !_.isEqual(initial, current); } - - private toUrlState(state: DashboardAppState): DashboardAppStateInUrl { - if (state.viewMode === ViewMode.VIEW) { - const { panels, ...stateWithoutPanels } = state; - return stateWithoutPanels; - } - - return state; - } } diff --git a/src/plugins/dashboard/public/application/hooks/use_dashboard_breadcrumbs.ts b/src/plugins/dashboard/public/application/hooks/use_dashboard_breadcrumbs.ts index d9e8991373448..76fbebb04acac 100644 --- a/src/plugins/dashboard/public/application/hooks/use_dashboard_breadcrumbs.ts +++ b/src/plugins/dashboard/public/application/hooks/use_dashboard_breadcrumbs.ts @@ -8,16 +8,11 @@ import { useEffect } from 'react'; import _ from 'lodash'; -import { EUI_MODAL_CANCEL_BUTTON } from '@elastic/eui'; import { useKibana } from '../../services/kibana_react'; import { DashboardStateManager } from '../dashboard_state_manager'; -import { - getDashboardBreadcrumb, - getDashboardTitle, - leaveConfirmStrings, -} from '../../dashboard_strings'; +import { getDashboardBreadcrumb, getDashboardTitle } from '../../dashboard_strings'; import { DashboardAppServices, DashboardRedirect } from '../types'; export const useDashboardBreadcrumbs = ( @@ -38,32 +33,12 @@ export const useDashboardBreadcrumbs = ( return; } - const { - getConfirmButtonText, - getCancelButtonText, - getLeaveTitle, - getLeaveSubtitle, - } = leaveConfirmStrings; - setBreadcrumbs([ { text: getDashboardBreadcrumb(), 'data-test-subj': 'dashboardListingBreadcrumb', onClick: () => { - if (dashboardStateManager.getIsDirty()) { - openConfirm(getLeaveSubtitle(), { - confirmButtonText: getConfirmButtonText(), - cancelButtonText: getCancelButtonText(), - defaultFocusedButton: EUI_MODAL_CANCEL_BUTTON, - title: getLeaveTitle(), - }).then((isConfirmed) => { - if (isConfirmed) { - redirectTo({ destination: 'listing' }); - } - }); - } else { - redirectTo({ destination: 'listing' }); - } + redirectTo({ destination: 'listing' }); }, }, { diff --git a/src/plugins/dashboard/public/application/hooks/use_dashboard_state_manager.ts b/src/plugins/dashboard/public/application/hooks/use_dashboard_state_manager.ts index 93fbb50950850..98401beca39ef 100644 --- a/src/plugins/dashboard/public/application/hooks/use_dashboard_state_manager.ts +++ b/src/plugins/dashboard/public/application/hooks/use_dashboard_state_manager.ts @@ -52,8 +52,10 @@ export const useDashboardStateManager = ( uiSettings, usageCollection, initializerContext, - dashboardCapabilities, savedObjectsTagging, + dashboardCapabilities, + dashboardPanelStorage, + allowByValueEmbeddables, } = useKibana().services; // Destructure and rename services; makes the Effect hook more specific, makes later @@ -86,12 +88,14 @@ export const useDashboardStateManager = ( const stateManager = new DashboardStateManager({ hasTaggingCapabilities, + dashboardPanelStorage, hideWriteControls, history, kbnUrlStateStorage, kibanaVersion, savedDashboard, usageCollection, + allowByValueEmbeddables, }); // sync initial app filters from state to filterManager @@ -178,6 +182,10 @@ export const useDashboardStateManager = ( } ); + if (stateManager.getIsEditMode()) { + stateManager.restorePanels(); + } + setDashboardStateManager(stateManager); setViewMode(stateManager.getViewMode()); @@ -191,6 +199,8 @@ export const useDashboardStateManager = ( dataPlugin, filterManager, hasTaggingCapabilities, + initializerContext.config, + dashboardPanelStorage, hideWriteControls, history, kibanaVersion, @@ -202,6 +212,7 @@ export const useDashboardStateManager = ( toasts, uiSettings, usageCollection, + allowByValueEmbeddables, dashboardCapabilities.storeSearchSession, ]); diff --git a/src/plugins/dashboard/public/application/hooks/use_saved_dashboard.ts b/src/plugins/dashboard/public/application/hooks/use_saved_dashboard.ts index fae98b8fed306..9ef8fef909ac3 100644 --- a/src/plugins/dashboard/public/application/hooks/use_saved_dashboard.ts +++ b/src/plugins/dashboard/public/application/hooks/use_saved_dashboard.ts @@ -14,7 +14,7 @@ import { useKibana } from '../../services/kibana_react'; import { DashboardConstants } from '../..'; import { DashboardSavedObject } from '../../saved_dashboards'; -import { getDashboard60Warning } from '../../dashboard_strings'; +import { getDashboard60Warning, getNewDashboardTitle } from '../../dashboard_strings'; import { DashboardAppServices } from '../types'; export const useSavedDashboard = (savedDashboardId: string | undefined, history: History) => { @@ -43,12 +43,7 @@ export const useSavedDashboard = (savedDashboardId: string | undefined, history: try { const dashboard = (await savedDashboards.get(savedDashboardId)) as DashboardSavedObject; - const { title, getFullPath } = dashboard; - if (savedDashboardId) { - recentlyAccessedPaths.add(getFullPath(), title, savedDashboardId); - } - - docTitle.change(title); + docTitle.change(dashboard.title || getNewDashboardTitle()); setSavedDashboard(dashboard); } catch (error) { // E.g. a corrupt or deleted dashboard @@ -58,13 +53,13 @@ export const useSavedDashboard = (savedDashboardId: string | undefined, history: })(); return () => setSavedDashboard(null); }, [ + toasts, docTitle, history, indexPatterns, recentlyAccessedPaths, savedDashboardId, savedDashboards, - toasts, ]); return savedDashboard; diff --git a/src/plugins/dashboard/public/application/lib/dashboard_panel_storage.ts b/src/plugins/dashboard/public/application/lib/dashboard_panel_storage.ts new file mode 100644 index 0000000000000..ab1842048abd0 --- /dev/null +++ b/src/plugins/dashboard/public/application/lib/dashboard_panel_storage.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { Storage } from '../../services/kibana_utils'; +import { NotificationsStart } from '../../services/core'; +import { panelStorageErrorStrings } from '../../dashboard_strings'; +import { SavedDashboardPanel } from '..'; + +export const DASHBOARD_PANELS_UNSAVED_ID = 'unsavedDashboard'; +const DASHBOARD_PANELS_SESSION_KEY = 'dashboardStateManagerPanels'; + +export class DashboardPanelStorage { + private sessionStorage: Storage; + + constructor(private toasts: NotificationsStart['toasts']) { + this.sessionStorage = new Storage(sessionStorage); + } + + public clearPanels(id = DASHBOARD_PANELS_UNSAVED_ID) { + try { + const sessionStoragePanels = this.sessionStorage.get(DASHBOARD_PANELS_SESSION_KEY) || {}; + if (sessionStoragePanels[id]) { + delete sessionStoragePanels[id]; + this.sessionStorage.set(DASHBOARD_PANELS_SESSION_KEY, sessionStoragePanels); + } + } catch (e) { + this.toasts.addDanger({ + title: panelStorageErrorStrings.getPanelsClearError(e.message), + 'data-test-subj': 'dashboardPanelsClearFailure', + }); + } + } + + public getPanels(id = DASHBOARD_PANELS_UNSAVED_ID): SavedDashboardPanel[] | undefined { + try { + return this.sessionStorage.get(DASHBOARD_PANELS_SESSION_KEY)?.[id]; + } catch (e) { + this.toasts.addDanger({ + title: panelStorageErrorStrings.getPanelsGetError(e.message), + 'data-test-subj': 'dashboardPanelsGetFailure', + }); + } + } + + public setPanels(id = DASHBOARD_PANELS_UNSAVED_ID, newPanels: SavedDashboardPanel[]) { + try { + const sessionStoragePanels = this.sessionStorage.get(DASHBOARD_PANELS_SESSION_KEY) || {}; + sessionStoragePanels[id] = newPanels; + this.sessionStorage.set(DASHBOARD_PANELS_SESSION_KEY, sessionStoragePanels); + } catch (e) { + this.toasts.addDanger({ + title: panelStorageErrorStrings.getPanelsSetError(e.message), + 'data-test-subj': 'dashboardPanelsSetFailure', + }); + } + } + + public getDashboardIdsWithUnsavedChanges() { + try { + return Object.keys(this.sessionStorage.get(DASHBOARD_PANELS_SESSION_KEY) || {}); + } catch (e) { + this.toasts.addDanger({ + title: panelStorageErrorStrings.getPanelsGetError(e.message), + 'data-test-subj': 'dashboardPanelsGetFailure', + }); + return []; + } + } + + public dashboardHasUnsavedEdits(id = DASHBOARD_PANELS_UNSAVED_ID) { + return this.getDashboardIdsWithUnsavedChanges().indexOf(id) !== -1; + } +} diff --git a/src/plugins/dashboard/public/application/lib/index.ts b/src/plugins/dashboard/public/application/lib/index.ts index fc2e17298b3f2..e803b783f3bad 100644 --- a/src/plugins/dashboard/public/application/lib/index.ts +++ b/src/plugins/dashboard/public/application/lib/index.ts @@ -13,3 +13,4 @@ export { getDashboardIdFromUrl } from './url'; export { createSessionRestorationDataProvider } from './session_restoration'; export { addHelpMenuToAppChrome } from './help_menu_util'; export { attemptLoadDashboardByTitle } from './load_dashboard_by_title'; +export { DashboardPanelStorage } from './dashboard_panel_storage'; diff --git a/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.tsx.snap b/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.tsx.snap index faec6b4f6f24b..cd9df989c0039 100644 --- a/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.tsx.snap +++ b/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.tsx.snap @@ -4,6 +4,7 @@ exports[`after fetch When given a title that matches multiple dashboards, filter void, + cancelButtonText = leaveConfirmStrings.getCancelButtonText() +) => + overlays + .openConfirm(leaveConfirmStrings.getDiscardSubtitle(), { + confirmButtonText: leaveConfirmStrings.getConfirmButtonText(), + cancelButtonText, + buttonColor: 'danger', + defaultFocusedButton: EUI_MODAL_CANCEL_BUTTON, + title: leaveConfirmStrings.getDiscardTitle(), + }) + .then((isConfirmed) => { + if (isConfirmed) { + discardCallback(); + } + }); + +export const confirmCreateWithUnsaved = ( + overlays: OverlayStart, + startBlankCallback: () => void, + contineCallback: () => void +) => { + const session = overlays.openModal( + toMountPoint( + session.close()}> + + {createConfirmStrings.getCreateTitle()} + + + + {createConfirmStrings.getCreateSubtitle()} + + + + session.close()} + > + {createConfirmStrings.getCancelButtonText()} + + { + startBlankCallback(); + session.close(); + }} + > + {createConfirmStrings.getStartOverButtonText()} + + { + contineCallback(); + session.close(); + }} + > + {createConfirmStrings.getContinueButtonText()} + + + + ), + { + 'data-test-subj': 'dashboardCreateConfirmModal', + } + ); +}; diff --git a/src/plugins/dashboard/public/application/listing/dashboard_listing.test.tsx b/src/plugins/dashboard/public/application/listing/dashboard_listing.test.tsx index ef4d4e693d0e3..f4943c9679f4a 100644 --- a/src/plugins/dashboard/public/application/listing/dashboard_listing.test.tsx +++ b/src/plugins/dashboard/public/application/listing/dashboard_listing.test.tsx @@ -29,6 +29,7 @@ import { chromeServiceMock, coreMock } from '../../../../../core/public/mocks'; import { I18nProvider } from '@kbn/i18n/react'; import React from 'react'; import { UrlForwardingStart } from '../../../../url_forwarding/public'; +import { DashboardPanelStorage } from '../lib'; function makeDefaultServices(): DashboardAppServices { const core = coreMock.createStart(); @@ -52,6 +53,7 @@ function makeDefaultServices(): DashboardAppServices { savedObjects: savedObjectsPluginMock.createStartContract(), embeddable: embeddablePluginMock.createInstance().doStart(), dashboardCapabilities: {} as DashboardCapabilities, + dashboardPanelStorage: {} as DashboardPanelStorage, initializerContext: {} as PluginInitializerContext, chrome: chromeServiceMock.createStartContract(), navigation: {} as NavigationPublicPluginStart, @@ -65,6 +67,7 @@ function makeDefaultServices(): DashboardAppServices { uiSettings: {} as IUiSettingsClient, restorePreviousUrl: () => {}, onAppLeave: (handler) => {}, + allowByValueEmbeddables: true, savedDashboards, core, }; diff --git a/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx b/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx index 07de4cd52bba6..2accf0183da14 100644 --- a/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx +++ b/src/plugins/dashboard/public/application/listing/dashboard_listing.tsx @@ -17,6 +17,8 @@ import { syncQueryStateWithUrl } from '../../services/data'; import { IKbnUrlStateStorage } from '../../services/kibana_utils'; import { TableListView, useKibana } from '../../services/kibana_react'; import { SavedObjectsTaggingApi } from '../../services/saved_objects_tagging_oss'; +import { DashboardUnsavedListing } from './dashboard_unsaved_listing'; +import { confirmCreateWithUnsaved } from './confirm_overlays'; import { getDashboardListItemLink } from './get_dashboard_list_item_link'; export interface DashboardListingProps { @@ -41,6 +43,7 @@ export const DashboardListing = ({ savedObjectsClient, savedObjectsTagging, dashboardCapabilities, + dashboardPanelStorage, chrome: { setBreadcrumbs }, }, } = useKibana(); @@ -91,12 +94,24 @@ export const DashboardListing = ({ [core.application, core.uiSettings, kbnUrlStateStorage, savedObjectsTagging] ); + const createItem = useCallback(() => { + if (!dashboardPanelStorage.dashboardHasUnsavedEdits()) { + redirectTo({ destination: 'dashboard' }); + } else { + confirmCreateWithUnsaved( + core.overlays, + () => { + dashboardPanelStorage.clearPanels(); + redirectTo({ destination: 'dashboard' }); + }, + () => redirectTo({ destination: 'dashboard' }) + ); + } + }, [dashboardPanelStorage, redirectTo, core.overlays]); + const noItemsFragment = useMemo( - () => - getNoItemsMessage(hideWriteControls, core.application, () => - redirectTo({ destination: 'dashboard' }) - ), - [redirectTo, core.application, hideWriteControls] + () => getNoItemsMessage(hideWriteControls, core.application, createItem), + [createItem, core.application, hideWriteControls] ); const fetchItems = useCallback( @@ -125,7 +140,8 @@ export const DashboardListing = ({ ); const editItem = useCallback( - ({ id }: { id: string | undefined }) => redirectTo({ destination: 'dashboard', id }), + ({ id }: { id: string | undefined }) => + redirectTo({ destination: 'dashboard', id, editMode: true }), [redirectTo] ); @@ -143,7 +159,7 @@ export const DashboardListing = ({ } = dashboardListingTable; return ( redirectTo({ destination: 'dashboard' })} + createItem={hideWriteControls ? undefined : createItem} deleteItems={hideWriteControls ? undefined : deleteItems} initialPageSize={savedObjects.settings.getPerPage()} editItem={hideWriteControls ? undefined : editItem} @@ -162,7 +178,9 @@ export const DashboardListing = ({ listingLimit, tableColumns, }} - /> + > + + ); }; diff --git a/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.test.tsx b/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.test.tsx new file mode 100644 index 0000000000000..3717ea79dc060 --- /dev/null +++ b/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.test.tsx @@ -0,0 +1,153 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { I18nProvider } from '@kbn/i18n/react'; +import { findTestSubject } from '@elastic/eui/lib/test'; +import { waitFor } from '@testing-library/react'; +import { mount } from 'enzyme'; +import React from 'react'; +import { DashboardSavedObject } from '../..'; +import { coreMock } from '../../../../../core/public/mocks'; +import { KibanaContextProvider } from '../../services/kibana_react'; +import { SavedObjectLoader } from '../../services/saved_objects'; +import { DashboardPanelStorage } from '../lib'; +import { DASHBOARD_PANELS_UNSAVED_ID } from '../lib/dashboard_panel_storage'; +import { DashboardAppServices, DashboardRedirect } from '../types'; +import { DashboardUnsavedListing } from './dashboard_unsaved_listing'; + +const mockedDashboards: { [key: string]: DashboardSavedObject } = { + dashboardUnsavedOne: { + id: `dashboardUnsavedOne`, + title: `Dashboard Unsaved One`, + } as DashboardSavedObject, + dashboardUnsavedTwo: { + id: `dashboardUnsavedTwo`, + title: `Dashboard Unsaved Two`, + } as DashboardSavedObject, + dashboardUnsavedThree: { + id: `dashboardUnsavedThree`, + title: `Dashboard Unsaved Three`, + } as DashboardSavedObject, +}; + +function makeDefaultServices(): DashboardAppServices { + const core = coreMock.createStart(); + core.overlays.openConfirm = jest.fn().mockResolvedValue(true); + const savedDashboards = {} as SavedObjectLoader; + savedDashboards.get = jest.fn().mockImplementation((id: string) => mockedDashboards[id]); + const dashboardPanelStorage = {} as DashboardPanelStorage; + dashboardPanelStorage.clearPanels = jest.fn(); + dashboardPanelStorage.getDashboardIdsWithUnsavedChanges = jest + .fn() + .mockImplementation(() => [ + 'dashboardUnsavedOne', + 'dashboardUnsavedTwo', + 'dashboardUnsavedThree', + ]); + return ({ + dashboardPanelStorage, + savedDashboards, + core, + } as unknown) as DashboardAppServices; +} + +const makeDefaultProps = () => ({ redirectTo: jest.fn() }); + +function mountWith({ + services: incomingServices, + props: incomingProps, +}: { + services?: DashboardAppServices; + props?: { redirectTo: DashboardRedirect }; +}) { + const services = incomingServices ?? makeDefaultServices(); + const props = incomingProps ?? makeDefaultProps(); + const wrappingComponent: React.FC<{ + children: React.ReactNode; + }> = ({ children }) => { + return ( + + {children} + + ); + }; + const component = mount(, { wrappingComponent }); + return { component, props, services }; +} + +describe('Unsaved listing', () => { + it('Gets information for each unsaved dashboard', async () => { + const { services } = mountWith({}); + await waitFor(() => { + expect(services.savedDashboards.get).toHaveBeenCalledTimes(3); + }); + }); + + it('Does not attempt to get unsaved dashboard id', async () => { + const services = makeDefaultServices(); + services.dashboardPanelStorage.getDashboardIdsWithUnsavedChanges = jest + .fn() + .mockImplementation(() => ['dashboardUnsavedOne', DASHBOARD_PANELS_UNSAVED_ID]); + mountWith({ services }); + await waitFor(() => { + expect(services.savedDashboards.get).toHaveBeenCalledTimes(1); + }); + }); + + it('Redirects to the requested dashboard in edit mode when continue editing clicked', async () => { + const { props, component } = mountWith({}); + const getEditButton = () => findTestSubject(component, 'edit-unsaved-Dashboard-Unsaved-One'); + await waitFor(() => { + component.update(); + expect(getEditButton().length).toEqual(1); + }); + getEditButton().simulate('click'); + expect(props.redirectTo).toHaveBeenCalledWith({ + destination: 'dashboard', + id: 'dashboardUnsavedOne', + editMode: true, + }); + }); + + it('Redirects to new dashboard when continue editing clicked', async () => { + const services = makeDefaultServices(); + services.dashboardPanelStorage.getDashboardIdsWithUnsavedChanges = jest + .fn() + .mockImplementation(() => [DASHBOARD_PANELS_UNSAVED_ID]); + const { props, component } = mountWith({ services }); + const getEditButton = () => findTestSubject(component, `edit-unsaved-New-Dashboard`); + await waitFor(() => { + component.update(); + expect(getEditButton().length).toBe(1); + }); + getEditButton().simulate('click'); + expect(props.redirectTo).toHaveBeenCalledWith({ + destination: 'dashboard', + id: undefined, + editMode: true, + }); + }); + + it('Shows a warning then clears changes when delete unsaved changes is pressed', async () => { + const { services, component } = mountWith({}); + const getDiscardButton = () => + findTestSubject(component, 'discard-unsaved-Dashboard-Unsaved-One'); + await waitFor(() => { + component.update(); + expect(getDiscardButton().length).toBe(1); + }); + getDiscardButton().simulate('click'); + waitFor(() => { + component.update(); + expect(services.core.overlays.openConfirm).toHaveBeenCalled(); + expect(services.dashboardPanelStorage.clearPanels).toHaveBeenCalledWith( + 'dashboardUnsavedOne' + ); + }); + }); +}); diff --git a/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.tsx b/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.tsx new file mode 100644 index 0000000000000..9b78f290acbeb --- /dev/null +++ b/src/plugins/dashboard/public/application/listing/dashboard_unsaved_listing.tsx @@ -0,0 +1,197 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { + EuiButtonEmpty, + EuiCallOut, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import React, { useCallback, useEffect, useState } from 'react'; +import { DashboardSavedObject } from '../..'; +import { + createConfirmStrings, + dashboardUnsavedListingStrings, + getNewDashboardTitle, +} from '../../dashboard_strings'; +import { useKibana } from '../../services/kibana_react'; +import { DASHBOARD_PANELS_UNSAVED_ID } from '../lib/dashboard_panel_storage'; +import { DashboardAppServices, DashboardRedirect } from '../types'; +import { confirmDiscardUnsavedChanges } from './confirm_overlays'; + +const DashboardUnsavedItem = ({ + id, + title, + onOpenClick, + onDiscardClick, +}: { + id: string; + title?: string; + onOpenClick: () => void; + onDiscardClick: () => void; +}) => { + return ( +
+ + + + + + +

+ {title || dashboardUnsavedListingStrings.getLoadingTitle()} +

+
+
+
+ + + + {dashboardUnsavedListingStrings.getEditTitle()} + + + + + {dashboardUnsavedListingStrings.getDiscardTitle()} + + + +
+ ); +}; + +interface UnsavedItemMap { + [key: string]: DashboardSavedObject; +} + +export const DashboardUnsavedListing = ({ redirectTo }: { redirectTo: DashboardRedirect }) => { + const { + services: { + dashboardPanelStorage, + savedDashboards, + core: { overlays }, + }, + } = useKibana(); + + const [items, setItems] = useState({}); + const [dashboardIds, setDashboardIds] = useState( + dashboardPanelStorage.getDashboardIdsWithUnsavedChanges() + ); + + const onOpen = useCallback( + (id?: string) => { + redirectTo({ destination: 'dashboard', id, editMode: true }); + }, + [redirectTo] + ); + + const onDiscard = useCallback( + (id?: string) => { + confirmDiscardUnsavedChanges( + overlays, + () => { + dashboardPanelStorage.clearPanels(id); + setDashboardIds(dashboardPanelStorage.getDashboardIdsWithUnsavedChanges()); + }, + createConfirmStrings.getCancelButtonText() + ); + }, + [overlays, dashboardPanelStorage] + ); + + useEffect(() => { + if (dashboardIds?.length === 0) { + return; + } + let canceled = false; + const dashPromises = dashboardIds + .filter((id) => id !== DASHBOARD_PANELS_UNSAVED_ID) + .map((dashboardId) => savedDashboards.get(dashboardId)); + Promise.all(dashPromises).then((dashboards: DashboardSavedObject[]) => { + const dashboardMap = {}; + if (canceled) { + return; + } + setItems( + dashboards.reduce((map, dashboard) => { + return { + ...map, + [dashboard.id || DASHBOARD_PANELS_UNSAVED_ID]: dashboard, + }; + }, dashboardMap) + ); + }); + return () => { + canceled = true; + }; + }, [dashboardIds, savedDashboards]); + + return dashboardIds.length === 0 ? null : ( + <> + 1)} + > + {dashboardIds.map((dashboardId: string) => { + const title: string | undefined = + dashboardId === DASHBOARD_PANELS_UNSAVED_ID + ? getNewDashboardTitle() + : items[dashboardId]?.title; + const redirectId = dashboardId === DASHBOARD_PANELS_UNSAVED_ID ? undefined : dashboardId; + return ( + onOpen(redirectId)} + onDiscardClick={() => onDiscard(redirectId)} + /> + ); + })} + + + + ); +}; diff --git a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx index 4cf565a12be22..47aae123cc0c2 100644 --- a/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx +++ b/src/plugins/dashboard/public/application/top_nav/dashboard_top_nav.tsx @@ -6,8 +6,6 @@ * Public License, v 1. */ -import { EUI_MODAL_CANCEL_BUTTON } from '@elastic/eui'; - import { i18n } from '@kbn/i18n'; import angular from 'angular'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; @@ -31,7 +29,6 @@ import { import { NavAction } from '../../types'; import { DashboardSavedObject } from '../..'; import { DashboardStateManager } from '../dashboard_state_manager'; -import { leaveConfirmStrings } from '../../dashboard_strings'; import { saveDashboard } from '../lib'; import { DashboardAppServices, @@ -46,7 +43,10 @@ import { showOptionsPopover } from './show_options_popover'; import { TopNavIds } from './top_nav_ids'; import { ShowShareModal } from './show_share_modal'; import { PanelToolbar } from './panel_toolbar'; +import { confirmDiscardUnsavedChanges } from '../listing/confirm_overlays'; import { OverlayRef } from '../../../../../core/public'; +import { getNewDashboardTitle } from '../../dashboard_strings'; +import { DASHBOARD_PANELS_UNSAVED_ID } from '../lib/dashboard_panel_storage'; import { DashboardContainer } from '..'; export interface DashboardTopNavState { @@ -91,6 +91,8 @@ export function DashboardTopNav({ setHeaderActionMenu, savedObjectsTagging, dashboardCapabilities, + dashboardPanelStorage, + allowByValueEmbeddables, } = useKibana().services; const [state, setState] = useState({ chromeIsVisible: false }); @@ -99,8 +101,16 @@ export function DashboardTopNav({ const visibleSubscription = chrome.getIsVisible$().subscribe((chromeIsVisible) => { setState((s) => ({ ...s, chromeIsVisible })); }); + const { id, title, getFullEditPath } = savedDashboard; + if (id || allowByValueEmbeddables) { + chrome.recentlyAccessed.add( + getFullEditPath(dashboardStateManager.getIsEditMode()), + title || getNewDashboardTitle(), + id || DASHBOARD_PANELS_UNSAVED_ID + ); + } return () => visibleSubscription.unsubscribe(); - }, [chrome]); + }, [chrome, allowByValueEmbeddables, dashboardStateManager, savedDashboard]); const addFromLibrary = useCallback(() => { if (!isErrorEmbeddable(dashboardContainer)) { @@ -142,47 +152,40 @@ export function DashboardTopNav({ } }, [state.addPanelOverlay]); + const onDiscardChanges = useCallback(() => { + function revertChangesAndExitEditMode() { + dashboardStateManager.resetState(); + dashboardStateManager.clearUnsavedPanels(); + + // We need to do a hard reset of the timepicker. appState will not reload like + // it does on 'open' because it's been saved to the url and the getAppState.previouslyStored() check on + // reload will cause it not to sync. + if (dashboardStateManager.getIsTimeSavedWithDashboard()) { + dashboardStateManager.syncTimefilterWithDashboardTime(timefilter); + dashboardStateManager.syncTimefilterWithDashboardRefreshInterval(timefilter); + } + dashboardStateManager.switchViewMode(ViewMode.VIEW); + } + confirmDiscardUnsavedChanges(core.overlays, revertChangesAndExitEditMode); + }, [core.overlays, dashboardStateManager, timefilter]); + const onChangeViewMode = useCallback( (newMode: ViewMode) => { clearAddPanel(); - const isPageRefresh = newMode === dashboardStateManager.getViewMode(); - const isLeavingEditMode = !isPageRefresh && newMode === ViewMode.VIEW; - const willLoseChanges = isLeavingEditMode && dashboardStateManager.getIsDirty(timefilter); - - if (!willLoseChanges) { - dashboardStateManager.switchViewMode(newMode); - return; + if (savedDashboard?.id && allowByValueEmbeddables) { + const { getFullEditPath, title, id } = savedDashboard; + chrome.recentlyAccessed.add(getFullEditPath(newMode === ViewMode.EDIT), title, id); } - - function revertChangesAndExitEditMode() { - dashboardStateManager.resetState(); - // This is only necessary for new dashboards, which will default to Edit mode. - dashboardStateManager.switchViewMode(ViewMode.VIEW); - - // We need to do a hard reset of the timepicker. appState will not reload like - // it does on 'open' because it's been saved to the url and the getAppState.previouslyStored() check on - // reload will cause it not to sync. - if (dashboardStateManager.getIsTimeSavedWithDashboard()) { - dashboardStateManager.syncTimefilterWithDashboardTime(timefilter); - dashboardStateManager.syncTimefilterWithDashboardRefreshInterval(timefilter); - } - redirectTo({ destination: 'dashboard', id: savedDashboard.id }); - } - - core.overlays - .openConfirm(leaveConfirmStrings.getDiscardSubtitle(), { - confirmButtonText: leaveConfirmStrings.getConfirmButtonText(), - cancelButtonText: leaveConfirmStrings.getCancelButtonText(), - defaultFocusedButton: EUI_MODAL_CANCEL_BUTTON, - title: leaveConfirmStrings.getDiscardTitle(), - }) - .then((isConfirmed) => { - if (isConfirmed) { - revertChangesAndExitEditMode(); - } - }); + dashboardStateManager.switchViewMode(newMode); + dashboardStateManager.restorePanels(); }, - [redirectTo, timefilter, core.overlays, savedDashboard.id, dashboardStateManager, clearAddPanel] + [ + clearAddPanel, + savedDashboard, + dashboardStateManager, + allowByValueEmbeddables, + chrome.recentlyAccessed, + ] ); /** @@ -210,8 +213,9 @@ export function DashboardTopNav({ 'data-test-subj': 'saveDashboardSuccess', }); + dashboardPanelStorage.clearPanels(lastDashboardId); if (id !== lastDashboardId) { - redirectTo({ destination: 'dashboard', id }); + redirectTo({ destination: 'dashboard', id, useReplace: !lastDashboardId }); } else { chrome.docTitle.change(dashboardStateManager.savedDashboard.lastSavedTitle); dashboardStateManager.switchViewMode(ViewMode.VIEW); @@ -236,6 +240,7 @@ export function DashboardTopNav({ [ core.notifications.toasts, dashboardStateManager, + dashboardPanelStorage, lastDashboardId, chrome.docTitle, redirectTo, @@ -349,6 +354,7 @@ export function DashboardTopNav({ }, [TopNavIds.EXIT_EDIT_MODE]: () => onChangeViewMode(ViewMode.VIEW), [TopNavIds.ENTER_EDIT_MODE]: () => onChangeViewMode(ViewMode.EDIT), + [TopNavIds.DISCARD_CHANGES]: onDiscardChanges, [TopNavIds.SAVE]: runSave, [TopNavIds.CLONE]: runClone, [TopNavIds.ADD_EXISTING]: addFromLibrary, @@ -385,6 +391,7 @@ export function DashboardTopNav({ }, [ dashboardCapabilities, dashboardStateManager, + onDiscardChanges, onChangeViewMode, savedDashboard, addFromLibrary, diff --git a/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts b/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts index 3364c2d2ff768..2bbccdccd2eac 100644 --- a/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts +++ b/src/plugins/dashboard/public/application/top_nav/get_top_nav_config.ts @@ -41,6 +41,7 @@ export function getTopNavConfig( getShareConfig(actions[TopNavIds.SHARE]), getAddConfig(actions[TopNavIds.ADD_EXISTING]), getViewConfig(actions[TopNavIds.EXIT_EDIT_MODE]), + getDiscardConfig(actions[TopNavIds.DISCARD_CHANGES]), getSaveConfig(actions[TopNavIds.SAVE]), getCreateNewConfig(actions[TopNavIds.VISUALIZE]), ]; @@ -112,13 +113,30 @@ function getViewConfig(action: NavAction) { defaultMessage: 'cancel', }), description: i18n.translate('dashboard.topNave.viewConfigDescription', { - defaultMessage: 'Cancel editing and switch to view-only mode', + defaultMessage: 'Switch to view-only mode', }), testId: 'dashboardViewOnlyMode', run: action, }; } +/** + * @returns {kbnTopNavConfig} + */ +function getDiscardConfig(action: NavAction) { + return { + id: 'discard', + label: i18n.translate('dashboard.topNave.discardlButtonAriaLabel', { + defaultMessage: 'discard', + }), + description: i18n.translate('dashboard.topNave.discardConfigDescription', { + defaultMessage: 'Discard unsaved changes', + }), + testId: 'dashboardDiscardChanges', + run: action, + }; +} + /** * @returns {kbnTopNavConfig} */ diff --git a/src/plugins/dashboard/public/application/top_nav/top_nav_ids.ts b/src/plugins/dashboard/public/application/top_nav/top_nav_ids.ts index fc31d67c12a2f..16cbb0d2d85c8 100644 --- a/src/plugins/dashboard/public/application/top_nav/top_nav_ids.ts +++ b/src/plugins/dashboard/public/application/top_nav/top_nav_ids.ts @@ -12,6 +12,7 @@ export const TopNavIds = { SAVE: 'save', EXIT_EDIT_MODE: 'exitEditMode', ENTER_EDIT_MODE: 'enterEditMode', + DISCARD_CHANGES: 'discard', CLONE: 'clone', FULL_SCREEN: 'fullScreenMode', VISUALIZE: 'visualize', diff --git a/src/plugins/dashboard/public/application/types.ts b/src/plugins/dashboard/public/application/types.ts index e4f9388a919d1..f7d64eebe3332 100644 --- a/src/plugins/dashboard/public/application/types.ts +++ b/src/plugins/dashboard/public/application/types.ts @@ -23,11 +23,12 @@ import { NavigationPublicPluginStart } from '../services/navigation'; import { SavedObjectsTaggingApi } from '../services/saved_objects_tagging_oss'; import { DataPublicPluginStart, IndexPatternsContract } from '../services/data'; import { SavedObjectLoader, SavedObjectsStart } from '../services/saved_objects'; +import { DashboardPanelStorage } from './lib'; import { UrlForwardingStart } from '../../../url_forwarding/public'; export type DashboardRedirect = (props: RedirectToProps) => void; export type RedirectToProps = - | { destination: 'dashboard'; id?: string; useReplace?: boolean } + | { destination: 'dashboard'; id?: string; useReplace?: boolean; editMode?: boolean } | { destination: 'listing'; filter?: string; useReplace?: boolean }; export interface DashboardEmbedSettings { @@ -67,12 +68,14 @@ export interface DashboardAppServices { uiSettings: IUiSettingsClient; restorePreviousUrl: () => void; savedObjects: SavedObjectsStart; + allowByValueEmbeddables: boolean; urlForwarding: UrlForwardingStart; savedDashboards: SavedObjectLoader; scopedHistory: () => ScopedHistory; indexPatterns: IndexPatternsContract; usageCollection?: UsageCollectionSetup; navigation: NavigationPublicPluginStart; + dashboardPanelStorage: DashboardPanelStorage; dashboardCapabilities: DashboardCapabilities; initializerContext: PluginInitializerContext; onAppLeave: AppMountParameters['onAppLeave']; diff --git a/src/plugins/dashboard/public/dashboard_constants.ts b/src/plugins/dashboard/public/dashboard_constants.ts index 3788a42029431..f809eaa2914ed 100644 --- a/src/plugins/dashboard/public/dashboard_constants.ts +++ b/src/plugins/dashboard/public/dashboard_constants.ts @@ -6,6 +6,8 @@ * Public License, v 1. */ +const DASHBOARD_STATE_STORAGE_KEY = '_a'; + export const DashboardConstants = { LANDING_PAGE_PATH: '/list', CREATE_NEW_DASHBOARD_URL: '/create', @@ -17,8 +19,12 @@ export const DashboardConstants = { SEARCH_SESSION_ID: 'searchSessionId', }; -export function createDashboardEditUrl(id: string) { - return `${DashboardConstants.VIEW_DASHBOARD_URL}/${id}`; +export function createDashboardEditUrl(id?: string, editMode?: boolean) { + if (!id) { + return `${DashboardConstants.CREATE_NEW_DASHBOARD_URL}`; + } + const edit = editMode ? `?${DASHBOARD_STATE_STORAGE_KEY}=(viewMode:edit)` : ''; + return `${DashboardConstants.VIEW_DASHBOARD_URL}/${id}${edit}`; } export function createDashboardListingFilterUrl(filter: string | undefined) { diff --git a/src/plugins/dashboard/public/dashboard_strings.ts b/src/plugins/dashboard/public/dashboard_strings.ts index 6c23aab4ebddb..68681826c27fb 100644 --- a/src/plugins/dashboard/public/dashboard_strings.ts +++ b/src/plugins/dashboard/public/dashboard_strings.ts @@ -24,10 +24,7 @@ export function getDashboardTitle( ): string { const isEditMode = viewMode === ViewMode.EDIT; let displayTitle: string; - const newDashboardTitle = i18n.translate('dashboard.savedDashboard.newDashboardTitle', { - defaultMessage: 'New Dashboard', - }); - const dashboardTitle = isNew ? newDashboardTitle : title; + const dashboardTitle = isNew ? getNewDashboardTitle() : title; if (isEditMode && isDirty) { displayTitle = i18n.translate('dashboard.strings.dashboardUnsavedEditTitle', { @@ -176,6 +173,11 @@ export const dashboardReplacePanelAction = { /* Dashboard Editor */ +export const getNewDashboardTitle = () => + i18n.translate('dashboard.savedDashboard.newDashboardTitle', { + defaultMessage: 'New Dashboard', + }); + export const shareModalStrings = { getTopMenuCheckbox: () => i18n.translate('dashboard.embedUrlParamExtension.topMenu', { @@ -242,6 +244,44 @@ export const leaveConfirmStrings = { }), }; +export const createConfirmStrings = { + getCreateTitle: () => + i18n.translate('dashboard.createConfirmModal.unsavedChangesTitle', { + defaultMessage: 'New dashboard already in progress', + }), + getCreateSubtitle: () => + i18n.translate('dashboard.createConfirmModal.unsavedChangesSubtitle', { + defaultMessage: 'You can continue editing or start with a blank dashboard.', + }), + getStartOverButtonText: () => + i18n.translate('dashboard.createConfirmModal.confirmButtonLabel', { + defaultMessage: 'Start over', + }), + getContinueButtonText: () => leaveConfirmStrings.getCancelButtonText(), + getCancelButtonText: () => + i18n.translate('dashboard.createConfirmModal.cancelButtonLabel', { + defaultMessage: 'Cancel', + }), +}; + +export const panelStorageErrorStrings = { + getPanelsGetError: (message: string) => + i18n.translate('dashboard.panelStorageError.getError', { + defaultMessage: 'Error encountered while fetching unsaved changes: {message}', + values: { message }, + }), + getPanelsSetError: (message: string) => + i18n.translate('dashboard.panelStorageError.setError', { + defaultMessage: 'Error encountered while setting unsaved changes: {message}', + values: { message }, + }), + getPanelsClearError: (message: string) => + i18n.translate('dashboard.panelStorageError.clearError', { + defaultMessage: 'Error encountered while clearing unsaved changes: {message}', + values: { message }, + }), +}; + /* Empty Screen */ @@ -307,3 +347,37 @@ export const dashboardListingTable = { defaultMessage: 'Description', }), }; + +export const dashboardUnsavedListingStrings = { + getUnsavedChangesTitle: (plural = false) => + i18n.translate('dashboard.listing.unsaved.unsavedChangesTitle', { + defaultMessage: 'You have unsaved changes in the following {dash}.', + values: { + dash: plural + ? dashboardListingTable.getEntityNamePlural() + : dashboardListingTable.getEntityName(), + }, + }), + getLoadingTitle: () => + i18n.translate('dashboard.listing.unsaved.loading', { + defaultMessage: 'Loading', + }), + getEditAriaLabel: (title: string) => + i18n.translate('dashboard.listing.unsaved.editAria', { + defaultMessage: 'Continue editing {title}', + values: { title }, + }), + getEditTitle: () => + i18n.translate('dashboard.listing.unsaved.editTitle', { + defaultMessage: 'Continue editing', + }), + getDiscardAriaLabel: (title: string) => + i18n.translate('dashboard.listing.unsaved.discardAria', { + defaultMessage: 'Discard changes to {title}', + values: { title }, + }), + getDiscardTitle: () => + i18n.translate('dashboard.listing.unsaved.discardTitle', { + defaultMessage: 'Discard changes', + }), +}; diff --git a/src/plugins/dashboard/public/plugin.tsx b/src/plugins/dashboard/public/plugin.tsx index e6a251fd4c51c..37f751f6c19f5 100644 --- a/src/plugins/dashboard/public/plugin.tsx +++ b/src/plugins/dashboard/public/plugin.tsx @@ -282,11 +282,11 @@ export class DashboardPlugin core, appUnMounted, usageCollection, - onAppLeave: params.onAppLeave, - initializerContext: this.initializerContext, restorePreviousUrl, element: params.element, + onAppLeave: params.onAppLeave, scopedHistory: this.currentHistory!, + initializerContext: this.initializerContext, setHeaderActionMenu: params.setHeaderActionMenu, }); }, diff --git a/src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts b/src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts index d0b4d63e4fcde..6fa86acefd067 100644 --- a/src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts +++ b/src/plugins/dashboard/public/saved_dashboards/saved_dashboard.ts @@ -30,6 +30,7 @@ export interface DashboardSavedObject extends SavedObject { searchSource: ISearchSource; getQuery(): Query; getFilters(): Filter[]; + getFullEditPath: (editMode?: boolean) => string; } // Used only by the savedDashboards service, usually no reason to change this @@ -106,7 +107,7 @@ export function createSavedDashboardClass( refreshInterval: undefined, }, }); - this.getFullPath = () => `/app/dashboards#${createDashboardEditUrl(String(this.id))}`; + this.getFullPath = () => `/app/dashboards#${createDashboardEditUrl(this.id)}`; } getQuery() { @@ -116,6 +117,10 @@ export function createSavedDashboardClass( getFilters() { return this.searchSource!.getOwnField('filter') || []; } + + getFullEditPath = (editMode?: boolean) => { + return `/app/dashboards#${createDashboardEditUrl(this.id, editMode)}`; + }; } // Unfortunately this throws a typescript error without the casting. I think it's due to the diff --git a/src/plugins/dashboard/public/services/kibana_utils.ts b/src/plugins/dashboard/public/services/kibana_utils.ts index 72b8a427effca..353f1bcf31301 100644 --- a/src/plugins/dashboard/public/services/kibana_utils.ts +++ b/src/plugins/dashboard/public/services/kibana_utils.ts @@ -7,6 +7,7 @@ */ export { + Storage, unhashUrl, syncState, ISyncStateRef, diff --git a/src/plugins/dashboard/public/types.ts b/src/plugins/dashboard/public/types.ts index 63aea0130e071..29094270e98da 100644 --- a/src/plugins/dashboard/public/types.ts +++ b/src/plugins/dashboard/public/types.ts @@ -81,8 +81,7 @@ export type DashboardAppStateDefaults = DashboardAppState & { }; /** - * In URL panels are optional, - * Panels are not added to the URL when in "view" mode + * Panels are not added to the URL */ export type DashboardAppStateInUrl = Omit & { panels?: SavedDashboardPanel[]; diff --git a/src/plugins/data/common/es_query/es_query/decorate_query.test.ts b/src/plugins/data/common/es_query/es_query/decorate_query.test.ts index 68d8b875c353a..eb08af5269fc1 100644 --- a/src/plugins/data/common/es_query/es_query/decorate_query.test.ts +++ b/src/plugins/data/common/es_query/es_query/decorate_query.test.ts @@ -22,6 +22,13 @@ describe('Query decorator', () => { expect(decoratedQuery).toEqual({ query_string: { query: '*', analyze_wildcard: true } }); }); + test('should merge in serialized query string options', () => { + const queryStringOptions = '{ "analyze_wildcard": true }'; + const decoratedQuery = decorateQuery({ query_string: { query: '*' } }, queryStringOptions); + + expect(decoratedQuery).toEqual({ query_string: { query: '*', analyze_wildcard: true } }); + }); + test('should add a default of a time_zone parameter if one is provided', () => { const decoratedQuery = decorateQuery( { query_string: { query: '*' } }, diff --git a/src/plugins/data/common/es_query/es_query/decorate_query.ts b/src/plugins/data/common/es_query/es_query/decorate_query.ts index 501c614859360..b71cebdd21c97 100644 --- a/src/plugins/data/common/es_query/es_query/decorate_query.ts +++ b/src/plugins/data/common/es_query/es_query/decorate_query.ts @@ -20,10 +20,16 @@ import { DslQuery, isEsQueryString } from './es_query_dsl'; export function decorateQuery( query: DslQuery, - queryStringOptions: Record, + queryStringOptions: Record | string, dateFormatTZ?: string ) { if (isEsQueryString(query)) { + // NOTE queryStringOptions comes from UI Settings and, in server context, is a serialized string + // https://github.com/elastic/kibana/issues/89902 + if (typeof queryStringOptions === 'string') { + queryStringOptions = JSON.parse(queryStringOptions); + } + extend(query.query_string, queryStringOptions); if (dateFormatTZ) { defaults(query.query_string, { diff --git a/src/plugins/data/common/field_formats/field_formats_registry.ts b/src/plugins/data/common/field_formats/field_formats_registry.ts index aa6cf4500c933..b5cc1b944094c 100644 --- a/src/plugins/data/common/field_formats/field_formats_registry.ts +++ b/src/plugins/data/common/field_formats/field_formats_registry.ts @@ -23,6 +23,7 @@ import { FormatFactory } from './utils'; import { ES_FIELD_TYPES, KBN_FIELD_TYPES } from '../kbn_field_types/types'; import { UI_SETTINGS } from '../constants'; import { FieldFormatNotFoundError } from '../field_formats'; +import { SerializedFieldFormat } from '../../../expressions/common/types'; export class FieldFormatsRegistry { protected fieldFormats: Map = new Map(); @@ -30,7 +31,20 @@ export class FieldFormatsRegistry { protected metaParamsOptions: Record = {}; protected getConfig?: FieldFormatsGetConfigFn; // overriden on the public contract - public deserialize: FormatFactory = () => { + public deserialize: FormatFactory = (mapping?: SerializedFieldFormat) => { + if (!mapping) { + return new (FieldFormat.from(identity))(); + } + + const { id, params = {} } = mapping; + if (id) { + const Format = this.getType(id); + + if (Format) { + return new Format(params, this.getConfig); + } + } + return new (FieldFormat.from(identity))(); }; diff --git a/src/plugins/data/common/search/aggs/buckets/create_filter/filters.test.ts b/src/plugins/data/common/search/aggs/buckets/create_filter/filters.test.ts index ffdd68f7e73a3..1eddfc1516065 100644 --- a/src/plugins/data/common/search/aggs/buckets/create_filter/filters.test.ts +++ b/src/plugins/data/common/search/aggs/buckets/create_filter/filters.test.ts @@ -51,6 +51,30 @@ describe('AggConfig Filters', () => { const aggConfigs = getAggConfigs(); const filter = createFilterFilters(aggConfigs.aggs[0] as IBucketAggConfig, 'type:nginx'); + expect(filter).toMatchInlineSnapshot(` + Object { + "meta": Object { + "alias": "type:nginx", + "index": "1234", + }, + "query": Object { + "bool": Object { + "filter": Array [], + "must": Array [ + Object { + "query_string": Object { + "query": "type:nginx", + "time_zone": "dateFormat:tz", + }, + }, + ], + "must_not": Array [], + "should": Array [], + }, + }, + } + `); + expect(filter!.query.bool.must[0].query_string.query).toBe('type:nginx'); expect(filter!.meta).toHaveProperty('index', '1234'); expect(filter!.meta).toHaveProperty('alias', 'type:nginx'); diff --git a/src/plugins/data/common/search/aggs/test_helpers/mock_agg_types_registry.ts b/src/plugins/data/common/search/aggs/test_helpers/mock_agg_types_registry.ts index 096654eb4bbb8..752d840549d48 100644 --- a/src/plugins/data/common/search/aggs/test_helpers/mock_agg_types_registry.ts +++ b/src/plugins/data/common/search/aggs/test_helpers/mock_agg_types_registry.ts @@ -26,6 +26,7 @@ const mockGetConfig = jest.fn().mockImplementation((key: string) => { ['P1DT', 'YYYY-MM-DD'], ['P1YT', 'YYYY'], ], + 'query:queryString:options': {}, }; return config[key] ?? key; }); diff --git a/src/plugins/data/common/search/expressions/esaggs/create_filter.test.ts b/src/plugins/data/common/search/expressions/esaggs/create_filter.test.ts index 076eeaba32c54..e4d4d0e5e7a18 100644 --- a/src/plugins/data/common/search/expressions/esaggs/create_filter.test.ts +++ b/src/plugins/data/common/search/expressions/esaggs/create_filter.test.ts @@ -10,12 +10,12 @@ import { isRangeFilter } from '../../../es_query/filters'; import { BytesFormat, FieldFormatsGetConfigFn } from '../../../field_formats'; import { AggConfigs, IAggConfig } from '../../aggs'; import { mockAggTypesRegistry } from '../../aggs/test_helpers'; -import { TabbedTable } from '../../tabify'; import { createFilter } from './create_filter'; +import { Datatable } from '../../../../../expressions/common'; describe('createFilter', () => { - let table: TabbedTable; + let table: Datatable; let aggConfig: IAggConfig; const typesRegistry = mockAggTypesRegistry(); @@ -62,11 +62,12 @@ describe('createFilter', () => { beforeEach(() => { table = { + type: 'datatable', columns: [ { id: '1-1', name: 'test', - aggConfig, + meta: { type: 'number' }, }, ], rows: [ diff --git a/src/plugins/data/common/search/expressions/esaggs/create_filter.ts b/src/plugins/data/common/search/expressions/esaggs/create_filter.ts index e02d50485ad97..f6a372cacd509 100644 --- a/src/plugins/data/common/search/expressions/esaggs/create_filter.ts +++ b/src/plugins/data/common/search/expressions/esaggs/create_filter.ts @@ -8,9 +8,9 @@ import { Filter } from '../../../es_query'; import { IAggConfig } from '../../aggs'; -import { TabbedTable } from '../../tabify'; +import { Datatable } from '../../../../../expressions/common'; -const getOtherBucketFilterTerms = (table: TabbedTable, columnIndex: number, rowIndex: number) => { +const getOtherBucketFilterTerms = (table: Datatable, columnIndex: number, rowIndex: number) => { if (rowIndex === -1) { return []; } @@ -36,7 +36,7 @@ const getOtherBucketFilterTerms = (table: TabbedTable, columnIndex: number, rowI export const createFilter = ( aggConfigs: IAggConfig[], - table: TabbedTable, + table: Datatable, columnIndex: number, rowIndex: number, cellValue: any diff --git a/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts b/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts index 1e48c5b786123..f12ab6ba068f6 100644 --- a/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts +++ b/src/plugins/data/common/search/expressions/esaggs/esaggs_fn.ts @@ -8,22 +8,16 @@ import { i18n } from '@kbn/i18n'; -import { - Datatable, - DatatableColumn, - ExpressionFunctionDefinition, -} from 'src/plugins/expressions/common'; +import { Datatable, ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; -import { FormatFactory } from '../../../field_formats/utils'; import { IndexPatternExpressionType } from '../../../index_patterns/expressions'; import { IndexPatternsContract } from '../../../index_patterns/index_patterns'; -import { calculateBounds } from '../../../query'; import { AggsStart, AggExpressionType } from '../../aggs'; import { ISearchStartSearchSource } from '../../search_source'; import { KibanaContext } from '../kibana_context_type'; -import { handleRequest, RequestHandlerParams } from './request_handler'; +import { handleRequest } from './request_handler'; const name = 'esaggs'; @@ -48,7 +42,6 @@ export type EsaggsExpressionFunctionDefinition = ExpressionFunctionDefinition< /** @internal */ export interface EsaggsStartDependencies { aggs: AggsStart; - deserializeFieldFormat: FormatFactory; indexPatterns: IndexPatternsContract; searchSource: ISearchStartSearchSource; getNow?: () => Date; @@ -103,48 +96,4 @@ export const getEsaggsMeta: () => Omit }); /** @internal */ -export async function handleEsaggsRequest( - input: Input, - args: Arguments, - params: RequestHandlerParams -): Promise { - const resolvedTimeRange = - input?.timeRange && calculateBounds(input.timeRange, { forceNow: params.getNow?.() }); - - const response = await handleRequest(params); - - const table: Datatable = { - type: 'datatable', - rows: response.rows, - columns: response.columns.map((column) => { - const cleanedColumn: DatatableColumn = { - id: column.id, - name: column.name, - meta: { - type: column.aggConfig.params.field?.type || 'number', - field: column.aggConfig.params.field?.name, - index: params.indexPattern?.title, - params: column.aggConfig.toSerializedFieldFormat(), - source: name, - sourceParams: { - indexPatternId: params.indexPattern?.id, - appliedTimeRange: - column.aggConfig.params.field?.name && - input?.timeRange && - args.timeFields && - args.timeFields.includes(column.aggConfig.params.field?.name) - ? { - from: resolvedTimeRange?.min?.toISOString(), - to: resolvedTimeRange?.max?.toISOString(), - } - : undefined, - ...column.aggConfig.serialize(), - }, - }, - }; - return cleanedColumn; - }), - }; - - return table; -} +export { handleRequest as handleEsaggsRequest }; diff --git a/src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts b/src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts index 3932348928ac9..72ac022e4fd34 100644 --- a/src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts +++ b/src/plugins/data/common/search/expressions/esaggs/request_handler.test.ts @@ -34,7 +34,6 @@ describe('esaggs expression function - public', () => { toDsl: jest.fn().mockReturnValue({ aggs: {} }), onSearchRequestStart: jest.fn(), } as unknown) as jest.Mocked, - deserializeFieldFormat: jest.fn(), filters: undefined, indexPattern: ({ id: 'logstash-*' } as unknown) as jest.Mocked, inspectorAdapters: {}, diff --git a/src/plugins/data/common/search/expressions/esaggs/request_handler.ts b/src/plugins/data/common/search/expressions/esaggs/request_handler.ts index 70b1221a74739..fc9d47a5a2f7d 100644 --- a/src/plugins/data/common/search/expressions/esaggs/request_handler.ts +++ b/src/plugins/data/common/search/expressions/esaggs/request_handler.ts @@ -18,7 +18,6 @@ import { Query, TimeRange, } from '../../../../common'; -import { FormatFactory } from '../../../../common/field_formats/utils'; import { IAggConfigs } from '../../aggs'; import { ISearchStartSearchSource } from '../../search_source'; @@ -29,7 +28,6 @@ import { getRequestInspectorStats, getResponseInspectorStats } from '../utils'; export interface RequestHandlerParams { abortSignal?: AbortSignal; aggs: IAggConfigs; - deserializeFieldFormat: FormatFactory; filters?: Filter[]; indexPattern?: IndexPattern; inspectorAdapters: Adapters; @@ -46,7 +44,6 @@ export interface RequestHandlerParams { export const handleRequest = async ({ abortSignal, aggs, - deserializeFieldFormat, filters, indexPattern, inspectorAdapters, diff --git a/src/plugins/data/common/search/search_source/normalize_sort_request.ts b/src/plugins/data/common/search/search_source/normalize_sort_request.ts index 7f1cbbd7f2da6..7461b6c1788f8 100644 --- a/src/plugins/data/common/search/search_source/normalize_sort_request.ts +++ b/src/plugins/data/common/search/search_source/normalize_sort_request.ts @@ -49,6 +49,11 @@ function normalize( } } + // FIXME: for unknown reason on the server this setting is serialized + // https://github.com/elastic/kibana/issues/89902 + if (typeof defaultSortOptions === 'string') { + defaultSortOptions = JSON.parse(defaultSortOptions); + } // Don't include unmapped_type for _score field // eslint-disable-next-line @typescript-eslint/naming-convention const { unmapped_type, ...otherSortOptions } = defaultSortOptions; diff --git a/src/plugins/data/common/search/search_source/search_source.test.ts b/src/plugins/data/common/search/search_source/search_source.test.ts index c2a4beb9b61a5..49fb1fa62f490 100644 --- a/src/plugins/data/common/search/search_source/search_source.test.ts +++ b/src/plugins/data/common/search/search_source/search_source.test.ts @@ -80,6 +80,175 @@ describe('SearchSource', () => { }); }); + describe('#getFields()', () => { + test('gets the value for the property', () => { + searchSource.setField('aggs', 5); + expect(searchSource.getFields()).toMatchInlineSnapshot(` + Object { + "aggs": 5, + } + `); + }); + + test('recurses parents to get the entire filters: plain object filter', () => { + const RECURSE = true; + + const parent = new SearchSource({}, searchSourceDependencies); + parent.setField('filter', [ + { + meta: { + index: 'd180cae0-60c3-11eb-8569-bd1f5ed24bc9', + params: {}, + alias: null, + disabled: false, + negate: false, + }, + query: { + range: { + '@date': { + gte: '2016-01-27T18:11:05.010Z', + lte: '2021-01-27T18:11:05.010Z', + format: 'strict_date_optional_time', + }, + }, + }, + }, + ]); + searchSource.setParent(parent); + searchSource.setField('aggs', 5); + expect(searchSource.getFields(RECURSE)).toMatchInlineSnapshot(` + Object { + "aggs": 5, + "filter": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "index": "d180cae0-60c3-11eb-8569-bd1f5ed24bc9", + "negate": false, + "params": Object {}, + }, + "query": Object { + "range": Object { + "@date": Object { + "format": "strict_date_optional_time", + "gte": "2016-01-27T18:11:05.010Z", + "lte": "2021-01-27T18:11:05.010Z", + }, + }, + }, + }, + ], + } + `); + + // calling twice gives the same result: no searchSources in the hierarchy were modified + expect(searchSource.getFields(RECURSE)).toMatchInlineSnapshot(` + Object { + "aggs": 5, + "filter": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "index": "d180cae0-60c3-11eb-8569-bd1f5ed24bc9", + "negate": false, + "params": Object {}, + }, + "query": Object { + "range": Object { + "@date": Object { + "format": "strict_date_optional_time", + "gte": "2016-01-27T18:11:05.010Z", + "lte": "2021-01-27T18:11:05.010Z", + }, + }, + }, + }, + ], + } + `); + }); + + test('recurses parents to get the entire filters: function filter', () => { + const RECURSE = true; + + const parent = new SearchSource({}, searchSourceDependencies); + parent.setField('filter', () => ({ + meta: { + index: 'd180cae0-60c3-11eb-8569-bd1f5ed24bc9', + params: {}, + alias: null, + disabled: false, + negate: false, + }, + query: { + range: { + '@date': { + gte: '2016-01-27T18:11:05.010Z', + lte: '2021-01-27T18:11:05.010Z', + format: 'strict_date_optional_time', + }, + }, + }, + })); + searchSource.setParent(parent); + searchSource.setField('aggs', 5); + expect(searchSource.getFields(RECURSE)).toMatchInlineSnapshot(` + Object { + "aggs": 5, + "filter": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "index": "d180cae0-60c3-11eb-8569-bd1f5ed24bc9", + "negate": false, + "params": Object {}, + }, + "query": Object { + "range": Object { + "@date": Object { + "format": "strict_date_optional_time", + "gte": "2016-01-27T18:11:05.010Z", + "lte": "2021-01-27T18:11:05.010Z", + }, + }, + }, + }, + ], + } + `); + + // calling twice gives the same result: no double-added filters + expect(searchSource.getFields(RECURSE)).toMatchInlineSnapshot(` + Object { + "aggs": 5, + "filter": Array [ + Object { + "meta": Object { + "alias": null, + "disabled": false, + "index": "d180cae0-60c3-11eb-8569-bd1f5ed24bc9", + "negate": false, + "params": Object {}, + }, + "query": Object { + "range": Object { + "@date": Object { + "format": "strict_date_optional_time", + "gte": "2016-01-27T18:11:05.010Z", + "lte": "2021-01-27T18:11:05.010Z", + }, + }, + }, + }, + ], + } + `); + }); + }); + describe('#removeField()', () => { test('remove property', () => { searchSource = new SearchSource({}, searchSourceDependencies); @@ -619,13 +788,13 @@ describe('SearchSource', () => { expect(JSON.parse(searchSourceJSON).from).toEqual(123456); }); - test('should omit sort and size', () => { + test('should omit size but not sort', () => { searchSource.setField('highlightAll', true); searchSource.setField('from', 123456); searchSource.setField('sort', { field: SortDirection.asc }); searchSource.setField('size', 200); const { searchSourceJSON } = searchSource.serialize(); - expect(Object.keys(JSON.parse(searchSourceJSON))).toEqual(['highlightAll', 'from']); + expect(Object.keys(JSON.parse(searchSourceJSON))).toEqual(['highlightAll', 'from', 'sort']); }); test('should serialize filters', () => { diff --git a/src/plugins/data/common/search/search_source/search_source.ts b/src/plugins/data/common/search/search_source/search_source.ts index bb60f0d7b4ad4..36c0aab6bc190 100644 --- a/src/plugins/data/common/search/search_source/search_source.ts +++ b/src/plugins/data/common/search/search_source/search_source.ts @@ -172,7 +172,49 @@ export class SearchSource { /** * returns all search source fields */ - getFields() { + getFields(recurse = false): SearchSourceFields { + let thisFilter = this.fields.filter; // type is single value, array, or function + if (thisFilter) { + if (typeof thisFilter === 'function') { + thisFilter = thisFilter() || []; // type is single value or array + } + + if (Array.isArray(thisFilter)) { + thisFilter = [...thisFilter]; + } else { + thisFilter = [thisFilter]; + } + } else { + thisFilter = []; + } + + if (recurse) { + const parent = this.getParent(); + if (parent) { + const parentFields = parent.getFields(recurse); + + let parentFilter = parentFields.filter; // type is single value, array, or function + if (parentFilter) { + if (typeof parentFilter === 'function') { + parentFilter = parentFilter() || []; // type is single value or array + } + + if (Array.isArray(parentFilter)) { + thisFilter.push(...parentFilter); + } else { + thisFilter.push(parentFilter); + } + } + + // add combined filters to the fields + const thisFields = { + ...this.fields, + filter: thisFilter, + }; + + return { ...parentFields, ...thisFields }; + } + } return { ...this.fields }; } @@ -605,9 +647,8 @@ export class SearchSource { /** * serializes search source fields (which can later be passed to {@link ISearchStartSearchSource}) */ - public getSerializedFields() { - const { filter: originalFilters, ...searchSourceFields } = omit(this.getFields(), [ - 'sort', + public getSerializedFields(recurse = false) { + const { filter: originalFilters, ...searchSourceFields } = omit(this.getFields(recurse), [ 'size', ]); let serializedSearchSourceFields: SearchSourceFields = { diff --git a/src/plugins/data/common/search/tabify/__snapshots__/tabify_docs.test.ts.snap b/src/plugins/data/common/search/tabify/__snapshots__/tabify_docs.test.ts.snap index d5ddaa31b8ac3..6cc191a67633c 100644 --- a/src/plugins/data/common/search/tabify/__snapshots__/tabify_docs.test.ts.snap +++ b/src/plugins/data/common/search/tabify/__snapshots__/tabify_docs.test.ts.snap @@ -26,23 +26,38 @@ Object { "name": "invalidMapping", }, Object { - "id": "nested.field", + "id": "nested", + "meta": Object { + "field": "nested", + "index": "test-index", + "params": undefined, + "type": "object", + }, + "name": "nested", + }, + Object { + "id": "sourceTest", "meta": Object { - "field": "nested.field", + "field": "sourceTest", "index": "test-index", "params": Object { "id": "number", }, "type": "number", }, - "name": "nested.field", + "name": "sourceTest", }, ], "rows": Array [ Object { "fieldTest": 123, "invalidMapping": 345, - "nested.field": 123, + "nested": Array [ + Object { + "field": 123, + }, + ], + "sourceTest": 123, }, ], "type": "datatable", @@ -52,6 +67,38 @@ Object { exports[`tabifyDocs converts source if option is set 1`] = ` Object { "columns": Array [ + Object { + "id": "fieldTest", + "meta": Object { + "field": "fieldTest", + "index": "test-index", + "params": Object { + "id": "number", + }, + "type": "number", + }, + "name": "fieldTest", + }, + Object { + "id": "invalidMapping", + "meta": Object { + "field": "invalidMapping", + "index": "test-index", + "params": undefined, + "type": "number", + }, + "name": "invalidMapping", + }, + Object { + "id": "nested", + "meta": Object { + "field": "nested", + "index": "test-index", + "params": undefined, + "type": "object", + }, + "name": "nested", + }, Object { "id": "sourceTest", "meta": Object { @@ -67,6 +114,13 @@ Object { ], "rows": Array [ Object { + "fieldTest": 123, + "invalidMapping": 345, + "nested": Array [ + Object { + "field": 123, + }, + ], "sourceTest": 123, }, ], @@ -109,6 +163,18 @@ Object { }, "name": "nested", }, + Object { + "id": "sourceTest", + "meta": Object { + "field": "sourceTest", + "index": "test-index", + "params": Object { + "id": "number", + }, + "type": "number", + }, + "name": "sourceTest", + }, ], "rows": Array [ Object { @@ -119,6 +185,7 @@ Object { "field": 123, }, ], + "sourceTest": 123, }, ], "type": "datatable", @@ -149,21 +216,36 @@ Object { "name": "invalidMapping", }, Object { - "id": "nested.field", + "id": "nested", + "meta": Object { + "field": "nested", + "index": undefined, + "params": undefined, + "type": "object", + }, + "name": "nested", + }, + Object { + "id": "sourceTest", "meta": Object { - "field": "nested.field", + "field": "sourceTest", "index": undefined, "params": undefined, "type": "number", }, - "name": "nested.field", + "name": "sourceTest", }, ], "rows": Array [ Object { "fieldTest": 123, "invalidMapping": 345, - "nested.field": 123, + "nested": Array [ + Object { + "field": 123, + }, + ], + "sourceTest": 123, }, ], "type": "datatable", diff --git a/src/plugins/data/common/search/tabify/index.ts b/src/plugins/data/common/search/tabify/index.ts index 08d54316d9d91..f93b3fcde345e 100644 --- a/src/plugins/data/common/search/tabify/index.ts +++ b/src/plugins/data/common/search/tabify/index.ts @@ -26,7 +26,7 @@ export const tabify = ( ); }; +export { tabifyDocs }; + export { tabifyAggResponse } from './tabify'; export { tabifyGetColumns } from './get_columns'; - -export { TabbedTable, TabbedAggRow, TabbedAggColumn } from './types'; diff --git a/src/plugins/data/common/search/tabify/response_writer.test.ts b/src/plugins/data/common/search/tabify/response_writer.test.ts index 9a37a2de9b5b0..9d9060f751262 100644 --- a/src/plugins/data/common/search/tabify/response_writer.test.ts +++ b/src/plugins/data/common/search/tabify/response_writer.test.ts @@ -59,6 +59,7 @@ describe('TabbedAggResponseWriter class', () => { getByName: (name: string) => fields.find((f) => f.name === name), filter: () => fields, }, + getFormatterForField: () => ({ toJSON: () => '' }), } as any; return new TabbedAggResponseWriter(new AggConfigs(indexPattern, aggs, { typesRegistry }), { @@ -136,31 +137,116 @@ describe('TabbedAggResponseWriter class', () => { const response = responseWriter.response(); + expect(response).toHaveProperty('type', 'datatable'); expect(response).toHaveProperty('rows'); expect(response.rows).toEqual([{ 'col-0-1': 'US', 'col-1-2': 5 }]); expect(response).toHaveProperty('columns'); expect(response.columns.length).toEqual(2); expect(response.columns[0]).toHaveProperty('id', 'col-0-1'); expect(response.columns[0]).toHaveProperty('name', 'geo.src: Descending'); - expect(response.columns[0]).toHaveProperty('aggConfig'); + expect(response.columns[0]).toHaveProperty('meta', { + index: 'logstash-*', + params: { + id: 'terms', + params: { + missingBucketLabel: 'Missing', + otherBucketLabel: 'Other', + }, + }, + field: 'geo.src', + source: 'esaggs', + sourceParams: { + enabled: true, + id: '1', + indexPatternId: '1234', + params: { + field: 'geo.src', + missingBucket: false, + missingBucketLabel: 'Missing', + order: 'desc', + otherBucket: false, + otherBucketLabel: 'Other', + size: 5, + }, + type: 'terms', + }, + type: 'number', + }); + expect(response.columns[1]).toHaveProperty('id', 'col-1-2'); expect(response.columns[1]).toHaveProperty('name', 'Count'); - expect(response.columns[1]).toHaveProperty('aggConfig'); + expect(response.columns[1]).toHaveProperty('meta', { + index: 'logstash-*', + params: { + id: 'number', + }, + source: 'esaggs', + sourceParams: { + enabled: true, + id: '2', + indexPatternId: '1234', + params: {}, + type: 'count', + }, + type: 'number', + }); }); test('produces correct response for no data', () => { const response = responseWriter.response(); - + expect(response).toHaveProperty('type', 'datatable'); expect(response).toHaveProperty('rows'); expect(response.rows.length).toBe(0); expect(response).toHaveProperty('columns'); expect(response.columns.length).toEqual(2); expect(response.columns[0]).toHaveProperty('id', 'col-0-1'); expect(response.columns[0]).toHaveProperty('name', 'geo.src: Descending'); - expect(response.columns[0]).toHaveProperty('aggConfig'); + expect(response.columns[0]).toHaveProperty('meta', { + index: 'logstash-*', + params: { + id: 'terms', + params: { + missingBucketLabel: 'Missing', + otherBucketLabel: 'Other', + }, + }, + field: 'geo.src', + source: 'esaggs', + sourceParams: { + enabled: true, + id: '1', + indexPatternId: '1234', + params: { + field: 'geo.src', + missingBucket: false, + missingBucketLabel: 'Missing', + order: 'desc', + otherBucket: false, + otherBucketLabel: 'Other', + size: 5, + }, + type: 'terms', + }, + type: 'number', + }); + expect(response.columns[1]).toHaveProperty('id', 'col-1-2'); expect(response.columns[1]).toHaveProperty('name', 'Count'); - expect(response.columns[1]).toHaveProperty('aggConfig'); + expect(response.columns[1]).toHaveProperty('meta', { + index: 'logstash-*', + params: { + id: 'number', + }, + source: 'esaggs', + sourceParams: { + enabled: true, + id: '2', + indexPatternId: '1234', + params: {}, + type: 'count', + }, + type: 'number', + }); }); }); }); diff --git a/src/plugins/data/common/search/tabify/response_writer.ts b/src/plugins/data/common/search/tabify/response_writer.ts index c03b477ca1cd1..2026b51b8efb0 100644 --- a/src/plugins/data/common/search/tabify/response_writer.ts +++ b/src/plugins/data/common/search/tabify/response_writer.ts @@ -10,7 +10,8 @@ import { isEmpty } from 'lodash'; import { IAggConfigs } from '../aggs'; import { tabifyGetColumns } from './get_columns'; -import { TabbedResponseWriterOptions, TabbedAggColumn, TabbedAggRow, TabbedTable } from './types'; +import { TabbedResponseWriterOptions, TabbedAggColumn, TabbedAggRow } from './types'; +import { Datatable, DatatableColumn } from '../../../../expressions/common/expression_types/specs'; interface BufferColumn { id: string; @@ -28,19 +29,18 @@ export class TabbedAggResponseWriter { metricBuffer: BufferColumn[] = []; private readonly partialRows: boolean; + private readonly params: Partial; /** * @param {AggConfigs} aggs - the agg configs object to which the aggregation response correlates * @param {boolean} metricsAtAllLevels - setting to true will produce metrics for every bucket * @param {boolean} partialRows - setting to true will not remove rows with missing values */ - constructor( - aggs: IAggConfigs, - { metricsAtAllLevels = false, partialRows = false }: Partial - ) { - this.partialRows = partialRows; + constructor(aggs: IAggConfigs, params: Partial) { + this.partialRows = params.partialRows || false; + this.params = params; - this.columns = tabifyGetColumns(aggs.getResponseAggs(), !metricsAtAllLevels); + this.columns = tabifyGetColumns(aggs.getResponseAggs(), !params.metricsAtAllLevels); this.rows = []; } @@ -65,9 +65,37 @@ export class TabbedAggResponseWriter { } } - response(): TabbedTable { + response(): Datatable { return { - columns: this.columns, + type: 'datatable', + columns: this.columns.map((column) => { + const cleanedColumn: DatatableColumn = { + id: column.id, + name: column.name, + meta: { + type: column.aggConfig.params.field?.type || 'number', + field: column.aggConfig.params.field?.name, + index: column.aggConfig.getIndexPattern()?.title, + params: column.aggConfig.toSerializedFieldFormat(), + source: 'esaggs', + sourceParams: { + indexPatternId: column.aggConfig.getIndexPattern()?.id, + appliedTimeRange: + column.aggConfig.params.field?.name && + this.params.timeRange && + this.params.timeRange.timeFields && + this.params.timeRange.timeFields.includes(column.aggConfig.params.field?.name) + ? { + from: this.params.timeRange?.from?.toISOString(), + to: this.params.timeRange?.to?.toISOString(), + } + : undefined, + ...column.aggConfig.serialize(), + }, + }, + }; + return cleanedColumn; + }), rows: this.rows, }; } diff --git a/src/plugins/data/common/search/tabify/tabify.test.ts b/src/plugins/data/common/search/tabify/tabify.test.ts index 02d734129d8e4..f2c980196ab26 100644 --- a/src/plugins/data/common/search/tabify/tabify.test.ts +++ b/src/plugins/data/common/search/tabify/tabify.test.ts @@ -27,6 +27,9 @@ describe('tabifyAggResponse Integration', () => { getByName: () => field, filter: () => [field], }, + getFormatterForField: () => ({ + toJSON: () => '{}', + }), } as unknown) as IndexPattern; return new AggConfigs(indexPattern, aggs, { typesRegistry }); @@ -48,7 +51,7 @@ describe('tabifyAggResponse Integration', () => { expect(resp.columns).toHaveLength(1); expect(resp.rows[0]).toEqual({ 'col-0-1': 1000 }); - expect(resp.columns[0]).toHaveProperty('aggConfig', aggConfigs.aggs[0]); + expect(resp.columns[0]).toHaveProperty('name', aggConfigs.aggs[0].makeLabel()); }); describe('transforms a complex response', () => { @@ -78,7 +81,7 @@ describe('tabifyAggResponse Integration', () => { expect(table.columns).toHaveLength(aggs.length); aggs.forEach((agg, i) => { - expect(table.columns[i]).toHaveProperty('aggConfig', agg); + expect(table.columns[i]).toHaveProperty('name', agg.makeLabel()); }); } diff --git a/src/plugins/data/common/search/tabify/tabify_docs.ts b/src/plugins/data/common/search/tabify/tabify_docs.ts index d66be3c5748fe..7d4d0fad20730 100644 --- a/src/plugins/data/common/search/tabify/tabify_docs.ts +++ b/src/plugins/data/common/search/tabify/tabify_docs.ts @@ -12,9 +12,9 @@ import { IndexPattern } from '../../index_patterns/index_patterns'; import { Datatable, DatatableColumn, DatatableColumnType } from '../../../../expressions/common'; export function flattenHit( - hit: Record, + hit: SearchResponse['hits']['hits'][0], indexPattern?: IndexPattern, - shallow: boolean = false + params?: TabifyDocsOptions ) { const flat = {} as Record; @@ -24,7 +24,7 @@ export function flattenHit( const field = indexPattern?.fields.getByName(key); - if (!shallow) { + if (params?.shallow === false) { const isNestedField = field?.type === 'nested'; if (Array.isArray(val) && !isNestedField) { val.forEach((v) => isPlainObject(v) && flatten(v, key + '.')); @@ -52,7 +52,10 @@ export function flattenHit( } } - flatten(hit); + flatten(hit.fields); + if (params?.source !== false && hit._source) { + flatten(hit._source as Record); + } return flat; } @@ -70,8 +73,7 @@ export const tabifyDocs = ( const rows = esResponse.hits.hits .map((hit) => { - const toConvert = params.source ? hit._source : hit.fields; - const flat = flattenHit(toConvert, index, params.shallow); + const flat = flattenHit(hit, index, params); for (const [key, value] of Object.entries(flat)) { const field = index?.fields.getByName(key); const fieldName = field?.name || key; diff --git a/src/plugins/data/common/search/tabify/types.ts b/src/plugins/data/common/search/tabify/types.ts index 2dace451059b9..779eec1b71ba8 100644 --- a/src/plugins/data/common/search/tabify/types.ts +++ b/src/plugins/data/common/search/tabify/types.ts @@ -45,9 +45,3 @@ export interface TabbedAggColumn { /** @public **/ export type TabbedAggRow = Record; - -/** @public **/ -export interface TabbedTable { - columns: TabbedAggColumn[]; - rows: TabbedAggRow[]; -} diff --git a/src/plugins/data/common/search/types.ts b/src/plugins/data/common/search/types.ts index c1293f4415458..38e963591f25c 100644 --- a/src/plugins/data/common/search/types.ts +++ b/src/plugins/data/common/search/types.ts @@ -84,11 +84,18 @@ export interface ISearchOptions { * An `AbortSignal` that allows the caller of `search` to abort a search request. */ abortSignal?: AbortSignal; + /** * Use this option to force using a specific server side search strategy. Leave empty to use the default strategy. */ strategy?: string; + /** + * Request the legacy format for the total number of hits. If sending `rest_total_hits_as_int` to + * something other than `true`, this should be set to `false`. + */ + legacyHitsTotal?: boolean; + /** * A session ID, grouping multiple search requests into a single session. */ diff --git a/src/plugins/data/common/utils/index.ts b/src/plugins/data/common/utils/index.ts index 8e17464f35172..f79666f669142 100644 --- a/src/plugins/data/common/utils/index.ts +++ b/src/plugins/data/common/utils/index.ts @@ -8,4 +8,3 @@ /** @internal */ export { shortenDottedString } from './shorten_dotted_string'; -export { tapFirst } from './tap_first'; diff --git a/src/plugins/data/common/utils/tap_first.test.ts b/src/plugins/data/common/utils/tap_first.test.ts deleted file mode 100644 index 5535a27df97db..0000000000000 --- a/src/plugins/data/common/utils/tap_first.test.ts +++ /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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { of } from 'rxjs'; -import { tapFirst } from './tap_first'; - -describe('tapFirst', () => { - it('should tap the first and only the first', () => { - const fn = jest.fn(); - of(1, 2, 3).pipe(tapFirst(fn)).subscribe(); - expect(fn).toBeCalledTimes(1); - expect(fn).lastCalledWith(1); - }); -}); diff --git a/src/plugins/data/common/utils/tap_first.ts b/src/plugins/data/common/utils/tap_first.ts deleted file mode 100644 index d5a9fe19fdbbf..0000000000000 --- a/src/plugins/data/common/utils/tap_first.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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { pipe } from 'rxjs'; -import { tap } from 'rxjs/operators'; - -export function tapFirst(next: (x: T) => void) { - let isFirst = true; - return pipe( - tap((x: T) => { - if (isFirst) next(x); - isFirst = false; - }) - ); -} diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index fc8c44e8d1870..15288d24726f7 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -344,10 +344,6 @@ export { ExpressionFunctionKibanaContext, ExpressionValueSearchContext, KibanaContext, - // tabify - TabbedAggColumn, - TabbedAggRow, - TabbedTable, } from '../common'; export type { AggConfigs, AggConfig } from '../common'; diff --git a/src/plugins/data/public/index_patterns/index_pattern.stub.ts b/src/plugins/data/public/index_patterns/index_pattern.stub.ts index d0b711284df8a..a9dbd1973ef9a 100644 --- a/src/plugins/data/public/index_patterns/index_pattern.stub.ts +++ b/src/plugins/data/public/index_patterns/index_pattern.stub.ts @@ -47,7 +47,7 @@ export class StubIndexPattern { formatHit: Record; fieldsFetcher: Record; formatField: Function; - getFormatterForField: () => { convert: Function }; + getFormatterForField: () => { convert: Function; toJSON: Function }; _reindexFields: Function; stubSetFieldFormat: Function; fields?: FieldSpec[]; @@ -87,6 +87,7 @@ export class StubIndexPattern { this.formatField = this.formatHit.formatField; this.getFormatterForField = () => ({ convert: () => '', + toJSON: () => '{}', }); this._reindexFields = function () { diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index f533af2db9672..408573e12eba5 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -1641,6 +1641,7 @@ export interface ISearchOptions { abortSignal?: AbortSignal; isRestore?: boolean; isStored?: boolean; + legacyHitsTotal?: boolean; sessionId?: string; strategy?: string; } @@ -2367,30 +2368,12 @@ export class SearchSource { // @deprecated fetch(options?: ISearchOptions): Promise>; getField(field: K, recurse?: boolean): SearchSourceFields[K]; - getFields(): { - type?: string | undefined; - query?: import("../..").Query | undefined; - filter?: Filter | Filter[] | (() => Filter | Filter[] | undefined) | undefined; - sort?: Record | Record[] | undefined; - highlight?: any; - highlightAll?: boolean | undefined; - aggs?: any; - from?: number | undefined; - size?: number | undefined; - source?: string | boolean | string[] | undefined; - version?: boolean | undefined; - fields?: SearchFieldValue[] | undefined; - fieldsFromSource?: string | boolean | string[] | undefined; - index?: import("../..").IndexPattern | undefined; - searchAfter?: import("./types").EsQuerySearchAfter | undefined; - timeout?: string | undefined; - terminate_after?: number | undefined; - }; + getFields(recurse?: boolean): SearchSourceFields; getId(): string; getOwnField(field: K): SearchSourceFields[K]; getParent(): SearchSource | undefined; getSearchRequestBody(): Promise; - getSerializedFields(): SearchSourceFields; + getSerializedFields(recurse?: boolean): SearchSourceFields; // Warning: (ae-incompatible-release-tags) The symbol "history" is marked as @public, but its signature references "SearchRequest" which is marked as @internal // // (undocumented) @@ -2414,6 +2397,7 @@ export class SearchSource { export interface SearchSourceFields { // (undocumented) aggs?: any; + // Warning: (ae-forgotten-export) The symbol "SearchFieldValue" needs to be exported by the entry point index.d.ts fields?: SearchFieldValue[]; // @deprecated fieldsFromSource?: NameList; @@ -2492,27 +2476,6 @@ export const syncQueryStateWithUrl: (query: Pick; - -// @public (undocumented) -export interface TabbedTable { - // (undocumented) - columns: TabbedAggColumn[]; - // (undocumented) - rows: TabbedAggRow[]; -} - // Warning: (ae-forgotten-export) The symbol "Timefilter" needs to be exported by the entry point index.d.ts // Warning: (ae-missing-release-tag) "TimefilterContract" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // @@ -2606,7 +2569,6 @@ export const UI_SETTINGS: { // src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:138:7 - (ae-forgotten-export) The symbol "FieldAttrSet" needs to be exported by the entry point index.d.ts // src/plugins/data/common/index_patterns/index_patterns/index_pattern.ts:169:7 - (ae-forgotten-export) The symbol "RuntimeField" needs to be exported by the entry point index.d.ts // src/plugins/data/common/search/aggs/types.ts:139:51 - (ae-forgotten-export) The symbol "AggTypesRegistryStart" needs to be exported by the entry point index.d.ts -// src/plugins/data/common/search/search_source/search_source.ts:187:7 - (ae-forgotten-export) The symbol "SearchFieldValue" needs to be exported by the entry point index.d.ts // src/plugins/data/public/field_formats/field_formats_service.ts:56:3 - (ae-forgotten-export) The symbol "FormatFactory" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:55:23 - (ae-forgotten-export) The symbol "FILTERS" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:55:23 - (ae-forgotten-export) The symbol "getDisplayValueFromFilter" needs to be exported by the entry point index.d.ts @@ -2639,21 +2601,21 @@ export const UI_SETTINGS: { // src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:400:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:400:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:400:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:400:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:402:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:403:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:413:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:414:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:415:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:419:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:420:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:423:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:424:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:427:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:396:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:396:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:396:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:396:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:398:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:399:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:408:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:409:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:410:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:411:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:415:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:416:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:419:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:420:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:423:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:34:5 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts // src/plugins/data/public/search/session/session_service.ts:41:5 - (ae-forgotten-export) The symbol "UrlGeneratorStateMapping" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/data/public/search/expressions/esaggs.test.ts b/src/plugins/data/public/search/expressions/esaggs.test.ts index 18752b82bc181..01ba19a9d8dfa 100644 --- a/src/plugins/data/public/search/expressions/esaggs.test.ts +++ b/src/plugins/data/public/search/expressions/esaggs.test.ts @@ -64,7 +64,6 @@ describe('esaggs expression function - public', () => { aggs: ({ createAggConfigs: jest.fn().mockReturnValue({ foo: 'bar' }), } as unknown) as jest.Mocked, - deserializeFieldFormat: jest.fn().mockImplementation((f: any) => f), indexPatterns: ({ create: jest.fn().mockResolvedValue({}), } as unknown) as jest.Mocked, @@ -99,10 +98,9 @@ describe('esaggs expression function - public', () => { test('calls handleEsaggsRequest with all of the right dependencies', async () => { await definition().fn(null, args, mockHandlers); - expect(handleEsaggsRequest).toHaveBeenCalledWith(null, args, { + expect(handleEsaggsRequest).toHaveBeenCalledWith({ abortSignal: mockHandlers.abortSignal, aggs: { foo: 'bar' }, - deserializeFieldFormat: startDependencies.deserializeFieldFormat, filters: undefined, indexPattern: {}, inspectorAdapters: mockHandlers.inspectorAdapters, @@ -130,8 +128,6 @@ describe('esaggs expression function - public', () => { await definition().fn(input, args, mockHandlers); expect(handleEsaggsRequest).toHaveBeenCalledWith( - input, - args, expect.objectContaining({ filters: input.filters, query: input.query, diff --git a/src/plugins/data/public/search/expressions/esaggs.ts b/src/plugins/data/public/search/expressions/esaggs.ts index 0753e0e15a0a7..6f7411cf07473 100644 --- a/src/plugins/data/public/search/expressions/esaggs.ts +++ b/src/plugins/data/public/search/expressions/esaggs.ts @@ -37,13 +37,7 @@ export function getFunctionDefinition({ return (): EsaggsExpressionFunctionDefinition => ({ ...getEsaggsMeta(), async fn(input, args, { inspectorAdapters, abortSignal, getSearchSessionId }) { - const { - aggs, - deserializeFieldFormat, - indexPatterns, - searchSource, - getNow, - } = await getStartDependencies(); + const { aggs, indexPatterns, searchSource, getNow } = await getStartDependencies(); const indexPattern = await indexPatterns.create(args.index.value, true); const aggConfigs = aggs.createAggConfigs( @@ -51,10 +45,9 @@ export function getFunctionDefinition({ args.aggs!.map((agg) => agg.value) ); - return await handleEsaggsRequest(input, args, { + return await handleEsaggsRequest({ abortSignal: (abortSignal as unknown) as AbortSignal, aggs: aggConfigs, - deserializeFieldFormat, filters: get(input, 'filters', undefined), indexPattern, inspectorAdapters: inspectorAdapters as Adapters, @@ -93,10 +86,9 @@ export function getEsaggs({ return getFunctionDefinition({ getStartDependencies: async () => { const [, , self] = await getStartServices(); - const { fieldFormats, indexPatterns, search, nowProvider } = self; + const { indexPatterns, search, nowProvider } = self; return { aggs: search.aggs, - deserializeFieldFormat: fieldFormats.deserialize.bind(fieldFormats), indexPatterns, searchSource: search.searchSource, getNow: () => nowProvider.get(), diff --git a/src/plugins/data/public/search/session/mocks.ts b/src/plugins/data/public/search/session/mocks.ts index 679898e3e51dd..66cc8cd86f2b6 100644 --- a/src/plugins/data/public/search/session/mocks.ts +++ b/src/plugins/data/public/search/session/mocks.ts @@ -17,6 +17,7 @@ export function getSessionsClientMock(): jest.Mocked { create: jest.fn(), find: jest.fn(), update: jest.fn(), + extend: jest.fn(), delete: jest.fn(), }; } diff --git a/src/plugins/data/public/search/session/sessions_client.ts b/src/plugins/data/public/search/session/sessions_client.ts index 5b0ba51c2f344..91db4e4fb9f1d 100644 --- a/src/plugins/data/public/search/session/sessions_client.ts +++ b/src/plugins/data/public/search/session/sessions_client.ts @@ -68,6 +68,12 @@ export class SessionsClient { }); } + public extend(sessionId: string, keepAlive: string): Promise { + return this.http!.post(`/internal/session/${encodeURIComponent(sessionId)}/_extend`, { + body: JSON.stringify({ keepAlive }), + }); + } + public delete(sessionId: string): Promise { return this.http!.delete(`/internal/session/${encodeURIComponent(sessionId)}`); } diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index 27af11674d061..6e8bef49c6ad5 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -213,10 +213,6 @@ export { IEsSearchRequest, IEsSearchResponse, ES_SEARCH_STRATEGY, - // tabify - TabbedAggColumn, - TabbedAggRow, - TabbedTable, } from '../common'; export { @@ -233,9 +229,9 @@ export { searchUsageObserver, shimAbortSignal, SearchUsage, - SessionService, - ISessionService, - DataApiRequestHandlerContext, + SearchSessionService, + ISearchSessionService, + SearchRequestHandlerContext, DataRequestHandlerContext, } from './search'; diff --git a/src/plugins/data/server/mocks.ts b/src/plugins/data/server/mocks.ts index 6d8e085554760..933e831b8c029 100644 --- a/src/plugins/data/server/mocks.ts +++ b/src/plugins/data/server/mocks.ts @@ -6,9 +6,14 @@ * Public License, v 1. */ -import { createSearchSetupMock, createSearchStartMock } from './search/mocks'; +import { + createSearchSetupMock, + createSearchStartMock, + createSearchRequestHandlerContext, +} from './search/mocks'; import { createFieldFormatsSetupMock, createFieldFormatsStartMock } from './field_formats/mocks'; import { createIndexPatternsStartMock } from './index_patterns/mocks'; +import { DataRequestHandlerContext } from './search'; function createSetupContract() { return { @@ -25,7 +30,14 @@ function createStartContract() { }; } +function createRequestHandlerContext() { + return ({ + search: createSearchRequestHandlerContext(), + } as unknown) as jest.Mocked; +} + export const dataPluginMock = { createSetupContract, createStartContract, + createRequestHandlerContext, }; diff --git a/src/plugins/data/server/search/es_search/es_search_strategy.ts b/src/plugins/data/server/search/es_search/es_search_strategy.ts index c176a50627b92..2d9b16ac8b00b 100644 --- a/src/plugins/data/server/search/es_search/es_search_strategy.ts +++ b/src/plugins/data/server/search/es_search/es_search_strategy.ts @@ -13,7 +13,7 @@ import type { Logger, SharedGlobalConfig } from 'kibana/server'; import type { ISearchStrategy } from '../types'; import type { SearchUsage } from '../collectors'; import { getDefaultSearchParams, getShardTimeout, shimAbortSignal } from './request_utils'; -import { toKibanaSearchResponse } from './response_utils'; +import { shimHitsTotal, toKibanaSearchResponse } from './response_utils'; import { searchUsageObserver } from '../collectors/usage'; import { getKbnServerError, KbnServerError } from '../../../../kibana_utils/server'; @@ -29,7 +29,7 @@ export const esSearchStrategyProvider = ( * @throws `KbnServerError` * @returns `Observable>` */ - search: (request, { abortSignal }, { esClient, uiSettingsClient }) => { + search: (request, { abortSignal, ...options }, { esClient, uiSettingsClient }) => { // Only default index pattern type is supported here. // See data_enhanced for other type support. if (request.indexType) { @@ -46,7 +46,8 @@ export const esSearchStrategyProvider = ( }; const promise = esClient.asCurrentUser.search>(params); const { body } = await shimAbortSignal(promise, abortSignal); - return toKibanaSearchResponse(body); + const response = shimHitsTotal(body, options); + return toKibanaSearchResponse(response); } catch (e) { throw getKbnServerError(e); } diff --git a/src/plugins/data/server/search/es_search/response_utils.test.ts b/src/plugins/data/server/search/es_search/response_utils.test.ts index 8c973b92c7ffe..7cb5705ecf8ef 100644 --- a/src/plugins/data/server/search/es_search/response_utils.test.ts +++ b/src/plugins/data/server/search/es_search/response_utils.test.ts @@ -6,7 +6,7 @@ * Public License, v 1. */ -import { getTotalLoaded, toKibanaSearchResponse } from './response_utils'; +import { getTotalLoaded, toKibanaSearchResponse, shimHitsTotal } from './response_utils'; import { SearchResponse } from 'elasticsearch'; describe('response utils', () => { @@ -55,4 +55,79 @@ describe('response utils', () => { }); }); }); + + describe('shimHitsTotal', () => { + test('returns the total if it is already numeric', () => { + const result = shimHitsTotal({ + hits: { + total: 5, + }, + } as any); + expect(result).toEqual({ + hits: { + total: 5, + }, + }); + }); + + test('returns the total if it is inside `value`', () => { + const result = shimHitsTotal({ + hits: { + total: { + value: 5, + }, + }, + } as any); + expect(result).toEqual({ + hits: { + total: 5, + }, + }); + }); + + test('returns other properties from the response', () => { + const result = shimHitsTotal({ + _shards: {}, + hits: { + hits: [], + total: { + value: 5, + }, + }, + } as any); + expect(result).toEqual({ + _shards: {}, + hits: { + hits: [], + total: 5, + }, + }); + }); + + test('returns the response as-is if `legacyHitsTotal` is `false`', () => { + const result = shimHitsTotal( + { + _shards: {}, + hits: { + hits: [], + total: { + value: 5, + relation: 'eq', + }, + }, + } as any, + { legacyHitsTotal: false } + ); + expect(result).toEqual({ + _shards: {}, + hits: { + hits: [], + total: { + value: 5, + relation: 'eq', + }, + }, + }); + }); + }); }); diff --git a/src/plugins/data/server/search/es_search/response_utils.ts b/src/plugins/data/server/search/es_search/response_utils.ts index d4fa14866fd97..3417f24cf420a 100644 --- a/src/plugins/data/server/search/es_search/response_utils.ts +++ b/src/plugins/data/server/search/es_search/response_utils.ts @@ -7,6 +7,7 @@ */ import { SearchResponse } from 'elasticsearch'; +import { ISearchOptions } from '../../../common'; /** * Get the `total`/`loaded` for this response (see `IKibanaSearchResponse`). Note that `skipped` is @@ -31,3 +32,20 @@ export function toKibanaSearchResponse(rawResponse: SearchResponse) { ...getTotalLoaded(rawResponse), }; } + +/** + * Temporary workaround until https://github.com/elastic/kibana/issues/26356 is addressed. + * Since we are setting `track_total_hits` in the request, `hits.total` will be an object + * containing the `value`. + * + * @internal + */ +export function shimHitsTotal( + response: SearchResponse, + { legacyHitsTotal = true }: ISearchOptions = {} +) { + if (!legacyHitsTotal) return response; + const total = (response.hits?.total as any)?.value ?? response.hits?.total; + const hits = { ...response.hits, total }; + return { ...response, hits }; +} diff --git a/src/plugins/data/server/search/expressions/esaggs.test.ts b/src/plugins/data/server/search/expressions/esaggs.test.ts index d71abbcd460c6..229665d338874 100644 --- a/src/plugins/data/server/search/expressions/esaggs.test.ts +++ b/src/plugins/data/server/search/expressions/esaggs.test.ts @@ -66,7 +66,6 @@ describe('esaggs expression function - server', () => { aggs: ({ createAggConfigs: jest.fn().mockReturnValue({ foo: 'bar' }), } as unknown) as jest.Mocked, - deserializeFieldFormat: jest.fn().mockImplementation((f: any) => f), indexPatterns: ({ create: jest.fn().mockResolvedValue({}), } as unknown) as jest.Mocked, @@ -107,10 +106,9 @@ describe('esaggs expression function - server', () => { test('calls handleEsaggsRequest with all of the right dependencies', async () => { await definition().fn(null, args, mockHandlers); - expect(handleEsaggsRequest).toHaveBeenCalledWith(null, args, { + expect(handleEsaggsRequest).toHaveBeenCalledWith({ abortSignal: mockHandlers.abortSignal, aggs: { foo: 'bar' }, - deserializeFieldFormat: startDependencies.deserializeFieldFormat, filters: undefined, indexPattern: {}, inspectorAdapters: mockHandlers.inspectorAdapters, @@ -138,8 +136,6 @@ describe('esaggs expression function - server', () => { await definition().fn(input, args, mockHandlers); expect(handleEsaggsRequest).toHaveBeenCalledWith( - input, - args, expect.objectContaining({ filters: input.filters, query: input.query, diff --git a/src/plugins/data/server/search/expressions/esaggs.ts b/src/plugins/data/server/search/expressions/esaggs.ts index 8f73f970c7ef8..13168879b8bad 100644 --- a/src/plugins/data/server/search/expressions/esaggs.ts +++ b/src/plugins/data/server/search/expressions/esaggs.ts @@ -53,12 +53,7 @@ export function getFunctionDefinition({ ); } - const { - aggs, - deserializeFieldFormat, - indexPatterns, - searchSource, - } = await getStartDependencies(kibanaRequest); + const { aggs, indexPatterns, searchSource } = await getStartDependencies(kibanaRequest); const indexPattern = await indexPatterns.create(args.index.value, true); const aggConfigs = aggs.createAggConfigs( @@ -66,10 +61,9 @@ export function getFunctionDefinition({ args.aggs!.map((agg) => agg.value) ); - return await handleEsaggsRequest(input, args, { + return await handleEsaggsRequest({ abortSignal: (abortSignal as unknown) as AbortSignal, aggs: aggConfigs, - deserializeFieldFormat, filters: get(input, 'filters', undefined), indexPattern, inspectorAdapters: inspectorAdapters as Adapters, @@ -106,16 +100,13 @@ export function getEsaggs({ }): () => EsaggsExpressionFunctionDefinition { return getFunctionDefinition({ getStartDependencies: async (request: KibanaRequest) => { - const [{ elasticsearch, savedObjects, uiSettings }, , self] = await getStartServices(); - const { fieldFormats, indexPatterns, search } = self; + const [{ elasticsearch, savedObjects }, , self] = await getStartServices(); + const { indexPatterns, search } = self; const esClient = elasticsearch.client.asScoped(request); const savedObjectsClient = savedObjects.getScopedClient(request); - const uiSettingsClient = uiSettings.asScopedToClient(savedObjectsClient); - const scopedFieldFormats = await fieldFormats.fieldFormatServiceFactory(uiSettingsClient); return { aggs: await search.aggs.asScopedToClient(savedObjectsClient, esClient.asCurrentUser), - deserializeFieldFormat: scopedFieldFormats.deserialize.bind(scopedFieldFormats), indexPatterns: await indexPatterns.indexPatternsServiceFactory( savedObjectsClient, esClient.asCurrentUser diff --git a/src/plugins/data/server/search/index.ts b/src/plugins/data/server/search/index.ts index a333424110065..301b0989b5183 100644 --- a/src/plugins/data/server/search/index.ts +++ b/src/plugins/data/server/search/index.ts @@ -10,5 +10,4 @@ export * from './types'; export * from './es_search'; export { usageProvider, SearchUsage, searchUsageObserver } from './collectors'; export * from './aggs'; -export { shimHitsTotal } from './routes'; export * from './session'; diff --git a/src/plugins/data/server/search/mocks.ts b/src/plugins/data/server/search/mocks.ts index 2d2c546ffa4cf..63917ebc0fbab 100644 --- a/src/plugins/data/server/search/mocks.ts +++ b/src/plugins/data/server/search/mocks.ts @@ -10,6 +10,8 @@ import { ISearchSetup, ISearchStart } from './types'; import { searchAggsSetupMock, searchAggsStartMock } from './aggs/mocks'; import { searchSourceMock } from './search_source/mocks'; +export { createSearchSessionsClientMock } from './session/mocks'; + export function createSearchSetupMock(): jest.Mocked { return { aggs: searchAggsSetupMock(), @@ -22,10 +24,21 @@ export function createSearchStartMock(): jest.Mocked { return { aggs: searchAggsStartMock(), getSearchStrategy: jest.fn(), - asScoped: jest.fn().mockReturnValue({ - search: jest.fn(), - cancel: jest.fn(), - }), + asScoped: jest.fn().mockReturnValue(createSearchRequestHandlerContext()), searchSource: searchSourceMock.createStartContract(), }; } + +export function createSearchRequestHandlerContext() { + return { + search: jest.fn(), + cancel: jest.fn(), + extend: jest.fn(), + saveSession: jest.fn(), + getSession: jest.fn(), + findSessions: jest.fn(), + updateSession: jest.fn(), + extendSession: jest.fn(), + cancelSession: jest.fn(), + }; +} diff --git a/src/plugins/data/server/search/routes/bsearch.ts b/src/plugins/data/server/search/routes/bsearch.ts index e30b7bdaa8402..58424939fcd44 100644 --- a/src/plugins/data/server/search/routes/bsearch.ts +++ b/src/plugins/data/server/search/routes/bsearch.ts @@ -6,23 +6,18 @@ * Public License, v 1. */ -import { catchError, first, map } from 'rxjs/operators'; -import { CoreStart, KibanaRequest } from 'src/core/server'; +import { catchError, first } from 'rxjs/operators'; import { BfetchServerSetup } from 'src/plugins/bfetch/server'; import { IKibanaSearchRequest, IKibanaSearchResponse, - ISearchClient, ISearchOptions, } from '../../../common/search'; -import { shimHitsTotal } from './shim_hits_total'; - -type GetScopedProider = (coreStart: CoreStart) => (request: KibanaRequest) => ISearchClient; +import { ISearchStart } from '../types'; export function registerBsearchRoute( bfetch: BfetchServerSetup, - coreStartPromise: Promise<[CoreStart, {}, {}]>, - getScopedProvider: GetScopedProider + getScoped: ISearchStart['asScoped'] ): void { bfetch.addBatchProcessingRoute< { request: IKibanaSearchRequest; options?: ISearchOptions }, @@ -34,20 +29,11 @@ export function registerBsearchRoute( * @throws `KibanaServerError` */ onBatchItem: async ({ request: requestData, options }) => { - const coreStart = await coreStartPromise; - const search = getScopedProvider(coreStart[0])(request); + const search = getScoped(request); return search .search(requestData, options) .pipe( first(), - map((response) => { - return { - ...response, - ...{ - rawResponse: shimHitsTotal(response.rawResponse), - }, - }; - }), catchError((err) => { // Re-throw as object, to get attributes passed to the client // eslint-disable-next-line no-throw-literal diff --git a/src/plugins/data/server/search/routes/call_msearch.ts b/src/plugins/data/server/search/routes/call_msearch.ts index fc30e2f29c3ef..e6ff5f454079b 100644 --- a/src/plugins/data/server/search/routes/call_msearch.ts +++ b/src/plugins/data/server/search/routes/call_msearch.ts @@ -12,9 +12,8 @@ import { SearchResponse } from 'elasticsearch'; import { IUiSettingsClient, IScopedClusterClient, SharedGlobalConfig } from 'src/core/server'; import type { MsearchRequestBody, MsearchResponse } from '../../../common/search/search_source'; -import { shimHitsTotal } from './shim_hits_total'; import { getKbnServerError } from '../../../../kibana_utils/server'; -import { getShardTimeout, getDefaultSearchParams, shimAbortSignal } from '..'; +import { getShardTimeout, getDefaultSearchParams, shimAbortSignal, shimHitsTotal } from '..'; /** @internal */ export function convertRequestBody( diff --git a/src/plugins/data/server/search/routes/index.ts b/src/plugins/data/server/search/routes/index.ts index ea20240f6ae19..25e0353fb4a27 100644 --- a/src/plugins/data/server/search/routes/index.ts +++ b/src/plugins/data/server/search/routes/index.ts @@ -9,4 +9,3 @@ export * from './call_msearch'; export * from './msearch'; export * from './search'; -export * from './shim_hits_total'; diff --git a/src/plugins/data/server/search/routes/search.ts b/src/plugins/data/server/search/routes/search.ts index 6d2da4c1e63dd..e556e3ca49ec2 100644 --- a/src/plugins/data/server/search/routes/search.ts +++ b/src/plugins/data/server/search/routes/search.ts @@ -9,7 +9,6 @@ import { first } from 'rxjs/operators'; import { schema } from '@kbn/config-schema'; import { getRequestAbortedSignal } from '../../lib'; -import { shimHitsTotal } from './shim_hits_total'; import { reportServerError } from '../../../../kibana_utils/server'; import type { DataPluginRouter } from '../types'; @@ -27,6 +26,7 @@ export function registerSearchRoute(router: DataPluginRouter): void { body: schema.object( { + legacyHitsTotal: schema.maybe(schema.boolean()), sessionId: schema.maybe(schema.string()), isStored: schema.maybe(schema.boolean()), isRestore: schema.maybe(schema.boolean()), @@ -36,7 +36,13 @@ export function registerSearchRoute(router: DataPluginRouter): void { }, }, async (context, request, res) => { - const { sessionId, isStored, isRestore, ...searchRequest } = request.body; + const { + legacyHitsTotal = true, + sessionId, + isStored, + isRestore, + ...searchRequest + } = request.body; const { strategy, id } = request.params; const abortSignal = getRequestAbortedSignal(request.events.aborted$); @@ -47,6 +53,7 @@ export function registerSearchRoute(router: DataPluginRouter): void { { abortSignal, strategy, + legacyHitsTotal, sessionId, isStored, isRestore, @@ -55,14 +62,7 @@ export function registerSearchRoute(router: DataPluginRouter): void { .pipe(first()) .toPromise(); - return res.ok({ - body: { - ...response, - ...{ - rawResponse: shimHitsTotal(response.rawResponse), - }, - }, - }); + return res.ok({ body: response }); } catch (err) { return reportServerError(res, err); } diff --git a/src/plugins/data/server/search/routes/shim_hits_total.test.ts b/src/plugins/data/server/search/routes/shim_hits_total.test.ts deleted file mode 100644 index 6dcd7c3ff6c70..0000000000000 --- a/src/plugins/data/server/search/routes/shim_hits_total.test.ts +++ /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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { shimHitsTotal } from './shim_hits_total'; - -describe('shimHitsTotal', () => { - test('returns the total if it is already numeric', () => { - const result = shimHitsTotal({ - hits: { - total: 5, - }, - } as any); - expect(result).toEqual({ - hits: { - total: 5, - }, - }); - }); - - test('returns the total if it is inside `value`', () => { - const result = shimHitsTotal({ - hits: { - total: { - value: 5, - }, - }, - } as any); - expect(result).toEqual({ - hits: { - total: 5, - }, - }); - }); - - test('returns other properties from the response', () => { - const result = shimHitsTotal({ - _shards: {}, - hits: { - hits: [], - total: { - value: 5, - }, - }, - } as any); - expect(result).toEqual({ - _shards: {}, - hits: { - hits: [], - total: 5, - }, - }); - }); -}); diff --git a/src/plugins/data/server/search/routes/shim_hits_total.ts b/src/plugins/data/server/search/routes/shim_hits_total.ts deleted file mode 100644 index 4b56d6394e0db..0000000000000 --- a/src/plugins/data/server/search/routes/shim_hits_total.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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { SearchResponse } from 'elasticsearch'; - -/** - * Temporary workaround until https://github.com/elastic/kibana/issues/26356 is addressed. - * Since we are setting `track_total_hits` in the request, `hits.total` will be an object - * containing the `value`. - * - * @internal - */ -export function shimHitsTotal(response: SearchResponse) { - const total = (response.hits?.total as any)?.value ?? response.hits?.total; - const hits = { ...response.hits, total }; - return { ...response, hits }; -} diff --git a/src/plugins/data/server/search/search_service.test.ts b/src/plugins/data/server/search/search_service.test.ts index 4b0a280c3c1ca..37b41516611e4 100644 --- a/src/plugins/data/server/search/search_service.test.ts +++ b/src/plugins/data/server/search/search_service.test.ts @@ -17,6 +17,18 @@ import { createIndexPatternsStartMock } from '../index_patterns/mocks'; import { SearchService, SearchServiceSetupDependencies } from './search_service'; import { bfetchPluginMock } from '../../../bfetch/server/mocks'; import { of } from 'rxjs'; +import { + IEsSearchRequest, + IEsSearchResponse, + IScopedSearchClient, + IScopedSearchSessionsClient, + ISearchSessionService, + ISearchStart, + ISearchStrategy, +} from '.'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { expressionsPluginMock } from '../../../expressions/public/mocks'; +import { createSearchSessionsClientMock } from './mocks'; describe('Search service', () => { let plugin: SearchService; @@ -70,4 +82,136 @@ describe('Search service', () => { expect(start).toHaveProperty('getSearchStrategy'); }); }); + + describe('asScopedProvider', () => { + let mockScopedClient: IScopedSearchClient; + let searcPluginStart: ISearchStart>; + let mockStrategy: jest.Mocked; + let mockSessionService: ISearchSessionService; + let mockSessionClient: jest.Mocked; + const sessionId = '1234'; + + beforeEach(() => { + mockStrategy = { search: jest.fn().mockReturnValue(of({})) }; + + mockSessionClient = createSearchSessionsClientMock(); + mockSessionService = { + asScopedProvider: () => (request: any) => mockSessionClient, + }; + + const pluginSetup = plugin.setup(mockCoreSetup, { + bfetch: bfetchPluginMock.createSetupContract(), + expressions: expressionsPluginMock.createSetupContract(), + }); + pluginSetup.registerSearchStrategy('es', mockStrategy); + pluginSetup.__enhance({ + defaultStrategy: 'es', + sessionService: mockSessionService, + }); + + searcPluginStart = plugin.start(mockCoreStart, { + fieldFormats: createFieldFormatsStartMock(), + indexPatterns: createIndexPatternsStartMock(), + }); + + const r: any = {}; + + mockScopedClient = searcPluginStart.asScoped(r); + }); + + describe('search', () => { + it('searches using the original request if not restoring, trackId is not called if there is no id in the response', async () => { + const searchRequest = { params: {} }; + const options = { sessionId, isStored: false, isRestore: false }; + mockSessionClient.trackId = jest.fn(); + + mockStrategy.search.mockReturnValue( + of({ + rawResponse: {} as any, + }) + ); + + await mockScopedClient.search(searchRequest, options).toPromise(); + + const [request, callOptions] = mockStrategy.search.mock.calls[0]; + + expect(callOptions).toBe(options); + expect(request).toBe(searchRequest); + expect(mockSessionClient.trackId).not.toBeCalled(); + }); + + it('searches using the original request if `id` is provided', async () => { + const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; + const searchRequest = { id: searchId, params: {} }; + const options = { sessionId, isStored: true, isRestore: true }; + + await mockScopedClient.search(searchRequest, options).toPromise(); + + const [request, callOptions] = mockStrategy.search.mock.calls[0]; + expect(callOptions).toBe(options); + expect(request).toBe(searchRequest); + }); + + it('searches by looking up an `id` if restoring and `id` is not provided', async () => { + const searchRequest = { params: {} }; + const options = { sessionId, isStored: true, isRestore: true }; + + mockSessionClient.getId = jest.fn().mockResolvedValueOnce('my_id'); + + await mockScopedClient.search(searchRequest, options).toPromise(); + + const [request, callOptions] = mockStrategy.search.mock.calls[0]; + expect(callOptions).toBe(options); + expect(request).toStrictEqual({ ...searchRequest, id: 'my_id' }); + }); + + it('calls `trackId` for every response, if the response contains an `id` and not restoring', async () => { + const searchRequest = { params: {} }; + const options = { sessionId, isStored: false, isRestore: false }; + mockSessionClient.trackId = jest.fn(); + + mockStrategy.search.mockReturnValue( + of( + { + id: 'my_id', + rawResponse: {} as any, + }, + { + id: 'my_id', + rawResponse: {} as any, + } + ) + ); + + await mockScopedClient.search(searchRequest, options).toPromise(); + + expect(mockSessionClient.trackId).toBeCalledTimes(2); + + expect(mockSessionClient.trackId.mock.calls[0]).toEqual([searchRequest, 'my_id', options]); + expect(mockSessionClient.trackId.mock.calls[1]).toEqual([searchRequest, 'my_id', options]); + }); + + it('does not call `trackId` if restoring', async () => { + const searchRequest = { params: {} }; + const options = { sessionId, isStored: true, isRestore: true }; + mockSessionClient.getId = jest.fn().mockResolvedValueOnce('my_id'); + mockSessionClient.trackId = jest.fn(); + + await mockScopedClient.search(searchRequest, options).toPromise(); + + expect(mockSessionClient.trackId).not.toBeCalled(); + }); + + it('does not call `trackId` if no session id provided', async () => { + const searchRequest = { params: {} }; + const options = {}; + mockSessionClient.getId = jest.fn().mockResolvedValueOnce('my_id'); + mockSessionClient.trackId = jest.fn(); + + await mockScopedClient.search(searchRequest, options).toPromise(); + + expect(mockSessionClient.trackId).not.toBeCalled(); + }); + }); + }); }); diff --git a/src/plugins/data/server/search/search_service.ts b/src/plugins/data/server/search/search_service.ts index 63593bbe84a08..24a2eff68482f 100644 --- a/src/plugins/data/server/search/search_service.ts +++ b/src/plugins/data/server/search/search_service.ts @@ -6,8 +6,9 @@ * Public License, v 1. */ -import { BehaviorSubject, Observable, throwError } from 'rxjs'; +import { BehaviorSubject, from, Observable, throwError } from 'rxjs'; import { pick } from 'lodash'; +import moment from 'moment'; import { CoreSetup, CoreStart, @@ -18,10 +19,11 @@ import { SharedGlobalConfig, StartServicesAccessor, } from 'src/core/server'; -import { first } from 'rxjs/operators'; +import { first, switchMap, tap } from 'rxjs/operators'; import { BfetchServerSetup } from 'src/plugins/bfetch/server'; import { ExpressionsServerSetup } from 'src/plugins/expressions/server'; import type { + IScopedSearchClient, ISearchSetup, ISearchStart, ISearchStrategy, @@ -34,7 +36,7 @@ import { AggsService } from './aggs'; import { FieldFormatsStart } from '../field_formats'; import { IndexPatternsServiceStart } from '../index_patterns'; -import { getCallMsearch, registerMsearchRoute, registerSearchRoute, shimHitsTotal } from './routes'; +import { getCallMsearch, registerMsearchRoute, registerSearchRoute } from './routes'; import { ES_SEARCH_STRATEGY, esSearchStrategyProvider } from './es_search'; import { DataPluginStart } from '../plugin'; import { UsageCollectionSetup } from '../../../usage_collection/server'; @@ -46,7 +48,6 @@ import { IEsSearchResponse, IKibanaSearchRequest, IKibanaSearchResponse, - ISearchClient, ISearchOptions, kibana, kibanaContext, @@ -62,7 +63,7 @@ import { } from '../../common/search/aggs/buckets/shard_delay'; import { aggShardDelay } from '../../common/search/aggs/buckets/shard_delay_fn'; import { ConfigSchema } from '../../config'; -import { SessionService, IScopedSessionService, ISessionService } from './session'; +import { ISearchSessionService, SearchSessionService } from './session'; import { KbnServerError } from '../../../kibana_utils/server'; import { registerBsearchRoute } from './routes/bsearch'; @@ -92,14 +93,14 @@ export class SearchService implements Plugin { private readonly searchSourceService = new SearchSourceService(); private defaultSearchStrategyName: string = ES_SEARCH_STRATEGY; private searchStrategies: StrategyMap = {}; - private sessionService: ISessionService; - private coreStart?: CoreStart; + private sessionService: ISearchSessionService; + private asScoped!: ISearchStart['asScoped']; constructor( private initializerContext: PluginInitializerContext, private readonly logger: Logger ) { - this.sessionService = new SessionService(); + this.sessionService = new SearchSessionService(); } public setup( @@ -116,16 +117,10 @@ export class SearchService implements Plugin { registerSearchRoute(router); registerMsearchRoute(router, routeDependencies); - core.getStartServices().then(([coreStart]) => { - this.coreStart = coreStart; - }); - core.http.registerRouteHandlerContext( 'search', async (context, request) => { - const search = this.asScopedProvider(this.coreStart!)(request); - const session = this.sessionService.asScopedProvider(this.coreStart!)(request); - return { ...search, session }; + return this.asScoped(request); } ); @@ -138,7 +133,7 @@ export class SearchService implements Plugin { ) ); - registerBsearchRoute(bfetch, core.getStartServices(), this.asScopedProvider); + registerBsearchRoute(bfetch, (request: KibanaRequest) => this.asScoped(request)); core.savedObjects.registerType(searchTelemetry); if (usageCollection) { @@ -181,7 +176,7 @@ export class SearchService implements Plugin { { fieldFormats, indexPatterns }: SearchServiceStartDependencies ): ISearchStart { const { elasticsearch, savedObjects, uiSettings } = core; - const asScoped = this.asScopedProvider(core); + this.asScoped = this.asScopedProvider(core); return { aggs: this.aggsService.start({ fieldFormats, @@ -189,7 +184,7 @@ export class SearchService implements Plugin { indexPatterns, }), getSearchStrategy: this.getSearchStrategy, - asScoped, + asScoped: this.asScoped, searchSource: { asScoped: async (request: KibanaRequest) => { const esClient = elasticsearch.client.asScoped(request); @@ -208,8 +203,8 @@ export class SearchService implements Plugin { const searchSourceDependencies: SearchSourceDependencies = { getConfig: (key: string): T => uiSettingsCache[key], - search: asScoped(request).search, - onResponse: (req, res) => shimHitsTotal(res), + search: this.asScoped(request).search, + onResponse: (req, res) => res, legacy: { callMsearch: getCallMsearch({ esClient, @@ -241,26 +236,54 @@ export class SearchService implements Plugin { this.searchStrategies[name] = strategy; }; - private search = < + private getSearchStrategy = < SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest, SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse >( - session: IScopedSessionService, + name: string = this.defaultSearchStrategyName + ): ISearchStrategy => { + this.logger.debug(`Get strategy ${name}`); + const strategy = this.searchStrategies[name]; + if (!strategy) { + throw new KbnServerError(`Search strategy ${name} not found`, 404); + } + return strategy; + }; + + private search = < + SearchStrategyRequest extends IKibanaSearchRequest, + SearchStrategyResponse extends IKibanaSearchResponse + >( + deps: SearchStrategyDependencies, request: SearchStrategyRequest, - options: ISearchOptions, - deps: SearchStrategyDependencies + options: ISearchOptions ) => { try { const strategy = this.getSearchStrategy( options.strategy ); - return session.search(strategy, request, options, deps); + + const getSearchRequest = async () => + !options.sessionId || !options.isRestore || request.id + ? request + : { + ...request, + id: await deps.searchSessionsClient.getId(request, options), + }; + + return from(getSearchRequest()).pipe( + switchMap((searchRequest) => strategy.search(searchRequest, options, deps)), + tap((response) => { + if (!options.sessionId || !response.id || options.isRestore) return; + deps.searchSessionsClient.trackId(request, response.id, options); + }) + ); } catch (e) { return throwError(e); } }; - private cancel = (id: string, options: ISearchOptions, deps: SearchStrategyDependencies) => { + private cancel = (deps: SearchStrategyDependencies, id: string, options: ISearchOptions = {}) => { const strategy = this.getSearchStrategy(options.strategy); if (!strategy.cancel) { throw new KbnServerError( @@ -272,10 +295,10 @@ export class SearchService implements Plugin { }; private extend = ( + deps: SearchStrategyDependencies, id: string, keepAlive: string, - options: ISearchOptions, - deps: SearchStrategyDependencies + options: ISearchOptions = {} ) => { const strategy = this.getSearchStrategy(options.strategy); if (!strategy.extend) { @@ -284,36 +307,71 @@ export class SearchService implements Plugin { return strategy.extend(id, keepAlive, options, deps); }; - private getSearchStrategy = < - SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest, - SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse - >( - name: string = this.defaultSearchStrategyName - ): ISearchStrategy => { - this.logger.debug(`Get strategy ${name}`); - const strategy = this.searchStrategies[name]; - if (!strategy) { - throw new KbnServerError(`Search strategy ${name} not found`, 404); + private cancelSession = async (deps: SearchStrategyDependencies, sessionId: string) => { + const searchIdMapping = await deps.searchSessionsClient.getSearchIdMapping(sessionId); + const response = await deps.searchSessionsClient.cancel(sessionId); + + for (const [searchId, strategyName] of searchIdMapping.entries()) { + const searchOptions = { + sessionId, + strategy: strategyName, + isStored: true, + }; + this.cancel(deps, searchId, searchOptions); } - return strategy; + + return response; + }; + + private extendSession = async ( + deps: SearchStrategyDependencies, + sessionId: string, + expires: Date + ) => { + const searchIdMapping = await deps.searchSessionsClient.getSearchIdMapping(sessionId); + const keepAlive = `${moment(expires).diff(moment())}ms`; + + for (const [searchId, strategyName] of searchIdMapping.entries()) { + const searchOptions = { + sessionId, + strategy: strategyName, + isStored: true, + }; + await this.extend(deps, searchId, keepAlive, searchOptions); + } + + return deps.searchSessionsClient.extend(sessionId, expires); }; private asScopedProvider = (core: CoreStart) => { const { elasticsearch, savedObjects, uiSettings } = core; const getSessionAsScoped = this.sessionService.asScopedProvider(core); - return (request: KibanaRequest): ISearchClient => { - const scopedSession = getSessionAsScoped(request); + return (request: KibanaRequest): IScopedSearchClient => { const savedObjectsClient = savedObjects.getScopedClient(request); + const searchSessionsClient = getSessionAsScoped(request); const deps = { + searchSessionsClient, savedObjectsClient, esClient: elasticsearch.client.asScoped(request), uiSettingsClient: uiSettings.asScopedToClient(savedObjectsClient), }; return { - search: (searchRequest, options = {}) => - this.search(scopedSession, searchRequest, options, deps), - cancel: (id, options = {}) => this.cancel(id, options, deps), - extend: (id, keepAlive, options = {}) => this.extend(id, keepAlive, options, deps), + search: < + SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest, + SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse + >( + searchRequest: SearchStrategyRequest, + options: ISearchOptions = {} + ) => + this.search(deps, searchRequest, options), + cancel: this.cancel.bind(this, deps), + extend: this.extend.bind(this, deps), + saveSession: searchSessionsClient.save, + getSession: searchSessionsClient.get, + findSessions: searchSessionsClient.find, + updateSession: searchSessionsClient.update, + extendSession: this.extendSession.bind(this, deps), + cancelSession: this.cancelSession.bind(this, deps), }; }; }; diff --git a/src/plugins/data/server/search/session/mocks.ts b/src/plugins/data/server/search/session/mocks.ts new file mode 100644 index 0000000000000..f75ba6fcc6620 --- /dev/null +++ b/src/plugins/data/server/search/session/mocks.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { IScopedSearchSessionsClient } from './types'; + +export function createSearchSessionsClientMock(): jest.Mocked< + IScopedSearchSessionsClient +> { + return { + getId: jest.fn(), + trackId: jest.fn(), + getSearchIdMapping: jest.fn(), + save: jest.fn(), + get: jest.fn(), + find: jest.fn(), + update: jest.fn(), + cancel: jest.fn(), + extend: jest.fn(), + }; +} diff --git a/src/plugins/data/server/search/session/session_service.test.ts b/src/plugins/data/server/search/session/session_service.test.ts deleted file mode 100644 index 2b981e3050947..0000000000000 --- a/src/plugins/data/server/search/session/session_service.test.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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { of } from 'rxjs'; -import { SearchStrategyDependencies } from '../types'; -import { SessionService } from './session_service'; - -describe('SessionService', () => { - it('search invokes `strategy.search`', async () => { - const service = new SessionService(); - const mockSearch = jest.fn().mockReturnValue(of({})); - const mockStrategy = { search: mockSearch }; - const mockRequest = { id: 'bar' }; - const mockOptions = { sessionId: '1234' }; - const mockDeps = { savedObjectsClient: {} } as SearchStrategyDependencies; - - await service.search(mockStrategy, mockRequest, mockOptions, mockDeps); - - expect(mockSearch).toHaveBeenCalled(); - expect(mockSearch).toHaveBeenCalledWith(mockRequest, mockOptions, mockDeps); - }); -}); diff --git a/src/plugins/data/server/search/session/session_service.ts b/src/plugins/data/server/search/session/session_service.ts index d6b7a582fe764..dc4f19f126882 100644 --- a/src/plugins/data/server/search/session/session_service.ts +++ b/src/plugins/data/server/search/session/session_service.ts @@ -6,27 +6,41 @@ * Public License, v 1. */ -import { CoreStart, KibanaRequest } from 'kibana/server'; -import { IKibanaSearchRequest, IKibanaSearchResponse } from '../../../common'; -import { ISearchStrategy } from '../types'; -import { ISessionService } from './types'; +import { ISearchSessionService } from './types'; /** - * The OSS session service. See data_enhanced in X-Pack for the search session service. + * The OSS session service, which leaves most search session-related logic unimplemented. + * @see x-pack/plugins/data_enhanced/server/search/session/session_service.ts + * @internal */ -export class SessionService implements ISessionService { +export class SearchSessionService implements ISearchSessionService { constructor() {} - public search( - strategy: ISearchStrategy, - ...args: Parameters['search']> - ) { - return strategy.search(...args); - } - - public asScopedProvider(core: CoreStart) { - return (request: KibanaRequest) => ({ - search: this.search, + public asScopedProvider() { + return () => ({ + getId: () => { + throw new Error('getId not implemented in OSS search session service'); + }, + trackId: async () => {}, + getSearchIdMapping: async () => new Map(), + save: async () => { + throw new Error('save not implemented in OSS search session service'); + }, + get: async () => { + throw new Error('get not implemented in OSS search session service'); + }, + find: async () => { + throw new Error('find not implemented in OSS search session service'); + }, + update: async () => { + throw new Error('update not implemented in OSS search session service'); + }, + extend: async () => { + throw new Error('extend not implemented in OSS search session service'); + }, + cancel: async () => { + throw new Error('cancel not implemented in OSS search session service'); + }, }); } } diff --git a/src/plugins/data/server/search/session/types.ts b/src/plugins/data/server/search/session/types.ts index 97ef7aa8e61c3..d3220c8f7fbca 100644 --- a/src/plugins/data/server/search/session/types.ts +++ b/src/plugins/data/server/search/session/types.ts @@ -6,19 +6,32 @@ * Public License, v 1. */ -import { Observable } from 'rxjs'; -import { CoreStart, KibanaRequest } from 'kibana/server'; -import { ISearchStrategy } from '../types'; -import { IKibanaSearchRequest, IKibanaSearchResponse } from '../../../common/search'; +import { + CoreStart, + KibanaRequest, + SavedObject, + SavedObjectsFindOptions, + SavedObjectsFindResponse, + SavedObjectsUpdateResponse, +} from 'kibana/server'; +import { IKibanaSearchRequest, ISearchOptions } from '../../../common/search'; -export interface IScopedSessionService { - search: ( - strategy: ISearchStrategy, - ...args: Parameters['search']> - ) => Observable; - [prop: string]: any; +export interface IScopedSearchSessionsClient { + getId: (request: IKibanaSearchRequest, options: ISearchOptions) => Promise; + trackId: ( + request: IKibanaSearchRequest, + searchId: string, + options: ISearchOptions + ) => Promise; + getSearchIdMapping: (sessionId: string) => Promise>; + save: (sessionId: string, attributes: Partial) => Promise | undefined>; + get: (sessionId: string) => Promise>; + find: (options: Omit) => Promise>; + update: (sessionId: string, attributes: Partial) => Promise>; + cancel: (sessionId: string) => Promise<{}>; + extend: (sessionId: string, expires: Date) => Promise>; } -export interface ISessionService { - asScopedProvider: (core: CoreStart) => (request: KibanaRequest) => IScopedSessionService; +export interface ISearchSessionService { + asScopedProvider: (core: CoreStart) => (request: KibanaRequest) => IScopedSearchSessionsClient; } diff --git a/src/plugins/data/server/search/types.ts b/src/plugins/data/server/search/types.ts index 7e7d22fdb2be1..ff3844c3d115e 100644 --- a/src/plugins/data/server/search/types.ts +++ b/src/plugins/data/server/search/types.ts @@ -25,17 +25,18 @@ import { import { AggsSetup, AggsStart } from './aggs'; import { SearchUsage } from './collectors'; import { IEsSearchRequest, IEsSearchResponse } from './es_search'; -import { ISessionService, IScopedSessionService } from './session'; +import { IScopedSearchSessionsClient, ISearchSessionService } from './session'; export interface SearchEnhancements { defaultStrategy: string; - sessionService: ISessionService; + sessionService: ISearchSessionService; } export interface SearchStrategyDependencies { savedObjectsClient: SavedObjectsClientContract; esClient: IScopedClusterClient; uiSettingsClient: IUiSettingsClient; + searchSessionsClient: IScopedSearchSessionsClient; } export interface ISearchSetup { @@ -85,6 +86,15 @@ export interface ISearchStrategy< ) => Promise; } +export interface IScopedSearchClient extends ISearchClient { + saveSession: IScopedSearchSessionsClient['save']; + getSession: IScopedSearchSessionsClient['get']; + findSessions: IScopedSearchSessionsClient['find']; + updateSession: IScopedSearchSessionsClient['update']; + cancelSession: IScopedSearchSessionsClient['cancel']; + extendSession: IScopedSearchSessionsClient['extend']; +} + export interface ISearchStart< SearchStrategyRequest extends IKibanaSearchRequest = IEsSearchRequest, SearchStrategyResponse extends IKibanaSearchResponse = IEsSearchResponse @@ -98,21 +108,19 @@ export interface ISearchStart< getSearchStrategy: ( name?: string // Name of the search strategy (defaults to the Elasticsearch strategy) ) => ISearchStrategy; - asScoped: (request: KibanaRequest) => ISearchClient; + asScoped: (request: KibanaRequest) => IScopedSearchClient; searchSource: { asScoped: (request: KibanaRequest) => Promise; }; } -export interface DataApiRequestHandlerContext extends ISearchClient { - session: IScopedSessionService; -} +export type SearchRequestHandlerContext = IScopedSearchClient; /** * @internal */ export interface DataRequestHandlerContext extends RequestHandlerContext { - search: DataApiRequestHandlerContext; + search: SearchRequestHandlerContext; } export type DataPluginRouter = IRouter; diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 9789f3354e9ef..beda5b3cea97e 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -55,9 +55,13 @@ import { RecursiveReadonly } from '@kbn/utility-types'; import { RequestAdapter } from 'src/plugins/inspector/common'; import { RequestHandlerContext } from 'src/core/server'; import { RequestStatistics } from 'src/plugins/inspector/common'; -import { SavedObject } from 'src/core/server'; +import { SavedObject } from 'kibana/server'; +import { SavedObject as SavedObject_2 } from 'src/core/server'; import { SavedObjectsClientContract } from 'src/core/server'; import { SavedObjectsClientContract as SavedObjectsClientContract_2 } from 'kibana/server'; +import { SavedObjectsFindOptions } from 'kibana/server'; +import { SavedObjectsFindResponse } from 'kibana/server'; +import { SavedObjectsUpdateResponse } from 'kibana/server'; import { Search } from '@elastic/elasticsearch/api/requestParams'; import { SearchResponse } from 'elasticsearch'; import { SerializedFieldFormat as SerializedFieldFormat_2 } from 'src/plugins/expressions/common'; @@ -305,21 +309,10 @@ export const castEsToKbnFieldTypeName: (esType: ES_FIELD_TYPES | string) => KBN_ // @public (undocumented) export const config: PluginConfigDescriptor; -// Warning: (ae-forgotten-export) The symbol "ISearchClient" needs to be exported by the entry point index.d.ts -// Warning: (ae-missing-release-tag) "DataApiRequestHandlerContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export interface DataApiRequestHandlerContext extends ISearchClient { - // Warning: (ae-forgotten-export) The symbol "IScopedSessionService" needs to be exported by the entry point index.d.ts - // - // (undocumented) - session: IScopedSessionService; -} - // @internal (undocumented) export interface DataRequestHandlerContext extends RequestHandlerContext { // (undocumented) - search: DataApiRequestHandlerContext; + search: SearchRequestHandlerContext; } // @public (undocumented) @@ -919,10 +912,21 @@ export interface ISearchOptions { abortSignal?: AbortSignal; isRestore?: boolean; isStored?: boolean; + legacyHitsTotal?: boolean; sessionId?: string; strategy?: string; } +// Warning: (ae-missing-release-tag) "ISearchSessionService" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export interface ISearchSessionService { + // Warning: (ae-forgotten-export) The symbol "IScopedSearchSessionsClient" needs to be exported by the entry point index.d.ts + // + // (undocumented) + asScopedProvider: (core: CoreStart) => (request: KibanaRequest) => IScopedSearchSessionsClient; +} + // Warning: (ae-missing-release-tag) "ISearchSetup" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -947,8 +951,10 @@ export interface ISearchStart ISearchClient; + asScoped: (request: KibanaRequest_2) => IScopedSearchClient; getSearchStrategy: (name?: string) => ISearchStrategy; // (undocumented) searchSource: { @@ -968,14 +974,6 @@ export interface ISearchStrategy Observable; } -// Warning: (ae-missing-release-tag) "ISessionService" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export interface ISessionService { - // (undocumented) - asScopedProvider: (core: CoreStart) => (request: KibanaRequest) => IScopedSessionService; -} - // @public (undocumented) export enum KBN_FIELD_TYPES { // (undocumented) @@ -1237,6 +1235,28 @@ export const search: { tabifyGetColumns: typeof tabifyGetColumns; }; +// Warning: (ae-missing-release-tag) "SearchRequestHandlerContext" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type SearchRequestHandlerContext = IScopedSearchClient; + +// @internal +export class SearchSessionService implements ISearchSessionService { + constructor(); + // (undocumented) + asScopedProvider(): () => { + getId: () => never; + trackId: () => Promise; + getSearchIdMapping: () => Promise>; + save: () => Promise; + get: () => Promise; + find: () => Promise; + update: () => Promise; + extend: () => Promise; + cancel: () => Promise; + }; +} + // Warning: (ae-missing-release-tag) "SearchStrategyDependencies" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1246,6 +1266,8 @@ export interface SearchStrategyDependencies { // (undocumented) savedObjectsClient: SavedObjectsClientContract; // (undocumented) + searchSessionsClient: IScopedSearchSessionsClient; + // (undocumented) uiSettingsClient: IUiSettingsClient; } @@ -1267,24 +1289,11 @@ export function searchUsageObserver(logger: Logger_2, usage?: SearchUsage): { error(): void; }; -// Warning: (ae-missing-release-tag) "SessionService" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public -export class SessionService implements ISessionService { - constructor(); - // (undocumented) - asScopedProvider(core: CoreStart): (request: KibanaRequest) => { - search: , Response_1 extends IKibanaSearchResponse>(strategy: ISearchStrategy, request: Request_1, options: import("../../../common").ISearchOptions, deps: import("../types").SearchStrategyDependencies) => import("rxjs").Observable; - }; - // (undocumented) - search(strategy: ISearchStrategy, ...args: Parameters['search']>): import("rxjs").Observable; -} - // @internal export const shimAbortSignal: (promise: TransportRequestPromise, signal?: AbortSignal | undefined) => TransportRequestPromise; // @internal -export function shimHitsTotal(response: SearchResponse): { +export function shimHitsTotal(response: SearchResponse, { legacyHitsTotal }?: ISearchOptions): { hits: { total: any; max_score: number; @@ -1293,7 +1302,7 @@ export function shimHitsTotal(response: SearchResponse): { _type: string; _id: string; _score: number; - _source: any; + _source: unknown; _version?: number | undefined; _explanation?: import("elasticsearch").Explanation | undefined; fields?: any; @@ -1315,27 +1324,6 @@ export function shimHitsTotal(response: SearchResponse): { // @public (undocumented) export function shouldReadFieldFromDocValues(aggregatable: boolean, esType: string): boolean; -// @public (undocumented) -export interface TabbedAggColumn { - // (undocumented) - aggConfig: IAggConfig; - // (undocumented) - id: string; - // (undocumented) - name: string; -} - -// @public (undocumented) -export type TabbedAggRow = Record; - -// @public (undocumented) -export interface TabbedTable { - // (undocumented) - columns: TabbedAggColumn[]; - // (undocumented) - rows: TabbedAggRow[]; -} - // Warning: (ae-missing-release-tag) "TimeRange" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -1426,23 +1414,23 @@ export function usageProvider(core: CoreSetup_2): SearchUsage; // src/plugins/data/server/index.ts:100:26 - (ae-forgotten-export) The symbol "TruncateFormat" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:126:27 - (ae-forgotten-export) The symbol "isFilterable" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index.ts:126:27 - (ae-forgotten-export) The symbol "isNestedField" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:243:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:243:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:243:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:243:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:245:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:246:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:255:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:256:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:257:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:261:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:262:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:266:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:269:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:270:1 - (ae-forgotten-export) The symbol "calcAutoIntervalLessThan" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:239:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:239:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:239:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:239:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:241:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:242:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:251:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:252:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:253:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:257:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:258:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:262:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:265:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:266:1 - (ae-forgotten-export) The symbol "calcAutoIntervalLessThan" needs to be exported by the entry point index.d.ts // src/plugins/data/server/index_patterns/index_patterns_service.ts:59:14 - (ae-forgotten-export) The symbol "IndexPatternsService" needs to be exported by the entry point index.d.ts // src/plugins/data/server/plugin.ts:79:74 - (ae-forgotten-export) The symbol "DataEnhancements" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/search/types.ts:103:5 - (ae-forgotten-export) The symbol "ISearchStartSearchSource" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/search/types.ts:113:5 - (ae-forgotten-export) The symbol "ISearchStartSearchSource" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/src/plugins/discover/public/application/angular/context_app.js b/src/plugins/discover/public/application/angular/context_app.js index 7386409110f64..26c72dae64c45 100644 --- a/src/plugins/discover/public/application/angular/context_app.js +++ b/src/plugins/discover/public/application/angular/context_app.js @@ -50,14 +50,15 @@ getAngularModule().directive('contextApp', function ContextApp() { function ContextAppController($scope, Private) { const { filterManager, indexPatterns, uiSettings, navigation } = getServices(); + const useNewFieldsApi = !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE); const queryParameterActions = getQueryParameterActions(filterManager, indexPatterns); const queryActions = Private(QueryActionsProvider); - const useNewFieldsApi = !uiSettings.get(SEARCH_FIELDS_FROM_SOURCE); this.state = createInitialState( parseInt(uiSettings.get(CONTEXT_STEP_SETTING), 10), getFirstSortableField(this.indexPattern, uiSettings.get(CONTEXT_TIE_BREAKER_FIELDS_SETTING)), useNewFieldsApi ); + this.state.useNewFieldsApi = useNewFieldsApi; this.topNavMenu = navigation.ui.TopNavMenu; this.actions = _.mapValues( diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index dcf86babaa5e1..507000113b611 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -414,18 +414,9 @@ function discoverController($route, $scope, Promise) { setBreadcrumbsTitle(savedSearch, chrome); - function removeSourceFromColumns(columns) { - return columns.filter((col) => col !== '_source'); - } - function getDefaultColumns() { - const columns = [...savedSearch.columns]; - - if ($scope.useNewFieldsApi) { - return removeSourceFromColumns(columns); - } - if (columns.length > 0) { - return columns; + if (savedSearch.columns.length > 0) { + return [...savedSearch.columns]; } return [...config.get(DEFAULT_COLUMNS_SETTING)]; } @@ -748,6 +739,21 @@ function discoverController($route, $scope, Promise) { history.push('/'); }; + const showUnmappedFieldsDefaultValue = $scope.useNewFieldsApi && !!$scope.opts.savedSearch.pre712; + let showUnmappedFields = showUnmappedFieldsDefaultValue; + + const onChangeUnmappedFields = (value) => { + showUnmappedFields = value; + $scope.unmappedFieldsConfig.showUnmappedFields = value; + $scope.fetch(); + }; + + $scope.unmappedFieldsConfig = { + showUnmappedFieldsDefaultValue, + showUnmappedFields, + onChangeUnmappedFields, + }; + $scope.updateDataSource = () => { const { indexPattern, searchSource, useNewFieldsApi } = $scope; const { columns, sort } = $scope.state; @@ -757,6 +763,7 @@ function discoverController($route, $scope, Promise) { sort, columns, useNewFieldsApi, + showUnmappedFields, }); return Promise.resolve(); }; diff --git a/src/plugins/discover/public/application/angular/discover_legacy.html b/src/plugins/discover/public/application/angular/discover_legacy.html index 76e5c568ffde6..83a9cf23c85f3 100644 --- a/src/plugins/discover/public/application/angular/discover_legacy.html +++ b/src/plugins/discover/public/application/angular/discover_legacy.html @@ -30,6 +30,7 @@ update-query="handleRefresh" update-saved-query-id="updateSavedQueryId" use-new-fields-api="useNewFieldsApi" + unmapped-fields-config="unmappedFieldsConfig" > diff --git a/src/plugins/discover/public/application/angular/doc_table/components/table_header/helpers.tsx b/src/plugins/discover/public/application/angular/doc_table/components/table_header/helpers.tsx index 3837495ec721f..d829dc97f4f7d 100644 --- a/src/plugins/discover/public/application/angular/doc_table/components/table_header/helpers.tsx +++ b/src/plugins/discover/public/application/angular/doc_table/components/table_header/helpers.tsx @@ -5,7 +5,6 @@ * compliance with, at your election, the Elastic License or the Server Side * Public License, v 1. */ - import { i18n } from '@kbn/i18n'; import { IndexPattern } from '../../../../../kibana_services'; diff --git a/src/plugins/discover/public/application/angular/doc_table/create_doc_table_react.tsx b/src/plugins/discover/public/application/angular/doc_table/create_doc_table_react.tsx index cbd93feb835a0..d8f826adbbb1c 100644 --- a/src/plugins/discover/public/application/angular/doc_table/create_doc_table_react.tsx +++ b/src/plugins/discover/public/application/angular/doc_table/create_doc_table_react.tsx @@ -14,11 +14,27 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { getServices, IIndexPattern } from '../../../kibana_services'; import { IndexPatternField } from '../../../../../data/common/index_patterns'; -export type AngularScope = IScope; - +export interface DocTableLegacyProps { + columns: string[]; + searchDescription?: string; + searchTitle?: string; + onFilter: (field: IndexPatternField | string, value: string, type: '+' | '-') => void; + rows: Array>; + indexPattern: IIndexPattern; + minimumVisibleRows: number; + onAddColumn?: (column: string) => void; + onBackToTop: () => void; + onSort?: (sort: string[][]) => void; + onMoveColumn?: (columns: string, newIdx: number) => void; + onRemoveColumn?: (column: string) => void; + sampleSize: number; + sort?: string[][]; + useNewFieldsApi?: boolean; +} export interface AngularDirective { template: string; } +export type AngularScope = IScope & { renderProps?: DocTableLegacyProps }; /** * Compiles and injects the give angular template into the given dom node @@ -27,13 +43,12 @@ export interface AngularDirective { export async function injectAngularElement( domNode: Element, template: string, - scopeProps: any, - getInjector: () => Promise -): Promise<() => void> { - const $injector = await getInjector(); - const rootScope: AngularScope = $injector.get('$rootScope'); - const $compile: ICompileService = $injector.get('$compile'); - const newScope = Object.assign(rootScope.$new(), scopeProps); + renderProps: any, + injector: auto.IInjectorService +) { + const rootScope: IScope = injector.get('$rootScope'); + const $compile: ICompileService = injector.get('$compile'); + const newScope = Object.assign(rootScope.$new(), { renderProps }); const $target = angular.element(domNode); const $element = angular.element(template); @@ -44,87 +59,63 @@ export async function injectAngularElement( linkFn(newScope); }); - return () => { - newScope.$destroy(); - }; + return newScope; } -/** - * Converts a given legacy angular directive to a render function - * for usage in a react component. Note that the rendering is async - */ -export function convertDirectiveToRenderFn( - directive: AngularDirective, - getInjector: () => Promise -) { - return (domNode: Element, props: any) => { - let rejected = false; - - const cleanupFnPromise = injectAngularElement(domNode, directive.template, props, getInjector); +function getRenderFn(domNode: Element, props: any) { + const directive = { + template: ``, + }; - cleanupFnPromise.catch(() => { - rejected = true; + return async () => { + try { + const injector = await getServices().getEmbeddableInjector(); + return await injectAngularElement(domNode, directive.template, props, injector); + } catch (e) { render(
error
, domNode); - }); - - return () => { - if (!rejected) { - // for cleanup - // http://roubenmeschian.com/rubo/?p=51 - cleanupFnPromise.then((cleanup) => cleanup()); - } - }; + } }; } -export interface DocTableLegacyProps { - columns: string[]; - searchDescription?: string; - searchTitle?: string; - onFilter: (field: IndexPatternField | string, value: string, type: '+' | '-') => void; - rows: Array>; - indexPattern: IIndexPattern; - minimumVisibleRows: number; - onAddColumn?: (column: string) => void; - onBackToTop: () => void; - onSort?: (sort: string[][]) => void; - onMoveColumn?: (columns: string, newIdx: number) => void; - onRemoveColumn?: (column: string) => void; - sampleSize: number; - sort?: string[][]; - useNewFieldsApi?: boolean; -} - export function DocTableLegacy(renderProps: DocTableLegacyProps) { - const renderFn = convertDirectiveToRenderFn( - { - template: ``, - }, - () => getServices().getEmbeddableInjector() - ); const ref = useRef(null); + const scope = useRef(); + useEffect(() => { - if (ref && ref.current) { - return renderFn(ref.current, renderProps); + if (ref && ref.current && !scope.current) { + const fn = getRenderFn(ref.current, renderProps); + fn().then((newScope) => { + scope.current = newScope; + }); + } else if (scope && scope.current) { + scope.current.renderProps = renderProps; + scope.current.$apply(); } - }, [renderFn, renderProps]); + }, [renderProps]); + useEffect(() => { + return () => { + if (scope.current) { + scope.current.$destroy(); + } + }; + }, []); return (
diff --git a/src/plugins/discover/public/application/angular/doc_table/doc_table.ts b/src/plugins/discover/public/application/angular/doc_table/doc_table.ts index 29ea9d5db9a34..6c2a3cbfc9512 100644 --- a/src/plugins/discover/public/application/angular/doc_table/doc_table.ts +++ b/src/plugins/discover/public/application/angular/doc_table/doc_table.ts @@ -59,6 +59,10 @@ export function createDocTableDirective(pagerFactory: any, $filter: any) { $scope.limit += 50; }; + $scope.$watch('minimumVisibleRows', (minimumVisibleRows: number) => { + $scope.limit = Math.max(minimumVisibleRows || 50, $scope.limit || 50); + }); + $scope.$watch('hits', (hits: any) => { if (!hits) return; diff --git a/src/plugins/discover/public/application/components/create_discover_directive.ts b/src/plugins/discover/public/application/components/create_discover_directive.ts index 10439488f4bc7..84f29780f1745 100644 --- a/src/plugins/discover/public/application/components/create_discover_directive.ts +++ b/src/plugins/discover/public/application/components/create_discover_directive.ts @@ -40,5 +40,6 @@ export function createDiscoverDirective(reactDirective: any) { ['topNavMenu', { watchDepth: 'reference' }], ['updateQuery', { watchDepth: 'reference' }], ['updateSavedQueryId', { watchDepth: 'reference' }], + ['unmappedFieldsConfig', { watchDepth: 'value' }], ]); } diff --git a/src/plugins/discover/public/application/components/discover.tsx b/src/plugins/discover/public/application/components/discover.tsx index 5653ef4f57435..6365cc0448b83 100644 --- a/src/plugins/discover/public/application/components/discover.tsx +++ b/src/plugins/discover/public/application/components/discover.tsx @@ -7,7 +7,7 @@ */ import './discover.scss'; -import React, { useState, useRef } from 'react'; +import React, { useState, useRef, useMemo } from 'react'; import { EuiButtonEmpty, EuiButtonIcon, @@ -80,6 +80,7 @@ export function Discover({ topNavMenu, updateQuery, updateSavedQueryId, + unmappedFieldsConfig, }: DiscoverProps) { const scrollableDesktop = useRef(null); const collapseIcon = useRef(null); @@ -102,6 +103,13 @@ export function Discover({ const contentCentered = resultState === 'uninitialized'; const isLegacy = services.uiSettings.get('doc_table:legacy'); const useNewFieldsApi = !services.uiSettings.get(SEARCH_FIELDS_FROM_SOURCE); + + const columns = useMemo(() => { + if (!state.columns) { + return []; + } + return useNewFieldsApi ? state.columns.filter((col) => col !== '_source') : state.columns; + }, [state, useNewFieldsApi]); return ( @@ -127,7 +135,7 @@ export function Discover({ @@ -277,7 +286,7 @@ export function Discover({ {isLegacy && rows && rows.length && ( { search: mockSearchApi, }, }, + docLinks: { + links: { + apis: { + indexExists: 'mockUrl', + }, + }, + }, }), getDocViewsRegistry: () => ({ addDocView(view: any) { diff --git a/src/plugins/discover/public/application/components/doc/doc.tsx b/src/plugins/discover/public/application/components/doc/doc.tsx index aad5b5e95ba36..9f78ae0e29ced 100644 --- a/src/plugins/discover/public/application/components/doc/doc.tsx +++ b/src/plugins/discover/public/application/components/doc/doc.tsx @@ -36,7 +36,7 @@ export interface DocProps { export function Doc(props: DocProps) { const [reqState, hit, indexPattern] = useEsDocSearch(props); - + const indexExistsLink = getServices().docLinks.links.apis.indexExists; return ( @@ -91,12 +91,7 @@ export function Doc(props: DocProps) { defaultMessage="{indexName} is missing." values={{ indexName: props.index }} />{' '} - + { popover = component.find(EuiPopover); expect(popover.prop('isOpen')).toBe(false); }); + + test('unmapped fields', () => { + const onChangeUnmappedFields = jest.fn(); + const componentProps = { + ...defaultProps, + showUnmappedFields: true, + useNewFieldsApi: false, + onChangeUnmappedFields, + }; + const component = mountComponent(componentProps); + const btn = findTestSubject(component, 'toggleFieldFilterButton'); + btn.simulate('click'); + const unmappedFieldsSwitch = findTestSubject(component, 'unmappedFieldsSwitch'); + act(() => { + unmappedFieldsSwitch.simulate('click'); + }); + expect(onChangeUnmappedFields).toHaveBeenCalledWith(false); + }); }); diff --git a/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx b/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx index 03a5198f1b1d9..22e08bf4abf67 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_field_search.tsx @@ -27,6 +27,8 @@ import { EuiOutsideClickDetector, EuiFilterButton, EuiSpacer, + EuiIcon, + EuiToolTip, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -35,6 +37,7 @@ export interface State { aggregatable: string; type: string; missing: boolean; + unmappedFields: boolean; [index: string]: string | boolean; } @@ -53,13 +56,36 @@ export interface Props { * types for the type filter */ types: string[]; + + /** + * use new fields api + */ + useNewFieldsApi?: boolean; + + /** + * callback funtion to change the value of unmapped fields switch + * @param value new value to set + */ + onChangeUnmappedFields?: (value: boolean) => void; + + /** + * should unmapped fields switch be rendered + */ + showUnmappedFields?: boolean; } /** * Component is Discover's side bar to search of available fields * Additionally there's a button displayed that allows the user to show/hide more filter fields */ -export function DiscoverFieldSearch({ onChange, value, types }: Props) { +export function DiscoverFieldSearch({ + onChange, + value, + types, + useNewFieldsApi, + showUnmappedFields, + onChangeUnmappedFields, +}: Props) { const searchPlaceholder = i18n.translate('discover.fieldChooser.searchPlaceHolder', { defaultMessage: 'Search field names', }); @@ -85,6 +111,7 @@ export function DiscoverFieldSearch({ onChange, value, types }: Props) { aggregatable: 'any', type: 'any', missing: true, + unmappedFields: !!showUnmappedFields, }); if (typeof value !== 'string') { @@ -154,6 +181,14 @@ export function DiscoverFieldSearch({ onChange, value, types }: Props) { handleValueChange('missing', missingValue); }; + const handleUnmappedFieldsChange = (e: EuiSwitchEvent) => { + const unmappedFieldsValue = e.target.checked; + handleValueChange('unmappedFields', unmappedFieldsValue); + if (onChangeUnmappedFields) { + onChangeUnmappedFields(unmappedFieldsValue); + } + }; + const buttonContent = ( { + if (!showUnmappedFields && useNewFieldsApi) { + return null; + } + return ( + + {showUnmappedFields ? ( + + + + + + + + + + + ) : null} + {useNewFieldsApi ? null : ( + + )} + + ); + }; + const selectionPanel = (
@@ -277,16 +357,7 @@ export function DiscoverFieldSearch({ onChange, value, types }: Props) { })} {selectionPanel} - - - + {footer()} diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx index d03d92892f56a..d6d29dbd88a67 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar.tsx @@ -104,6 +104,27 @@ export interface DiscoverSidebarProps { * Shows index pattern and a button that displays the sidebar in a flyout */ useFlyout?: boolean; + + /** + * an object containing properties for proper handling of unmapped fields in the UI + */ + unmappedFieldsConfig?: { + /** + * callback function to change the value of `showUnmappedFields` flag + * @param value new value to set + */ + onChangeUnmappedFields: (value: boolean) => void; + /** + * determines whether to display unmapped fields + * configurable through the switch in the UI + */ + showUnmappedFields: boolean; + /** + * determines if we should display an option to toggle showUnmappedFields value in the first place + * this value is not configurable through the UI + */ + showUnmappedFieldsDefaultValue: boolean; + }; } export function DiscoverSidebar({ @@ -123,6 +144,7 @@ export function DiscoverSidebar({ trackUiMetric, useNewFieldsApi = false, useFlyout = false, + unmappedFieldsConfig, }: DiscoverSidebarProps) { const [fields, setFields] = useState(null); @@ -145,14 +167,30 @@ export function DiscoverSidebar({ ); const popularLimit = services.uiSettings.get(FIELDS_LIMIT_SETTING); - const { selected: selectedFields, popular: popularFields, unpopular: unpopularFields, } = useMemo( - () => groupFields(fields, columns, popularLimit, fieldCounts, fieldFilter, useNewFieldsApi), - [fields, columns, popularLimit, fieldCounts, fieldFilter, useNewFieldsApi] + () => + groupFields( + fields, + columns, + popularLimit, + fieldCounts, + fieldFilter, + useNewFieldsApi, + !!unmappedFieldsConfig?.showUnmappedFields + ), + [ + fields, + columns, + popularLimit, + fieldCounts, + fieldFilter, + useNewFieldsApi, + unmappedFieldsConfig?.showUnmappedFields, + ] ); const fieldTypes = useMemo(() => { @@ -239,6 +277,9 @@ export function DiscoverSidebar({ onChange={onChangeFieldSearch} value={fieldFilter.name} types={fieldTypes} + useNewFieldsApi={useNewFieldsApi} + onChangeUnmappedFields={unmappedFieldsConfig?.onChangeUnmappedFields} + showUnmappedFields={unmappedFieldsConfig?.showUnmappedFieldsDefaultValue} /> diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.test.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.test.tsx index 919476ffb458e..aaf992b03b119 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.test.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.test.tsx @@ -15,7 +15,7 @@ import realHits from 'fixtures/real_hits.js'; import stubbedLogstashFields from 'fixtures/logstash_fields'; import { mountWithIntl } from '@kbn/test/jest'; import React from 'react'; -import { DiscoverSidebarProps } from './discover_sidebar'; +import { DiscoverSidebar, DiscoverSidebarProps } from './discover_sidebar'; import { coreMock } from '../../../../../../core/public/mocks'; import { IndexPatternAttributes } from '../../../../../data/common'; import { getStubIndexPattern } from '../../../../../data/public/test_utils'; @@ -131,4 +131,16 @@ describe('discover responsive sidebar', function () { findTestSubject(comp, 'plus-extension-gif').simulate('click'); expect(props.onAddFilter).toHaveBeenCalled(); }); + it('renders sidebar with unmapped fields config', function () { + const unmappedFieldsConfig = { + onChangeUnmappedFields: jest.fn(), + showUnmappedFields: false, + showUnmappedFieldsDefaultValue: false, + }; + const componentProps = { ...props, unmappedFieldsConfig }; + const component = mountWithIntl(); + const discoverSidebar = component.find(DiscoverSidebar); + expect(discoverSidebar).toHaveLength(1); + expect(discoverSidebar.props().unmappedFieldsConfig).toEqual(unmappedFieldsConfig); + }); }); diff --git a/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.tsx b/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.tsx index e2bb641d5d578..600343db4e256 100644 --- a/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.tsx +++ b/src/plugins/discover/public/application/components/sidebar/discover_sidebar_responsive.tsx @@ -97,6 +97,27 @@ export interface DiscoverSidebarResponsiveProps { * Read from the Fields API */ useNewFieldsApi?: boolean; + + /** + * an object containing properties for proper handling of unmapped fields in the UI + */ + unmappedFieldsConfig?: { + /** + * callback function to change the value of `showUnmappedFields` flag + * @param value new value to set + */ + onChangeUnmappedFields: (value: boolean) => void; + /** + * determines whether to display unmapped fields + * configurable through the switch in the UI + */ + showUnmappedFields: boolean; + /** + * determines if we should display an option to toggle showUnmappedFields value in the first place + * this value is not configurable through the UI + */ + showUnmappedFieldsDefaultValue: boolean; + }; } /** diff --git a/src/plugins/discover/public/application/components/sidebar/lib/group_fields.test.ts b/src/plugins/discover/public/application/components/sidebar/lib/group_fields.test.ts index 923b0636734ea..22979f2b7eef1 100644 --- a/src/plugins/discover/public/application/components/sidebar/lib/group_fields.test.ts +++ b/src/plugins/discover/public/application/components/sidebar/lib/group_fields.test.ts @@ -178,6 +178,7 @@ describe('group_fields', function () { fieldFilterState, true ); + expect(actual.popular).toEqual([category]); expect(actual.selected).toEqual([currency]); expect(actual.unpopular).toEqual([]); @@ -214,4 +215,30 @@ describe('group_fields', function () { 'unknown', ]); }); + + it('excludes unmapped fields if showUnmappedFields set to false', function () { + const fieldFilterState = getDefaultFieldFilter(); + const fieldsWithUnmappedField = [...fields]; + fieldsWithUnmappedField.push({ + name: 'unknown_field', + type: 'unknown', + esTypes: ['unknown'], + count: 1, + scripted: false, + searchable: true, + aggregatable: true, + readFromDocValues: true, + }); + + const actual = groupFields( + fieldsWithUnmappedField as IndexPatternField[], + ['customer_birth_date', 'currency', 'unknown'], + 5, + fieldCounts, + fieldFilterState, + true, + false + ); + expect(actual.unpopular).toEqual([]); + }); }); diff --git a/src/plugins/discover/public/application/components/sidebar/lib/group_fields.tsx b/src/plugins/discover/public/application/components/sidebar/lib/group_fields.tsx index ba7000b4fc6df..0e9a015a230ed 100644 --- a/src/plugins/discover/public/application/components/sidebar/lib/group_fields.tsx +++ b/src/plugins/discover/public/application/components/sidebar/lib/group_fields.tsx @@ -24,7 +24,8 @@ export function groupFields( popularLimit: number, fieldCounts: Record, fieldFilterState: FieldFilterState, - useNewFieldsApi: boolean + useNewFieldsApi: boolean, + showUnmappedFields = true ): GroupedFields { const result: GroupedFields = { selected: [], @@ -61,7 +62,9 @@ export function groupFields( result.popular.push(field); } } else if (field.type !== '_source') { - if (!isSubfield) { + // do not show unmapped fields unless explicitly specified + // do not add subfields to this list + if ((field.type !== 'unknown' || showUnmappedFields) && !isSubfield) { result.unpopular.push(field); } } diff --git a/src/plugins/discover/public/application/components/types.ts b/src/plugins/discover/public/application/components/types.ts index fe0d40f222f23..256a6dc8f6bb7 100644 --- a/src/plugins/discover/public/application/components/types.ts +++ b/src/plugins/discover/public/application/components/types.ts @@ -176,4 +176,24 @@ export interface DiscoverProps { * Function to update the actual savedQuery id */ updateSavedQueryId: (savedQueryId?: string) => void; + /** + * An object containing properties for proper handling of unmapped fields in the UI + */ + unmappedFieldsConfig?: { + /** + * determines whether to display unmapped fields + * configurable through the switch in the UI + */ + showUnmappedFields: boolean; + /** + * determines if we should display an option to toggle showUnmappedFields value in the first place + * this value is not configurable through the UI + */ + showUnmappedFieldsDefaultValue: boolean; + /** + * callback function to change the value of `showUnmappedFields` flag + * @param value new value to set + */ + onChangeUnmappedFields: (value: boolean) => void; + }; } diff --git a/src/plugins/discover/public/application/embeddable/search_embeddable.ts b/src/plugins/discover/public/application/embeddable/search_embeddable.ts index f9976d1a04087..cfb8882c83e2a 100644 --- a/src/plugins/discover/public/application/embeddable/search_embeddable.ts +++ b/src/plugins/discover/public/application/embeddable/search_embeddable.ts @@ -38,7 +38,11 @@ import { } from '../../kibana_services'; import { SEARCH_EMBEDDABLE_TYPE } from './constants'; import { SavedSearch } from '../..'; -import { SAMPLE_SIZE_SETTING, SORT_DEFAULT_ORDER_SETTING } from '../../../common'; +import { + SAMPLE_SIZE_SETTING, + SEARCH_FIELDS_FROM_SOURCE, + SORT_DEFAULT_ORDER_SETTING, +} from '../../../common'; import { DiscoverGridSettings } from '../components/discover_grid/types'; import { DiscoverServices } from '../../build_services'; import { ElasticSearchHit } from '../doc_views/doc_views_types'; @@ -62,6 +66,7 @@ interface SearchScope extends ng.IScope { totalHitCount?: number; isLoading?: boolean; showTimeCol?: boolean; + useNewFieldsApi?: boolean; } interface SearchEmbeddableConfig { @@ -220,11 +225,14 @@ export class SearchEmbeddable this.updateInput({ sort }); }; + const useNewFieldsApi = !getServices().uiSettings.get(SEARCH_FIELDS_FROM_SOURCE, false); + searchScope.useNewFieldsApi = useNewFieldsApi; + searchScope.addColumn = (columnName: string) => { if (!searchScope.columns) { return; } - const columns = columnActions.addColumn(searchScope.columns, columnName); + const columns = columnActions.addColumn(searchScope.columns, columnName, useNewFieldsApi); this.updateInput({ columns }); }; @@ -232,7 +240,7 @@ export class SearchEmbeddable if (!searchScope.columns) { return; } - const columns = columnActions.removeColumn(searchScope.columns, columnName); + const columns = columnActions.removeColumn(searchScope.columns, columnName, useNewFieldsApi); this.updateInput({ columns }); }; @@ -280,10 +288,10 @@ export class SearchEmbeddable private fetch = async () => { const searchSessionId = this.input.searchSessionId; - + const useNewFieldsApi = !this.services.uiSettings.get(SEARCH_FIELDS_FROM_SOURCE, false); if (!this.searchScope) return; - const { searchSource } = this.savedSearch; + const { searchSource, pre712 } = this.savedSearch; // Abort any in-progress requests if (this.abortController) this.abortController.abort(); @@ -298,6 +306,20 @@ export class SearchEmbeddable this.services.uiSettings.get(SORT_DEFAULT_ORDER_SETTING) ) ); + if (useNewFieldsApi) { + searchSource.removeField('fieldsFromSource'); + const fields: Record = { field: '*' }; + if (pre712) { + fields.include_unmapped = true; + } + searchSource.setField('fields', [fields]); + } else { + searchSource.removeField('fields'); + if (this.searchScope.indexPattern) { + const fieldNames = this.searchScope.indexPattern.fields.map((field) => field.name); + searchSource.setField('fieldsFromSource', fieldNames); + } + } // Log request to inspector this.inspectorAdapters.requests!.reset(); diff --git a/src/plugins/discover/public/application/embeddable/search_template.html b/src/plugins/discover/public/application/embeddable/search_template.html index be2f5cceac080..3e37b3645650f 100644 --- a/src/plugins/discover/public/application/embeddable/search_template.html +++ b/src/plugins/discover/public/application/embeddable/search_template.html @@ -16,5 +16,6 @@ render-complete sorting="sort" total-hit-count="totalHitCount" + use-new-fields-api="useNewFieldsApi" > diff --git a/src/plugins/discover/public/application/helpers/update_search_source.test.ts b/src/plugins/discover/public/application/helpers/update_search_source.test.ts index 5e27ec94154a8..14b826524706b 100644 --- a/src/plugins/discover/public/application/helpers/update_search_source.test.ts +++ b/src/plugins/discover/public/application/helpers/update_search_source.test.ts @@ -63,7 +63,61 @@ describe('updateSearchSource', () => { }); expect(result.getField('index')).toEqual(indexPatternMock); expect(result.getField('size')).toEqual(sampleSize); - expect(result.getField('fields')).toEqual(['*']); + expect(result.getField('fields')).toEqual([{ field: '*' }]); + expect(result.getField('fieldsFromSource')).toBe(undefined); + }); + + test('requests unmapped fields when the flag is provided, using the new fields api', async () => { + const searchSourceMock = createSearchSourceMock({}); + const sampleSize = 250; + const result = updateSearchSource(searchSourceMock, { + indexPattern: indexPatternMock, + services: ({ + data: dataPluginMock.createStartContract(), + uiSettings: ({ + get: (key: string) => { + if (key === SAMPLE_SIZE_SETTING) { + return sampleSize; + } + return false; + }, + } as unknown) as IUiSettingsClient, + } as unknown) as DiscoverServices, + sort: [] as SortOrder[], + columns: [], + useNewFieldsApi: true, + showUnmappedFields: true, + }); + expect(result.getField('index')).toEqual(indexPatternMock); + expect(result.getField('size')).toEqual(sampleSize); + expect(result.getField('fields')).toEqual([{ field: '*', include_unmapped: 'true' }]); + expect(result.getField('fieldsFromSource')).toBe(undefined); + }); + + test('updates a given search source when showUnmappedFields option is set to true', async () => { + const searchSourceMock = createSearchSourceMock({}); + const sampleSize = 250; + const result = updateSearchSource(searchSourceMock, { + indexPattern: indexPatternMock, + services: ({ + data: dataPluginMock.createStartContract(), + uiSettings: ({ + get: (key: string) => { + if (key === SAMPLE_SIZE_SETTING) { + return sampleSize; + } + return false; + }, + } as unknown) as IUiSettingsClient, + } as unknown) as DiscoverServices, + sort: [] as SortOrder[], + columns: [], + useNewFieldsApi: true, + showUnmappedFields: true, + }); + expect(result.getField('index')).toEqual(indexPatternMock); + expect(result.getField('size')).toEqual(sampleSize); + expect(result.getField('fields')).toEqual([{ field: '*', include_unmapped: 'true' }]); expect(result.getField('fieldsFromSource')).toBe(undefined); }); }); diff --git a/src/plugins/discover/public/application/helpers/update_search_source.ts b/src/plugins/discover/public/application/helpers/update_search_source.ts index b4794b1b26311..aa46e4641bf51 100644 --- a/src/plugins/discover/public/application/helpers/update_search_source.ts +++ b/src/plugins/discover/public/application/helpers/update_search_source.ts @@ -23,12 +23,14 @@ export function updateSearchSource( sort, columns, useNewFieldsApi, + showUnmappedFields, }: { indexPattern: IndexPattern; services: DiscoverServices; sort: SortOrder[]; columns: string[]; useNewFieldsApi: boolean; + showUnmappedFields?: boolean; } ) { const { uiSettings, data } = services; @@ -46,7 +48,11 @@ export function updateSearchSource( .setField('filter', data.query.filterManager.getFilters()); if (useNewFieldsApi) { searchSource.removeField('fieldsFromSource'); - searchSource.setField('fields', ['*']); + const fields: Record = { field: '*' }; + if (showUnmappedFields) { + fields.include_unmapped = 'true'; + } + searchSource.setField('fields', [fields]); } else { searchSource.removeField('fields'); const fieldNames = indexPattern.fields.map((field) => field.name); diff --git a/src/plugins/discover/public/saved_searches/_saved_search.ts b/src/plugins/discover/public/saved_searches/_saved_search.ts index fb4ca519ad71c..95213b5119fea 100644 --- a/src/plugins/discover/public/saved_searches/_saved_search.ts +++ b/src/plugins/discover/public/saved_searches/_saved_search.ts @@ -19,6 +19,7 @@ export function createSavedSearchClass(savedObjects: SavedObjectsStart) { grid: 'object', sort: 'keyword', version: 'integer', + pre712: 'boolean', }; // Order these fields to the top, the rest are alphabetical public static fieldOrder = ['title', 'description']; @@ -39,6 +40,7 @@ export function createSavedSearchClass(savedObjects: SavedObjectsStart) { grid: 'object', sort: 'keyword', version: 'integer', + pre712: 'boolean', }, searchSource: true, defaults: { @@ -48,6 +50,7 @@ export function createSavedSearchClass(savedObjects: SavedObjectsStart) { hits: 0, sort: [], version: 1, + pre712: false, }, }); this.showInRecentlyAccessed = true; diff --git a/src/plugins/discover/public/saved_searches/types.ts b/src/plugins/discover/public/saved_searches/types.ts index b273f15e8d68f..cbebc9da20f77 100644 --- a/src/plugins/discover/public/saved_searches/types.ts +++ b/src/plugins/discover/public/saved_searches/types.ts @@ -23,6 +23,7 @@ export interface SavedSearch { save: (saveOptions: SavedObjectSaveOpts) => Promise; lastSavedTitle?: string; copyOnSave?: boolean; + pre712?: boolean; } export interface SavedSearchLoader { get: (id: string) => Promise; diff --git a/src/plugins/discover/server/saved_objects/search.ts b/src/plugins/discover/server/saved_objects/search.ts index 967be8806e0ab..4faf8e404c1ce 100644 --- a/src/plugins/discover/server/saved_objects/search.ts +++ b/src/plugins/discover/server/saved_objects/search.ts @@ -44,6 +44,7 @@ export const searchSavedObjectType: SavedObjectsType = { title: { type: 'text' }, grid: { type: 'object', enabled: false }, version: { type: 'integer' }, + pre712: { type: 'boolean' }, }, }, migrations: searchMigrations as any, diff --git a/src/plugins/discover/server/saved_objects/search_migrations.test.ts b/src/plugins/discover/server/saved_objects/search_migrations.test.ts index 68177b0aa4ddb..3e6cc4ad71315 100644 --- a/src/plugins/discover/server/saved_objects/search_migrations.test.ts +++ b/src/plugins/discover/server/saved_objects/search_migrations.test.ts @@ -350,4 +350,41 @@ Object { testMigrateMatchAllQuery(migrationFn); }); }); + + describe('7.12.0', () => { + const migrationFn = searchMigrations['7.12.0']; + + describe('migrateExistingSavedSearch', () => { + it('should add a new flag to existing saved searches', () => { + const migratedDoc = migrationFn( + { + type: 'search', + attributes: { + kibanaSavedObjectMeta: {}, + }, + }, + savedObjectMigrationContext + ); + const migratedPre712Flag = migratedDoc.attributes.pre712; + + expect(migratedPre712Flag).toEqual(true); + }); + + it('should not modify a flag if it already exists', () => { + const migratedDoc = migrationFn( + { + type: 'search', + attributes: { + kibanaSavedObjectMeta: {}, + pre712: false, + }, + }, + savedObjectMigrationContext + ); + const migratedPre712Flag = migratedDoc.attributes.pre712; + + expect(migratedPre712Flag).toEqual(false); + }); + }); + }); }); diff --git a/src/plugins/discover/server/saved_objects/search_migrations.ts b/src/plugins/discover/server/saved_objects/search_migrations.ts index 151ac83f7c327..fd6f8756963c8 100644 --- a/src/plugins/discover/server/saved_objects/search_migrations.ts +++ b/src/plugins/discover/server/saved_objects/search_migrations.ts @@ -117,9 +117,28 @@ const migrateSearchSortToNestedArray: SavedObjectMigrationFn = (doc) = }; }; +const migrateExistingSavedSearch: SavedObjectMigrationFn = (doc) => { + if (!doc.attributes) { + return doc; + } + const pre712 = doc.attributes.pre712; + // pre712 already has a value + if (pre712 !== undefined) { + return doc; + } + return { + ...doc, + attributes: { + ...doc.attributes, + pre712: true, + }, + }; +}; + export const searchMigrations = { '6.7.2': flow(migrateMatchAllQuery), '7.0.0': flow(setNewReferences), '7.4.0': flow(migrateSearchSortToNestedArray), '7.9.3': flow(migrateMatchAllQuery), + '7.12.0': flow(migrateExistingSavedSearch), }; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx index 36841f8b1d521..4945b7a059e8c 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.test.tsx @@ -287,4 +287,55 @@ describe('', () => { expect(formHook?.getFormData()).toEqual({ name: 'myName' }); }); }); + + describe('change handlers', () => { + const onError = jest.fn(); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + const getTestComp = (fieldConfig: FieldConfig) => { + const TestComp = () => { + const { form } = useForm(); + + return ( +
+ + + ); + }; + return TestComp; + }; + + const setup = (fieldConfig: FieldConfig) => { + return registerTestBed(getTestComp(fieldConfig), { + memoryRouter: { wrapComponent: false }, + })() as TestBed; + }; + + it('calls onError when validation state changes', async () => { + const { + form: { setInputValue }, + } = setup({ + validations: [ + { + validator: ({ value }) => (value === '1' ? undefined : { message: 'oops!' }), + }, + ], + }); + + expect(onError).toBeCalledTimes(0); + await act(async () => { + setInputValue('myField', '0'); + }); + expect(onError).toBeCalledTimes(1); + expect(onError).toBeCalledWith(['oops!']); + await act(async () => { + setInputValue('myField', '1'); + }); + expect(onError).toBeCalledTimes(2); + expect(onError).toBeCalledWith(null); + }); + }); }); diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx index 94c2bc42d2855..cc79ed24b5d0c 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/components/use_field.tsx @@ -20,6 +20,7 @@ export interface Props { componentProps?: Record; readDefaultValueOnForm?: boolean; onChange?: (value: I) => void; + onError?: (errors: string[] | null) => void; children?: (field: FieldHook) => JSX.Element | null; [key: string]: any; } @@ -33,6 +34,7 @@ function UseFieldComp(props: Props(props: Props(form, path, fieldConfig, onChange); + const field = useField(form, path, fieldConfig, onChange, onError); // Children prevails over anything else provided. if (children) { diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts index c396f223e97fd..db7b0b2820a47 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/hooks/use_field.ts @@ -27,7 +27,8 @@ export const useField = ( form: FormHook, path: string, config: FieldConfig & InternalFieldConfig = {}, - valueChangeListener?: (value: I) => void + valueChangeListener?: (value: I) => void, + errorChangeListener?: (errors: string[] | null) => void ) => { const { type = FIELD_TYPES.TEXT, @@ -596,6 +597,15 @@ export const useField = ( }; }, [onValueChange]); + useEffect(() => { + if (!isMounted.current) { + return; + } + if (errorChangeListener) { + errorChangeListener(errors.length ? errors.map((error) => error.message) : null); + } + }, [errors, errorChangeListener]); + useEffect(() => { isMounted.current = true; diff --git a/src/plugins/expressions/public/render.ts b/src/plugins/expressions/public/render.ts index 69158acfffc73..9aebb0e07b094 100644 --- a/src/plugins/expressions/public/render.ts +++ b/src/plugins/expressions/public/render.ts @@ -83,7 +83,7 @@ export class ExpressionRenderHandler { reload: () => { this.updateSubject.next(null); }, - update: (params) => { + update: (params: UpdateValue) => { this.updateSubject.next(params); }, event: (data) => { diff --git a/src/plugins/kibana_overview/tsconfig.json b/src/plugins/kibana_overview/tsconfig.json new file mode 100644 index 0000000000000..ac3ac109cb35f --- /dev/null +++ b/src/plugins/kibana_overview/tsconfig.json @@ -0,0 +1,23 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "public/**/*", + "common/**/*", + ], + "references": [ + { "path": "../../core/tsconfig.json" }, + { "path": "../../plugins/navigation/tsconfig.json" }, + { "path": "../../plugins/data/tsconfig.json" }, + { "path": "../../plugins/home/tsconfig.json" }, + { "path": "../../plugins/newsfeed/tsconfig.json" }, + { "path": "../../plugins/usage_collection/tsconfig.json" }, + { "path": "../../plugins/kibana_react/tsconfig.json" }, + ] +} 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 e2061487bfcee..8bc6c130d318f 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 @@ -518,6 +518,7 @@ class TableListView extends React.Component + {this.props.children} {this.renderListingLimitWarning()} {this.renderFetchError()} diff --git a/src/plugins/kibana_utils/public/state_sync/public.api.md b/src/plugins/kibana_utils/public/state_sync/public.api.md index 5524563c034a8..a2e2c802e0466 100644 --- a/src/plugins/kibana_utils/public/state_sync/public.api.md +++ b/src/plugins/kibana_utils/public/state_sync/public.api.md @@ -22,6 +22,7 @@ export const createSessionStorageStateStorage: (storage?: Storage) => ISessionSt // @public export interface IKbnUrlStateStorage extends IStateStorage { + cancel: () => void; // (undocumented) change$: (key: string) => Observable; // (undocumented) 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 index 890de8f6ed6a1..982d25425687f 100644 --- a/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts +++ b/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts @@ -290,7 +290,7 @@ describe('state_sync', () => { expect(history.length).toBe(startHistoryLength); expect(getCurrentUrl()).toMatchInlineSnapshot(`"/"`); - urlSyncStrategy.kbnUrlControls.cancel(); + urlSyncStrategy.cancel(); expect(history.length).toBe(startHistoryLength); expect(getCurrentUrl()).toMatchInlineSnapshot(`"/"`); @@ -303,6 +303,32 @@ describe('state_sync', () => { stop(); }); + it('cancels pending URL updates when sync stops', 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(`"/"`); + + stop(); + + await tick(); + + expect(history.length).toBe(startHistoryLength); + expect(getCurrentUrl()).toMatchInlineSnapshot(`"/"`); + }); + it("should preserve reference to unchanged state slices if them didn't change", async () => { const otherUnchangedSlice = { a: 'test' }; const oldState = { 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 index 037c6f9fc666d..58840331a3134 100644 --- 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 @@ -51,7 +51,7 @@ describe('KbnUrlStateStorage', () => { const key = '_s'; const pr = urlStateStorage.set(key, state); expect(getCurrentUrl()).toMatchInlineSnapshot(`"/"`); - urlStateStorage.kbnUrlControls.cancel(); + urlStateStorage.cancel(); await pr; expect(getCurrentUrl()).toMatchInlineSnapshot(`"/"`); expect(urlStateStorage.get(key)).toEqual(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 index 0935ecd20111f..591605c22db24 100644 --- 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 @@ -39,6 +39,11 @@ export interface IKbnUrlStateStorage extends IStateStorage { get: (key: string) => State | null; change$: (key: string) => Observable; + /** + * Cancels any pending url updates + */ + cancel: () => void; + /** * Lower level wrapper around history library that handles batching multiple URL updates into one history change */ @@ -108,6 +113,9 @@ export const createKbnUrlStateStorage = ( }), share() ), + cancel() { + url.cancel(); + }, kbnUrlControls: url, }; }; diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 950fdf9405b75..14cd7141ac9e2 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -4097,6 +4097,9 @@ "xpackDashboardMode:roles": { "type": "keyword" }, + "securitySolution:ipReputationLinks": { + "type": "keyword" + }, "visualize:enableLabs": { "type": "boolean" }, @@ -4115,9 +4118,6 @@ "visualization:tileMap:maxPrecision": { "type": "long" }, - "securitySolution:ipReputationLinks": { - "type": "keyword" - }, "csv:separator": { "type": "keyword" }, diff --git a/src/plugins/vis_type_table/public/components/table_vis_basic.tsx b/src/plugins/vis_type_table/public/components/table_vis_basic.tsx index 6cea4d09c4e7f..8bb5186159b7d 100644 --- a/src/plugins/vis_type_table/public/components/table_vis_basic.tsx +++ b/src/plugins/vis_type_table/public/components/table_vis_basic.tsx @@ -13,15 +13,14 @@ import { orderBy } from 'lodash'; import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; import { createTableVisCell } from './table_vis_cell'; -import { Table } from '../table_vis_response_handler'; -import { TableVisConfig, TableVisUseUiStateProps } from '../types'; -import { useFormattedColumnsAndRows, usePagination } from '../utils'; +import { TableContext, TableVisConfig, TableVisUseUiStateProps } from '../types'; +import { usePagination } from '../utils'; import { TableVisControls } from './table_vis_controls'; import { createGridColumns } from './table_vis_columns'; interface TableVisBasicProps { fireEvent: IInterpreterRenderHandlers['event']; - table: Table; + table: TableContext; visConfig: TableVisConfig; title?: string; uiStateProps: TableVisUseUiStateProps; @@ -35,7 +34,7 @@ export const TableVisBasic = memo( title, uiStateProps: { columnsWidth, sort, setColumnsWidth, setSort }, }: TableVisBasicProps) => { - const { columns, rows } = useFormattedColumnsAndRows(table, visConfig); + const { columns, rows, formattedColumns } = table; // custom sorting is in place until the EuiDataGrid sorting gets rid of flaws -> https://github.com/elastic/eui/issues/4108 const sortedRows = useMemo( @@ -47,13 +46,19 @@ export const TableVisBasic = memo( ); // renderCellValue is a component which renders a cell based on column and row indexes - const renderCellValue = useMemo(() => createTableVisCell(columns, sortedRows), [ - columns, + const renderCellValue = useMemo(() => createTableVisCell(sortedRows, formattedColumns), [ + formattedColumns, sortedRows, ]); // Columns config - const gridColumns = createGridColumns(table, columns, columnsWidth, sortedRows, fireEvent); + const gridColumns = createGridColumns( + columns, + sortedRows, + formattedColumns, + columnsWidth, + fireEvent + ); // Pagination config const pagination = usePagination(visConfig, rows.length); @@ -126,10 +131,9 @@ export const TableVisBasic = memo( additionalControls: ( ), @@ -138,8 +142,7 @@ export const TableVisBasic = memo( renderCellValue={renderCellValue} renderFooterCellValue={ visConfig.showTotal - ? // @ts-expect-error - ({ colIndex }) => columns[colIndex].formattedTotal || null + ? ({ columnId }) => formattedColumns[columnId].formattedTotal || null : undefined } pagination={pagination} diff --git a/src/plugins/vis_type_table/public/components/table_vis_cell.tsx b/src/plugins/vis_type_table/public/components/table_vis_cell.tsx index 0a6aafc84bf26..04df3907c8c9b 100644 --- a/src/plugins/vis_type_table/public/components/table_vis_cell.tsx +++ b/src/plugins/vis_type_table/public/components/table_vis_cell.tsx @@ -9,17 +9,15 @@ import React from 'react'; import { EuiDataGridCellValueElementProps } from '@elastic/eui'; -import { Table } from '../table_vis_response_handler'; -import { FormattedColumn } from '../types'; +import { DatatableRow } from 'src/plugins/expressions'; +import { FormattedColumns } from '../types'; -export const createTableVisCell = (formattedColumns: FormattedColumn[], rows: Table['rows']) => ({ - // @ts-expect-error - colIndex, +export const createTableVisCell = (rows: DatatableRow[], formattedColumns: FormattedColumns) => ({ rowIndex, columnId, }: EuiDataGridCellValueElementProps) => { const rowValue = rows[rowIndex][columnId]; - const column = formattedColumns[colIndex]; + const column = formattedColumns[columnId]; const content = column.formatter.convert(rowValue, 'html'); const cellContent = ( diff --git a/src/plugins/vis_type_table/public/components/table_vis_columns.tsx b/src/plugins/vis_type_table/public/components/table_vis_columns.tsx index 2610677b2491c..6b44a2504ff89 100644 --- a/src/plugins/vis_type_table/public/components/table_vis_columns.tsx +++ b/src/plugins/vis_type_table/public/components/table_vis_columns.tsx @@ -10,9 +10,8 @@ import React from 'react'; import { EuiDataGridColumnCellActionProps, EuiDataGridColumn } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; -import { Table } from '../table_vis_response_handler'; -import { FormattedColumn, TableVisUiState } from '../types'; +import { DatatableColumn, DatatableRow, IInterpreterRenderHandlers } from 'src/plugins/expressions'; +import { FormattedColumns, TableVisUiState } from '../types'; interface FilterCellData { /** @@ -27,33 +26,24 @@ interface FilterCellData { } export const createGridColumns = ( - table: Table, - columns: FormattedColumn[], + columns: DatatableColumn[], + rows: DatatableRow[], + formattedColumns: FormattedColumns, columnsWidth: TableVisUiState['colWidth'], - rows: Table['rows'], fireEvent: IInterpreterRenderHandlers['event'] ) => { const onFilterClick = (data: FilterCellData, negate: boolean) => { - /** - * Visible column index and the actual one from the source table could be different. - * e.x. a column could be filtered out if it's not a dimension - - * see formattedColumns in use_formatted_columns.ts file, - * or an extra percantage column could be added, which doesn't exist in the raw table - */ - const rawTableActualColumnIndex = table.columns.findIndex( - (c) => c.id === columns[data.column].id - ); fireEvent({ name: 'filterBucket', data: { data: [ { table: { - ...table, + columns, rows, }, ...data, - column: rawTableActualColumnIndex, + column: data.column, }, ], negate, @@ -63,12 +53,13 @@ export const createGridColumns = ( return columns.map( (col, colIndex): EuiDataGridColumn => { - const cellActions = col.filterable + const formattedColumn = formattedColumns[col.id]; + const cellActions = formattedColumn.filterable ? [ ({ rowIndex, columnId, Component, closePopover }: EuiDataGridColumnCellActionProps) => { const rowValue = rows[rowIndex][columnId]; const contentsIsDefined = rowValue !== null && rowValue !== undefined; - const cellContent = col.formatter.convert(rowValue); + const cellContent = formattedColumn.formatter.convert(rowValue); const filterForText = i18n.translate( 'visTypeTable.tableCellFilter.filterForValueText', @@ -105,7 +96,7 @@ export const createGridColumns = ( ({ rowIndex, columnId, Component, closePopover }: EuiDataGridColumnCellActionProps) => { const rowValue = rows[rowIndex][columnId]; const contentsIsDefined = rowValue !== null && rowValue !== undefined; - const cellContent = col.formatter.convert(rowValue); + const cellContent = formattedColumn.formatter.convert(rowValue); const filterOutText = i18n.translate( 'visTypeTable.tableCellFilter.filterOutValueText', @@ -144,8 +135,8 @@ export const createGridColumns = ( const initialWidth = columnsWidth.find((c) => c.colIndex === colIndex); const column: EuiDataGridColumn = { id: col.id, - display: col.title, - displayAsText: col.title, + display: col.name, + displayAsText: col.name, actions: { showHide: false, showMoveLeft: false, diff --git a/src/plugins/vis_type_table/public/components/table_vis_controls.tsx b/src/plugins/vis_type_table/public/components/table_vis_controls.tsx index 1f4f49442957f..3eda73084e41d 100644 --- a/src/plugins/vis_type_table/public/components/table_vis_controls.tsx +++ b/src/plugins/vis_type_table/public/components/table_vis_controls.tsx @@ -11,81 +11,103 @@ import { EuiButtonEmpty, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } f import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { DatatableRow } from 'src/plugins/expressions'; +import { DatatableColumn, DatatableRow } from 'src/plugins/expressions'; import { CoreStart } from 'kibana/public'; import { useKibana } from '../../../kibana_react/public'; -import { FormattedColumn } from '../types'; -import { Table } from '../table_vis_response_handler'; -import { exportAsCsv } from '../utils'; +import { exporters } from '../../../data/public'; +import { + CSV_SEPARATOR_SETTING, + CSV_QUOTE_VALUES_SETTING, + downloadFileAs, +} from '../../../share/public'; +import { getFormatService } from '../services'; interface TableVisControlsProps { dataGridAriaLabel: string; filename?: string; - cols: FormattedColumn[]; + columns: DatatableColumn[]; rows: DatatableRow[]; - table: Table; } -export const TableVisControls = memo(({ dataGridAriaLabel, ...props }: TableVisControlsProps) => { - const [isPopoverOpen, setIsPopoverOpen] = useState(false); - const togglePopover = useCallback(() => setIsPopoverOpen((state) => !state), []); - const closePopover = useCallback(() => setIsPopoverOpen(false), []); +export const TableVisControls = memo( + ({ dataGridAriaLabel, filename, columns, rows }: TableVisControlsProps) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const togglePopover = useCallback(() => setIsPopoverOpen((state) => !state), []); + const closePopover = useCallback(() => setIsPopoverOpen(false), []); - const { - services: { uiSettings }, - } = useKibana(); + const { + services: { uiSettings }, + } = useKibana(); - const onClickExport = useCallback( - (formatted: boolean) => - exportAsCsv(formatted, { - ...props, - uiSettings, - }), - [props, uiSettings] - ); + const onClickExport = useCallback( + (formatted: boolean) => { + const csvSeparator = uiSettings.get(CSV_SEPARATOR_SETTING); + const quoteValues = uiSettings.get(CSV_QUOTE_VALUES_SETTING); - const exportBtnAriaLabel = i18n.translate('visTypeTable.vis.controls.exportButtonAriaLabel', { - defaultMessage: 'Export {dataGridAriaLabel} as CSV', - values: { - dataGridAriaLabel, - }, - }); + const content = exporters.datatableToCSV( + { + type: 'datatable', + columns, + rows, + }, + { + csvSeparator, + quoteValues, + formatFactory: getFormatService().deserialize, + raw: !formatted, + } + ); + downloadFileAs(`${filename || 'unsaved'}.csv`, { content, type: exporters.CSV_MIME_TYPE }); + }, + [columns, rows, filename, uiSettings] + ); - const button = ( - - - - ); + const exportBtnAriaLabel = i18n.translate('visTypeTable.vis.controls.exportButtonAriaLabel', { + defaultMessage: 'Export {dataGridAriaLabel} as CSV', + values: { + dataGridAriaLabel, + }, + }); - const items = [ - onClickExport(false)}> - - , - onClickExport(true)}> - - , - ]; + const button = ( + + + + ); - return ( - - - - ); -}); + const items = [ + onClickExport(false)}> + + , + onClickExport(true)}> + + , + ]; + + return ( + + + + ); + } +); diff --git a/src/plugins/vis_type_table/public/components/table_vis_split.tsx b/src/plugins/vis_type_table/public/components/table_vis_split.tsx index be1a918e22c4b..3d1cacd732fae 100644 --- a/src/plugins/vis_type_table/public/components/table_vis_split.tsx +++ b/src/plugins/vis_type_table/public/components/table_vis_split.tsx @@ -9,8 +9,7 @@ import React, { memo } from 'react'; import { IInterpreterRenderHandlers } from 'src/plugins/expressions'; -import { TableGroup } from '../table_vis_response_handler'; -import { TableVisConfig, TableVisUseUiStateProps } from '../types'; +import { TableGroup, TableVisConfig, TableVisUseUiStateProps } from '../types'; import { TableVisBasic } from './table_vis_basic'; interface TableVisSplitProps { @@ -24,11 +23,11 @@ export const TableVisSplit = memo( ({ fireEvent, tables, visConfig, uiStateProps }: TableVisSplitProps) => { return ( <> - {tables.map(({ tables: dataTable, key, title }) => ( -
+ {tables.map(({ table, title }) => ( +
{ diff --git a/src/plugins/vis_type_table/public/table_vis_fn.test.ts b/src/plugins/vis_type_table/public/table_vis_fn.test.ts index ea8030688caed..31b440ffb642f 100644 --- a/src/plugins/vis_type_table/public/table_vis_fn.test.ts +++ b/src/plugins/vis_type_table/public/table_vis_fn.test.ts @@ -7,11 +7,11 @@ */ import { createTableVisFn } from './table_vis_fn'; -import { tableVisResponseHandler } from './table_vis_response_handler'; +import { tableVisResponseHandler } from './utils'; import { functionWrapper } from '../../expressions/common/expression_functions/specs/tests/utils'; -jest.mock('./table_vis_response_handler', () => ({ +jest.mock('./utils', () => ({ tableVisResponseHandler: jest.fn().mockReturnValue({ tables: [{ columns: [], rows: [] }], }), @@ -62,6 +62,6 @@ describe('interpreter/functions#table', () => { it('calls response handler with correct values', async () => { await fn(context, { visConfig: JSON.stringify(visConfig) }, undefined); expect(tableVisResponseHandler).toHaveBeenCalledTimes(1); - expect(tableVisResponseHandler).toHaveBeenCalledWith(context, visConfig.dimensions); + expect(tableVisResponseHandler).toHaveBeenCalledWith(context, visConfig); }); }); diff --git a/src/plugins/vis_type_table/public/table_vis_fn.ts b/src/plugins/vis_type_table/public/table_vis_fn.ts index 99fee424b8bea..3dd8e81fc2ab2 100644 --- a/src/plugins/vis_type_table/public/table_vis_fn.ts +++ b/src/plugins/vis_type_table/public/table_vis_fn.ts @@ -7,10 +7,10 @@ */ import { i18n } from '@kbn/i18n'; -import { tableVisResponseHandler, TableContext } from './table_vis_response_handler'; import { ExpressionFunctionDefinition, Datatable, Render } from '../../expressions/public'; -import { TableVisConfig } from './types'; +import { TableVisData, TableVisConfig } from './types'; import { VIS_TYPE_TABLE } from '../common'; +import { tableVisResponseHandler } from './utils'; export type Input = Datatable; @@ -19,7 +19,7 @@ interface Arguments { } export interface TableVisRenderValue { - visData: TableContext; + visData: TableVisData; visType: typeof VIS_TYPE_TABLE; visConfig: TableVisConfig; } @@ -47,7 +47,7 @@ export const createTableVisFn = (): TableExpressionFunctionDefinition => ({ }, fn(input, args, handlers) { const visConfig = args.visConfig && JSON.parse(args.visConfig); - const convertedData = tableVisResponseHandler(input, visConfig.dimensions); + const convertedData = tableVisResponseHandler(input, visConfig); if (handlers?.inspectorAdapters?.tables) { handlers.inspectorAdapters.tables.logDatatable('default', input); diff --git a/src/plugins/vis_type_table/public/table_vis_response_handler.ts b/src/plugins/vis_type_table/public/table_vis_response_handler.ts deleted file mode 100644 index dbd01f94bd3c5..0000000000000 --- a/src/plugins/vis_type_table/public/table_vis_response_handler.ts +++ /dev/null @@ -1,90 +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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { Required } from '@kbn/utility-types'; - -import { getFormatService } from './services'; -import { Input } from './table_vis_fn'; -import { Dimensions } from './types'; - -export interface TableContext { - table?: Table; - tables: TableGroup[]; - direction?: 'row' | 'column'; -} - -export interface TableGroup { - table: Input; - tables: Table[]; - title: string; - name: string; - key: string | number; - column: number; - row: number; -} - -export interface Table { - columns: Input['columns']; - rows: Input['rows']; -} - -export function tableVisResponseHandler(input: Input, dimensions: Dimensions): TableContext { - let table: Table | undefined; - let tables: TableGroup[] = []; - let direction: TableContext['direction']; - - const split = dimensions.splitColumn || dimensions.splitRow; - - if (split) { - tables = []; - direction = dimensions.splitRow ? 'row' : 'column'; - const splitColumnIndex = split[0].accessor; - const splitColumnFormatter = getFormatService().deserialize(split[0].format); - const splitColumn = input.columns[splitColumnIndex]; - const splitMap: { [key: string]: number } = {}; - let splitIndex = 0; - - input.rows.forEach((row, rowIndex) => { - const splitValue: string | number = row[splitColumn.id]; - - if (!splitMap.hasOwnProperty(splitValue)) { - splitMap[splitValue] = splitIndex++; - const tableGroup: Required = { - title: `${splitColumnFormatter.convert(splitValue)}: ${splitColumn.name}`, - name: splitColumn.name, - key: splitValue, - column: splitColumnIndex, - row: rowIndex, - table: input, - tables: [], - }; - - tableGroup.tables.push({ - columns: input.columns, - rows: [], - }); - - tables.push(tableGroup); - } - - const tableIndex = splitMap[splitValue]; - tables[tableIndex].tables[0].rows.push(row); - }); - } else { - table = { - columns: input.columns, - rows: input.rows, - }; - } - - return { - direction, - table, - tables, - }; -} diff --git a/src/plugins/vis_type_table/public/types.ts b/src/plugins/vis_type_table/public/types.ts index 03cf8bb3395d6..61ba7739b9cb1 100644 --- a/src/plugins/vis_type_table/public/types.ts +++ b/src/plugins/vis_type_table/public/types.ts @@ -7,6 +7,7 @@ */ import { IFieldFormat } from 'src/plugins/data/public'; +import { DatatableColumn, DatatableRow } from 'src/plugins/expressions'; import { SchemaConfig } from 'src/plugins/visualizations/public'; import { TableVisParams } from '../common'; @@ -43,7 +44,6 @@ export interface TableVisConfig extends TableVisParams { } export interface FormattedColumn { - id: string; title: string; formatter: IFieldFormat; formattedTotal?: string | number; @@ -51,3 +51,24 @@ export interface FormattedColumn { sumTotal?: number; total?: number; } + +export interface FormattedColumns { + [key: string]: FormattedColumn; +} + +export interface TableContext { + columns: DatatableColumn[]; + rows: DatatableRow[]; + formattedColumns: FormattedColumns; +} + +export interface TableGroup { + table: TableContext; + title: string; +} + +export interface TableVisData { + table?: TableContext; + tables: TableGroup[]; + direction?: 'row' | 'column'; +} diff --git a/src/plugins/vis_type_table/public/utils/add_percentage_column.ts b/src/plugins/vis_type_table/public/utils/add_percentage_column.ts index 0e3879255dd06..11528c76ee300 100644 --- a/src/plugins/vis_type_table/public/utils/add_percentage_column.ts +++ b/src/plugins/vis_type_table/public/utils/add_percentage_column.ts @@ -6,48 +6,59 @@ * Public License, v 1. */ +import { findIndex } from 'lodash'; import { i18n } from '@kbn/i18n'; -import { DatatableRow } from 'src/plugins/expressions'; +import { DatatableColumn } from 'src/plugins/expressions'; import { getFormatService } from '../services'; -import { FormattedColumn } from '../types'; -import { Table } from '../table_vis_response_handler'; +import { FormattedColumns, TableContext } from '../types'; -function insertColumn(arr: FormattedColumn[], index: number, col: FormattedColumn) { +function insertColumn(arr: DatatableColumn[], index: number, col: DatatableColumn) { const newArray = [...arr]; newArray.splice(index + 1, 0, col); return newArray; } /** - * @param columns - the formatted columns that will be displayed - * @param title - the title of the column to add to - * @param rows - the row data for the columns - * @param insertAtIndex - the index to insert the percentage column at - * @returns cols and rows for the table to render now included percentage column(s) + * Adds a brand new column with percentages of selected column to existing data table */ -export function addPercentageColumn( - columns: FormattedColumn[], - title: string, - rows: Table['rows'], - insertAtIndex: number -) { - const { id, sumTotal } = columns[insertAtIndex]; - const newId = `${id}-percents`; +export function addPercentageColumn(table: TableContext, name: string) { + const { columns, rows, formattedColumns } = table; + const insertAtIndex = findIndex(columns, { name }); + // column to show percentage for was removed + if (insertAtIndex < 0) return table; + + const { id } = columns[insertAtIndex]; + const { sumTotal } = formattedColumns[id]; + const percentageColumnId = `${id}-percents`; const formatter = getFormatService().deserialize({ id: 'percent' }); - const i18nTitle = i18n.translate('visTypeTable.params.percentageTableColumnName', { + const percentageColumnName = i18n.translate('visTypeTable.params.percentageTableColumnName', { defaultMessage: '{title} percentages', - values: { title }, + values: { title: name }, }); const newCols = insertColumn(columns, insertAtIndex, { - title: i18nTitle, - id: newId, - formatter, - filterable: false, + name: percentageColumnName, + id: percentageColumnId, + meta: { + type: 'number', + params: { id: 'percent' }, + }, }); - const newRows = rows.map((row) => ({ - [newId]: (row[id] as number) / (sumTotal as number), + const newFormattedColumns: FormattedColumns = { + ...formattedColumns, + [percentageColumnId]: { + title: percentageColumnName, + formatter, + filterable: false, + }, + }; + const newRows = rows.map((row) => ({ + [percentageColumnId]: (row[id] as number) / (sumTotal as number), ...row, })); - return { cols: newCols, rows: newRows }; + return { + columns: newCols, + rows: newRows, + formattedColumns: newFormattedColumns, + }; } diff --git a/src/plugins/vis_type_table/public/utils/create_formatted_table.ts b/src/plugins/vis_type_table/public/utils/create_formatted_table.ts new file mode 100644 index 0000000000000..9dbb6c0c76e25 --- /dev/null +++ b/src/plugins/vis_type_table/public/utils/create_formatted_table.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { chain } from 'lodash'; +import { Datatable } from 'src/plugins/expressions'; +import { getFormatService } from '../services'; +import { FormattedColumn, FormattedColumns, TableVisConfig, TableContext } from '../types'; +import { AggTypes } from '../../common'; + +export const createFormattedTable = ( + table: Datatable | TableContext, + visConfig: TableVisConfig +) => { + const { buckets, metrics } = visConfig.dimensions; + + const formattedColumns = table.columns.reduce((acc, col, i) => { + const isBucket = buckets.find(({ accessor }) => accessor === i); + const dimension = isBucket || metrics.find(({ accessor }) => accessor === i); + + if (!dimension) return acc; + + const formatter = getFormatService().deserialize(dimension.format); + const formattedColumn: FormattedColumn = { + title: col.name, + formatter, + filterable: !!isBucket, + }; + + const isDate = dimension.format.id === 'date' || dimension.format.params?.id === 'date'; + const allowsNumericalAggregations = formatter.allowsNumericalAggregations; + + if (allowsNumericalAggregations || isDate || visConfig.totalFunc === AggTypes.COUNT) { + const sumOfColumnValues = table.rows.reduce((prev, curr) => { + // some metrics return undefined for some of the values + // derivative is an example of this as it returns undefined in the first row + if (curr[col.id] === undefined) return prev; + return prev + (curr[col.id] as number); + }, 0); + + formattedColumn.sumTotal = sumOfColumnValues; + + switch (visConfig.totalFunc) { + case AggTypes.SUM: { + if (!isDate) { + formattedColumn.formattedTotal = formatter.convert(sumOfColumnValues); + formattedColumn.total = sumOfColumnValues; + } + break; + } + case AggTypes.AVG: { + if (!isDate) { + const total = sumOfColumnValues / table.rows.length; + formattedColumn.formattedTotal = formatter.convert(total); + formattedColumn.total = total; + } + break; + } + case AggTypes.MIN: { + const total = chain(table.rows).map(col.id).min().value() as number; + formattedColumn.formattedTotal = formatter.convert(total); + formattedColumn.total = total; + break; + } + case AggTypes.MAX: { + const total = chain(table.rows).map(col.id).max().value() as number; + formattedColumn.formattedTotal = formatter.convert(total); + formattedColumn.total = total; + break; + } + case AggTypes.COUNT: { + const total = table.rows.length; + formattedColumn.formattedTotal = total; + formattedColumn.total = total; + break; + } + default: + break; + } + } + + acc[col.id] = formattedColumn; + + return acc; + }, {}); + + return { + // filter out columns which are not dimensions + columns: table.columns.filter((col) => formattedColumns[col.id]), + rows: table.rows, + formattedColumns, + }; +}; diff --git a/src/plugins/vis_type_table/public/utils/export_as_csv.ts b/src/plugins/vis_type_table/public/utils/export_as_csv.ts deleted file mode 100644 index 4371a20cfa0da..0000000000000 --- a/src/plugins/vis_type_table/public/utils/export_as_csv.ts +++ /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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { isObject } from 'lodash'; -// @ts-ignore -import { saveAs } from '@elastic/filesaver'; - -import { CoreStart } from 'kibana/public'; -import { DatatableRow } from 'src/plugins/expressions'; -import { CSV_SEPARATOR_SETTING, CSV_QUOTE_VALUES_SETTING } from '../../../share/public'; -import { FormattedColumn } from '../types'; -import { Table } from '../table_vis_response_handler'; - -const nonAlphaNumRE = /[^a-zA-Z0-9]/; -const allDoubleQuoteRE = /"/g; - -interface ToCsvData { - filename?: string; - cols: FormattedColumn[]; - rows: DatatableRow[]; - table: Table; - uiSettings: CoreStart['uiSettings']; -} - -const toCsv = (formatted: boolean, { cols, rows, table, uiSettings }: ToCsvData) => { - const separator = uiSettings.get(CSV_SEPARATOR_SETTING); - const quoteValues = uiSettings.get(CSV_QUOTE_VALUES_SETTING); - - function escape(val: unknown) { - if (!formatted && isObject(val)) val = val.valueOf(); - val = String(val); - if (quoteValues && nonAlphaNumRE.test(val as string)) { - val = '"' + (val as string).replace(allDoubleQuoteRE, '""') + '"'; - } - return val as string; - } - - const csvRows: string[][] = []; - - for (const row of rows) { - const rowArray: string[] = []; - for (const col of cols) { - const value = row[col.id]; - const formattedValue = formatted ? escape(col.formatter.convert(value)) : escape(value); - rowArray.push(formattedValue); - } - csvRows.push(rowArray); - } - - // add headers to the rows - csvRows.unshift(cols.map(({ title }) => escape(title))); - - return csvRows.map((row) => row.join(separator) + '\r\n').join(''); -}; - -export const exportAsCsv = (formatted: boolean, data: ToCsvData) => { - const csv = new Blob([toCsv(formatted, data)], { type: 'text/plain;charset=utf-8' }); - saveAs(csv, `${data.filename || 'unsaved'}.csv`); -}; diff --git a/src/plugins/vis_type_table/public/utils/index.ts b/src/plugins/vis_type_table/public/utils/index.ts index 6a6dda0d12fa3..8731b52a7ba30 100644 --- a/src/plugins/vis_type_table/public/utils/index.ts +++ b/src/plugins/vis_type_table/public/utils/index.ts @@ -7,4 +7,4 @@ */ export * from './use'; -export * from './export_as_csv'; +export * from './table_vis_response_handler'; diff --git a/src/plugins/vis_type_table/public/utils/table_vis_response_handler.ts b/src/plugins/vis_type_table/public/utils/table_vis_response_handler.ts new file mode 100644 index 0000000000000..0a2b8d8180854 --- /dev/null +++ b/src/plugins/vis_type_table/public/utils/table_vis_response_handler.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { Datatable } from 'src/plugins/expressions'; +import { getFormatService } from '../services'; +import { TableVisData, TableGroup, TableVisConfig, TableContext } from '../types'; +import { addPercentageColumn } from './add_percentage_column'; +import { createFormattedTable } from './create_formatted_table'; + +/** + * Converts datatable input from response into appropriate format for consuming renderer + */ +export function tableVisResponseHandler(input: Datatable, visConfig: TableVisConfig): TableVisData { + const tables: TableGroup[] = []; + let table: TableContext | undefined; + let direction: TableVisData['direction']; + + const split = visConfig.dimensions.splitColumn || visConfig.dimensions.splitRow; + + if (split) { + direction = visConfig.dimensions.splitRow ? 'row' : 'column'; + const splitColumnIndex = split[0].accessor; + const splitColumnFormatter = getFormatService().deserialize(split[0].format); + const splitColumn = input.columns[splitColumnIndex]; + const columns = input.columns.filter((c, idx) => idx !== splitColumnIndex); + const splitMap: { [key: string]: number } = {}; + let splitIndex = 0; + + input.rows.forEach((row) => { + const splitValue: string | number = row[splitColumn.id]; + + if (!splitMap.hasOwnProperty(splitValue)) { + splitMap[splitValue] = splitIndex++; + const tableGroup: TableGroup = { + title: `${splitColumnFormatter.convert(splitValue)}: ${splitColumn.name}`, + table: { + columns, + rows: [], + formattedColumns: {}, + }, + }; + + tables.push(tableGroup); + } + + const tableIndex = splitMap[splitValue]; + tables[tableIndex].table.rows.push(row); + }); + + tables.forEach((tg) => { + tg.table = createFormattedTable({ ...tg.table, columns: input.columns }, visConfig); + + if (visConfig.percentageCol) { + tg.table = addPercentageColumn(tg.table, visConfig.percentageCol); + } + }); + } else { + table = createFormattedTable(input, visConfig); + + if (visConfig.percentageCol) { + table = addPercentageColumn(table, visConfig.percentageCol); + } + } + + return { + direction, + table, + tables, + }; +} diff --git a/src/plugins/vis_type_table/public/utils/use/index.ts b/src/plugins/vis_type_table/public/utils/use/index.ts index 08daf7f28c0e8..9fcc791561046 100644 --- a/src/plugins/vis_type_table/public/utils/use/index.ts +++ b/src/plugins/vis_type_table/public/utils/use/index.ts @@ -6,6 +6,5 @@ * Public License, v 1. */ -export * from './use_formatted_columns'; export * from './use_pagination'; export * from './use_ui_state'; diff --git a/src/plugins/vis_type_table/public/utils/use/use_formatted_columns.ts b/src/plugins/vis_type_table/public/utils/use/use_formatted_columns.ts deleted file mode 100644 index 3a733e7a9a4dc..0000000000000 --- a/src/plugins/vis_type_table/public/utils/use/use_formatted_columns.ts +++ /dev/null @@ -1,115 +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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { useMemo } from 'react'; -import { chain, findIndex } from 'lodash'; - -import { AggTypes } from '../../../common'; -import { Table } from '../../table_vis_response_handler'; -import { FormattedColumn, TableVisConfig } from '../../types'; -import { getFormatService } from '../../services'; -import { addPercentageColumn } from '../add_percentage_column'; - -export const useFormattedColumnsAndRows = (table: Table, visConfig: TableVisConfig) => { - const { formattedColumns: columns, formattedRows: rows } = useMemo(() => { - const { buckets, metrics } = visConfig.dimensions; - let formattedRows = table.rows; - - let formattedColumns = table.columns - .map((col, i) => { - const isBucket = buckets.find(({ accessor }) => accessor === i); - const dimension = isBucket || metrics.find(({ accessor }) => accessor === i); - - if (!dimension) return undefined; - - const formatter = getFormatService().deserialize(dimension.format); - const formattedColumn: FormattedColumn = { - id: col.id, - title: col.name, - formatter, - filterable: !!isBucket, - }; - - const isDate = dimension.format.id === 'date' || dimension.format.params?.id === 'date'; - const allowsNumericalAggregations = formatter.allowsNumericalAggregations; - - if (allowsNumericalAggregations || isDate || visConfig.totalFunc === AggTypes.COUNT) { - const sumOfColumnValues = table.rows.reduce((prev, curr) => { - // some metrics return undefined for some of the values - // derivative is an example of this as it returns undefined in the first row - if (curr[col.id] === undefined) return prev; - return prev + (curr[col.id] as number); - }, 0); - - formattedColumn.sumTotal = sumOfColumnValues; - - switch (visConfig.totalFunc) { - case AggTypes.SUM: { - if (!isDate) { - formattedColumn.formattedTotal = formatter.convert(sumOfColumnValues); - formattedColumn.total = sumOfColumnValues; - } - break; - } - case AggTypes.AVG: { - if (!isDate) { - const total = sumOfColumnValues / table.rows.length; - formattedColumn.formattedTotal = formatter.convert(total); - formattedColumn.total = total; - } - break; - } - case AggTypes.MIN: { - const total = chain(table.rows).map(col.id).min().value() as number; - formattedColumn.formattedTotal = formatter.convert(total); - formattedColumn.total = total; - break; - } - case AggTypes.MAX: { - const total = chain(table.rows).map(col.id).max().value() as number; - formattedColumn.formattedTotal = formatter.convert(total); - formattedColumn.total = total; - break; - } - case AggTypes.COUNT: { - const total = table.rows.length; - formattedColumn.formattedTotal = total; - formattedColumn.total = total; - break; - } - default: - break; - } - } - - return formattedColumn; - }) - .filter((column): column is FormattedColumn => !!column); - - if (visConfig.percentageCol) { - const insertAtIndex = findIndex(formattedColumns, { title: visConfig.percentageCol }); - - // column to show percentage for was removed - if (insertAtIndex < 0) return { formattedColumns, formattedRows }; - - const { cols, rows: rowsWithPercentage } = addPercentageColumn( - formattedColumns, - visConfig.percentageCol, - table.rows, - insertAtIndex - ); - - formattedRows = rowsWithPercentage; - formattedColumns = cols; - } - - return { formattedColumns, formattedRows }; - }, [table, visConfig.dimensions, visConfig.percentageCol, visConfig.totalFunc]); - - return { columns, rows }; -}; diff --git a/src/plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.test.ts b/src/plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.test.ts index 25ec326527970..a1c47292ba81f 100644 --- a/src/plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.test.ts +++ b/src/plugins/vis_type_timelion/public/components/timelion_expression_input_helpers.test.ts @@ -8,14 +8,12 @@ import { SUGGESTION_TYPE, suggest } from './timelion_expression_input_helpers'; import { getArgValueSuggestions } from '../helpers/arg_value_suggestions'; -import { setIndexPatterns, setSavedObjectsClient } from '../helpers/plugin_services'; +import { setIndexPatterns } from '../helpers/plugin_services'; import { IndexPatternsContract } from 'src/plugins/data/public'; -import { SavedObjectsClient } from 'kibana/public'; import { ITimelionFunction } from '../../common/types'; describe('Timelion expression suggestions', () => { setIndexPatterns({} as IndexPatternsContract); - setSavedObjectsClient({} as SavedObjectsClient); const argValueSuggestions = getArgValueSuggestions(); diff --git a/src/plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts b/src/plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts index 444559a0b4581..b1dfe2044d8a5 100644 --- a/src/plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts +++ b/src/plugins/vis_type_timelion/public/helpers/arg_value_suggestions.ts @@ -7,9 +7,13 @@ */ import { get } from 'lodash'; -import { getIndexPatterns, getSavedObjectsClient } from './plugin_services'; +import { getIndexPatterns } from './plugin_services'; import { TimelionFunctionArgs } from '../../common/types'; -import { indexPatterns as indexPatternsUtils, IndexPatternAttributes } from '../../../data/public'; +import { + IndexPatternField, + indexPatterns as indexPatternsUtils, + KBN_FIELD_TYPES, +} from '../../../data/public'; export interface Location { min: number; @@ -30,9 +34,10 @@ export interface FunctionArg { }; } +const isRuntimeField = (field: IndexPatternField) => Boolean(field.runtimeField); + export function getArgValueSuggestions() { const indexPatterns = getIndexPatterns(); - const savedObjectsClient = getSavedObjectsClient(); async function getIndexPattern(functionArgs: FunctionArg[]) { const indexPatternArg = functionArgs.find(({ name }) => name === 'index'); @@ -42,22 +47,9 @@ export function getArgValueSuggestions() { } 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 + return (await indexPatterns.find(indexPatternTitle)).find( + (index) => index.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 }) { @@ -73,18 +65,11 @@ export function getArgValueSuggestions() { 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 }; - }); + const size = 25; + + return (await indexPatterns.find(search, size)).map(({ title }) => ({ + name: title, + })); }, async metric(partial: string, functionArgs: FunctionArg[]) { if (!partial || !partial.includes(':')) { @@ -106,18 +91,15 @@ export function getArgValueSuggestions() { const valueSplit = partial.split(':'); return indexPattern.fields - .getAll() - .filter((field) => { - return ( + .getByType(KBN_FIELD_TYPES.NUMBER) + .filter( + (field) => + !isRuntimeField(field) && field.aggregatable && - 'number' === field.type && containsFieldName(valueSplit[1], field) && !indexPatternsUtils.isNestedField(field) - ); - }) - .map((field) => { - return { name: `${valueSplit[0]}:${field.name}`, help: field.type }; - }); + ) + .map((field) => ({ name: `${valueSplit[0]}:${field.name}`, help: field.type })); }, async split(partial: string, functionArgs: FunctionArg[]) { const indexPattern = await getIndexPattern(functionArgs); @@ -127,17 +109,21 @@ export function getArgValueSuggestions() { return indexPattern.fields .getAll() - .filter((field) => { - return ( + .filter( + (field) => + !isRuntimeField(field) && field.aggregatable && - ['number', 'boolean', 'date', 'ip', 'string'].includes(field.type) && + [ + KBN_FIELD_TYPES.NUMBER, + KBN_FIELD_TYPES.BOOLEAN, + KBN_FIELD_TYPES.DATE, + KBN_FIELD_TYPES.IP, + KBN_FIELD_TYPES.STRING, + ].includes(field.type as KBN_FIELD_TYPES) && containsFieldName(partial, field) && !indexPatternsUtils.isNestedField(field) - ); - }) - .map((field) => { - return { name: field.name, help: field.type }; - }); + ) + .map((field) => ({ name: field.name, help: field.type })); }, async timefield(partial: string, functionArgs: FunctionArg[]) { const indexPattern = await getIndexPattern(functionArgs); @@ -146,17 +132,14 @@ export function getArgValueSuggestions() { } return indexPattern.fields - .getAll() - .filter((field) => { - return ( - 'date' === field.type && + .getByType(KBN_FIELD_TYPES.DATE) + .filter( + (field) => + !isRuntimeField(field) && containsFieldName(partial, field) && !indexPatternsUtils.isNestedField(field) - ); - }) - .map((field) => { - return { name: field.name }; - }); + ) + .map((field) => ({ name: field.name })); }, }, }; diff --git a/src/plugins/vis_type_timelion/public/helpers/plugin_services.ts b/src/plugins/vis_type_timelion/public/helpers/plugin_services.ts index 0a85b1c1e5fed..5c23652c3207c 100644 --- a/src/plugins/vis_type_timelion/public/helpers/plugin_services.ts +++ b/src/plugins/vis_type_timelion/public/helpers/plugin_services.ts @@ -7,7 +7,6 @@ */ import type { IndexPatternsContract, ISearchStart } from 'src/plugins/data/public'; -import type { SavedObjectsClientContract } from 'kibana/public'; import { createGetterSetter } from '../../../kibana_utils/public'; export const [getIndexPatterns, setIndexPatterns] = createGetterSetter( @@ -15,8 +14,3 @@ export const [getIndexPatterns, setIndexPatterns] = createGetterSetter('Search'); - -export const [ - getSavedObjectsClient, - setSavedObjectsClient, -] = createGetterSetter('SavedObjectsClient'); diff --git a/src/plugins/vis_type_timelion/public/plugin.ts b/src/plugins/vis_type_timelion/public/plugin.ts index e69b42d6c526c..1d200be0d276e 100644 --- a/src/plugins/vis_type_timelion/public/plugin.ts +++ b/src/plugins/vis_type_timelion/public/plugin.ts @@ -25,7 +25,7 @@ import { VisualizationsSetup } from '../../visualizations/public'; import { getTimelionVisualizationConfig } from './timelion_vis_fn'; import { getTimelionVisDefinition } from './timelion_vis_type'; -import { setIndexPatterns, setSavedObjectsClient, setDataSearch } from './helpers/plugin_services'; +import { setIndexPatterns, setDataSearch } from './helpers/plugin_services'; import { ConfigSchema } from '../config'; import { getArgValueSuggestions } from './helpers/arg_value_suggestions'; @@ -92,7 +92,6 @@ export class TimelionVisPlugin public start(core: CoreStart, plugins: TimelionVisStartDependencies) { setIndexPatterns(plugins.data.indexPatterns); - setSavedObjectsClient(core.savedObjects.client); setDataSearch(plugins.data.search); return { diff --git a/src/plugins/vis_type_timelion/server/plugin.ts b/src/plugins/vis_type_timelion/server/plugin.ts index fca557efc01e3..cdc75527b6f59 100644 --- a/src/plugins/vis_type_timelion/server/plugin.ts +++ b/src/plugins/vis_type_timelion/server/plugin.ts @@ -11,12 +11,8 @@ import { first } from 'rxjs/operators'; import { TypeOf, schema } from '@kbn/config-schema'; import { RecursiveReadonly } from '@kbn/utility-types'; import { deepFreeze } from '@kbn/std'; -import type { RequestHandlerContext } from 'src/core/server'; -import type { - PluginStart, - DataApiRequestHandlerContext, -} from '../../../../src/plugins/data/server'; +import type { PluginStart, DataRequestHandlerContext } from '../../../../src/plugins/data/server'; import { CoreSetup, PluginInitializerContext } from '../../../../src/core/server'; import { configSchema } from '../config'; import loadFunctions from './lib/load_functions'; @@ -46,7 +42,9 @@ export interface TimelionPluginStartDeps { export class Plugin { constructor(private readonly initializerContext: PluginInitializerContext) {} - public async setup(core: CoreSetup): Promise> { + public async setup( + core: CoreSetup + ): Promise> { const config = await this.initializerContext.config .create>() .pipe(first()) @@ -71,9 +69,7 @@ export class Plugin { const logger = this.initializerContext.logger.get('timelion'); - const router = core.http.createRouter< - RequestHandlerContext & { search: DataApiRequestHandlerContext } - >(); + const router = core.http.createRouter(); const deps = { configManager, diff --git a/src/plugins/vis_type_timelion/server/routes/run.ts b/src/plugins/vis_type_timelion/server/routes/run.ts index ae26013cc39f6..2f6c0d709fdcb 100644 --- a/src/plugins/vis_type_timelion/server/routes/run.ts +++ b/src/plugins/vis_type_timelion/server/routes/run.ts @@ -18,6 +18,7 @@ import getNamespacesSettings from '../lib/get_namespaced_settings'; import getTlConfig from '../handlers/lib/tl_config'; import { TimelionFunctionInterface } from '../types'; import { ConfigManager } from '../lib/config_manager'; +import { TimelionPluginStartDeps } from '../plugin'; const timelionDefaults = getNamespacesSettings(); @@ -32,7 +33,7 @@ export function runRoute( logger: Logger; getFunction: (name: string) => TimelionFunctionInterface; configManager: ConfigManager; - core: CoreSetup; + core: CoreSetup; } ) { router.post( @@ -77,17 +78,22 @@ export function runRoute( }, router.handleLegacyErrors(async (context, request, response) => { try { + const [, { data }] = await core.getStartServices(); const uiSettings = await context.core.uiSettings.client.getAll(); + const indexPatternsService = await data.indexPatterns.indexPatternsServiceFactory( + context.core.savedObjects.client, + context.core.elasticsearch.client.asCurrentUser + ); const tlConfig = getTlConfig({ context, request, settings: _.defaults(uiSettings, timelionDefaults), // Just in case they delete some setting. getFunction, + getIndexPatternsService: () => indexPatternsService, getStartServices: core.getStartServices, allowedGraphiteUrls: configManager.getGraphiteUrls(), esShardTimeout: configManager.getEsShardTimeout(), - savedObjectsClient: context.core.savedObjects.client, }); const chainRunner = chainRunnerFn(tlConfig); const sheet = await Bluebird.all(chainRunner.processRequest(request.body)); diff --git a/src/plugins/vis_type_timelion/server/routes/validate_es.ts b/src/plugins/vis_type_timelion/server/routes/validate_es.ts index 1637fcc464f46..e0e6270735440 100644 --- a/src/plugins/vis_type_timelion/server/routes/validate_es.ts +++ b/src/plugins/vis_type_timelion/server/routes/validate_es.ts @@ -7,12 +7,10 @@ */ import _ from 'lodash'; -import { IRouter, RequestHandlerContext } from 'kibana/server'; -import type { DataApiRequestHandlerContext } from '../../../data/server'; +import { IRouter } from 'kibana/server'; +import type { DataRequestHandlerContext } from '../../../data/server'; -export function validateEsRoute( - router: IRouter -) { +export function validateEsRoute(router: IRouter) { router.get( { path: '/api/timelion/validate/es', diff --git a/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js b/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js index 671042ae6f24c..8828fd6917fee 100644 --- a/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js +++ b/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js @@ -22,16 +22,12 @@ import { UI_SETTINGS } from '../../../../data/server'; describe('es', () => { let tlConfig; - function stubRequestAndServer(response, indexPatternSavedObjects = []) { + function stubRequestAndServer(response) { return { context: { search: { search: jest.fn().mockReturnValue(of(response)) } }, - savedObjectsClient: { - find: function () { - return Promise.resolve({ - saved_objects: indexPatternSavedObjects, - }); - }, - }, + getIndexPatternsService: () => ({ + find: async () => [], + }), }; } diff --git a/src/plugins/vis_type_timelion/server/series_functions/es/index.js b/src/plugins/vis_type_timelion/server/series_functions/es/index.js index bce0485039560..7aacc1c1632ff 100644 --- a/src/plugins/vis_type_timelion/server/series_functions/es/index.js +++ b/src/plugins/vis_type_timelion/server/series_functions/es/index.js @@ -96,23 +96,12 @@ export default new Datasource('es', { kibana: true, fit: 'nearest', }); + const indexPatternsService = tlConfig.getIndexPatternsService(); + const indexPatternSpec = (await indexPatternsService.find(config.index)).find( + (index) => index.title === config.index + ); - const findResp = await tlConfig.savedObjectsClient.find({ - type: 'index-pattern', - fields: ['title', 'fields'], - search: `"${config.index}"`, - search_fields: ['title'], - }); - const indexPatternSavedObject = findResp.saved_objects.find((savedObject) => { - return savedObject.attributes.title === config.index; - }); - let scriptedFields = []; - if (indexPatternSavedObject) { - const fields = JSON.parse(indexPatternSavedObject.attributes.fields); - scriptedFields = fields.filter((field) => { - return field.scripted; - }); - } + const scriptedFields = indexPatternSpec?.getScriptedFields() ?? []; const esShardTimeout = tlConfig.esShardTimeout; diff --git a/src/plugins/vis_type_timeseries/server/types.ts b/src/plugins/vis_type_timeseries/server/types.ts index 29cd33031c883..3ee052bb2bea2 100644 --- a/src/plugins/vis_type_timeseries/server/types.ts +++ b/src/plugins/vis_type_timeseries/server/types.ts @@ -6,11 +6,8 @@ * Public License, v 1. */ -import type { IRouter, RequestHandlerContext } from 'src/core/server'; -import type { DataApiRequestHandlerContext } from '../../data/server'; - -export interface VisTypeTimeseriesRequestHandlerContext extends RequestHandlerContext { - search: DataApiRequestHandlerContext; -} +import type { IRouter } from 'src/core/server'; +import type { DataRequestHandlerContext } from '../../data/server'; +export type VisTypeTimeseriesRequestHandlerContext = DataRequestHandlerContext; export type VisTypeTimeseriesRouter = IRouter; diff --git a/src/plugins/visualize/kibana.json b/src/plugins/visualize/kibana.json index 2256a7a7f550d..144d33debe3c9 100644 --- a/src/plugins/visualize/kibana.json +++ b/src/plugins/visualize/kibana.json @@ -11,7 +11,6 @@ "visualizations", "embeddable", "dashboard", - "uiActions", "presentationUtil" ], "optionalPlugins": [ diff --git a/src/plugins/visualize/public/actions/visualize_field_action.ts b/src/plugins/visualize/public/actions/visualize_field_action.ts deleted file mode 100644 index f83e1f1930323..0000000000000 --- a/src/plugins/visualize/public/actions/visualize_field_action.ts +++ /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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; -import { - createAction, - ACTION_VISUALIZE_FIELD, - VisualizeFieldContext, -} from '../../../ui_actions/public'; -import { - getApplication, - getUISettings, - getIndexPatterns, - getQueryService, - getShareService, -} from '../services'; -import { VISUALIZE_APP_URL_GENERATOR, VisualizeUrlGeneratorState } from '../url_generator'; -import { AGGS_TERMS_SIZE_SETTING } from '../../common/constants'; - -export const visualizeFieldAction = createAction({ - type: ACTION_VISUALIZE_FIELD, - id: ACTION_VISUALIZE_FIELD, - getDisplayName: () => - i18n.translate('visualize.discover.visualizeFieldLabel', { - defaultMessage: 'Visualize field', - }), - isCompatible: async () => !!getApplication().capabilities.visualize.show, - getHref: async (context) => { - const url = await getVisualizeUrl(context); - return url; - }, - execute: async (context) => { - const url = await getVisualizeUrl(context); - const hash = url.split('#')[1]; - - getApplication().navigateToApp('visualize', { - path: `/#${hash}`, - }); - }, -}); - -const getVisualizeUrl = async (context: VisualizeFieldContext) => { - const indexPattern = await getIndexPatterns().get(context.indexPatternId); - const field = indexPattern.fields.find((fld) => fld.name === context.fieldName); - const aggsTermSize = getUISettings().get(AGGS_TERMS_SIZE_SETTING); - let agg; - - // If we're visualizing a date field, and our index is time based (and thus has a time filter), - // then run a date histogram - if (field?.type === 'date' && indexPattern.timeFieldName === context.fieldName) { - agg = { - type: 'date_histogram', - schema: 'segment', - params: { - field: context.fieldName, - interval: 'auto', - }, - }; - } else { - agg = { - type: 'terms', - schema: 'segment', - params: { - field: context.fieldName, - size: parseInt(aggsTermSize, 10), - orderBy: '1', - }, - }; - } - const generator = getShareService().urlGenerators.getUrlGenerator(VISUALIZE_APP_URL_GENERATOR); - const urlState: VisualizeUrlGeneratorState = { - filters: getQueryService().filterManager.getFilters(), - query: getQueryService().queryString.getQuery(), - timeRange: getQueryService().timefilter.timefilter.getTime(), - indexPatternId: context.indexPatternId, - type: 'histogram', - vis: { - type: 'histogram', - aggs: [{ schema: 'metric', type: 'count', id: '1' }, agg], - }, - }; - return generator.createUrl(urlState); -}; diff --git a/src/plugins/visualize/public/plugin.ts b/src/plugins/visualize/public/plugin.ts index 8d02e08549663..e240e391d6053 100644 --- a/src/plugins/visualize/public/plugin.ts +++ b/src/plugins/visualize/public/plugin.ts @@ -39,18 +39,8 @@ import { DEFAULT_APP_CATEGORIES } from '../../../core/public'; import { SavedObjectsStart } from '../../saved_objects/public'; import { EmbeddableStart } from '../../embeddable/public'; import { DashboardStart } from '../../dashboard/public'; -import { UiActionsSetup, VISUALIZE_FIELD_TRIGGER } from '../../ui_actions/public'; import type { SavedObjectTaggingOssPluginStart } from '../../saved_objects_tagging_oss/public'; -import { - setUISettings, - setApplication, - setIndexPatterns, - setQueryService, - setShareService, - setVisEditorsRegistry, -} from './services'; -import { visualizeFieldAction } from './actions/visualize_field_action'; -import { createVisualizeUrlGenerator } from './url_generator'; +import { setVisEditorsRegistry, setUISettings } from './services'; import { createVisEditorsRegistry, VisEditorsRegistry } from './vis_editors_registry'; export interface VisualizePluginStartDependencies { @@ -71,7 +61,6 @@ export interface VisualizePluginSetupDependencies { urlForwarding: UrlForwardingSetup; data: DataPublicPluginSetup; share?: SharePluginSetup; - uiActions: UiActionsSetup; } export interface VisualizePluginSetup { @@ -96,7 +85,7 @@ export class VisualizePlugin public async setup( core: CoreSetup, - { home, urlForwarding, data, share, uiActions }: VisualizePluginSetupDependencies + { home, urlForwarding, data }: VisualizePluginSetupDependencies ) { const { appMounted, @@ -129,19 +118,8 @@ export class VisualizePlugin this.stopUrlTracking = () => { stopUrlTracker(); }; - if (share) { - share.urlGenerators.registerUrlGenerator( - createVisualizeUrlGenerator(async () => { - const [coreStart] = await core.getStartServices(); - return { - appBasePath: coreStart.application.getUrlForApp('visualize'), - useHashedUrl: coreStart.uiSettings.get('state:storeInSessionStorage'), - }; - }) - ); - } + setUISettings(core.uiSettings); - uiActions.addTriggerAction(VISUALIZE_FIELD_TRIGGER, visualizeFieldAction); core.application.register({ id: 'visualize', @@ -245,12 +223,6 @@ export class VisualizePlugin public start(core: CoreStart, plugins: VisualizePluginStartDependencies) { setVisEditorsRegistry(this.visEditorsRegistry); - setApplication(core.application); - setIndexPatterns(plugins.data.indexPatterns); - setQueryService(plugins.data.query); - if (plugins.share) { - setShareService(plugins.share); - } } stop() { diff --git a/src/plugins/visualize/public/services.ts b/src/plugins/visualize/public/services.ts index 48c9965b4210a..ced651047814b 100644 --- a/src/plugins/visualize/public/services.ts +++ b/src/plugins/visualize/public/services.ts @@ -5,28 +5,13 @@ * compliance with, at your election, the Elastic License or the Server Side * Public License, v 1. */ - -import { ApplicationStart, IUiSettingsClient } from '../../../core/public'; +import { IUiSettingsClient } from '../../../core/public'; import { createGetterSetter } from '../../../plugins/kibana_utils/public'; -import { IndexPatternsContract, DataPublicPluginStart } from '../../../plugins/data/public'; -import { SharePluginStart } from '../../../plugins/share/public'; import { VisEditorsRegistry } from './vis_editors_registry'; export const [getUISettings, setUISettings] = createGetterSetter('UISettings'); -export const [getApplication, setApplication] = createGetterSetter('Application'); - -export const [getShareService, setShareService] = createGetterSetter('Share'); - -export const [getIndexPatterns, setIndexPatterns] = createGetterSetter( - 'IndexPatterns' -); - export const [ getVisEditorsRegistry, setVisEditorsRegistry, ] = createGetterSetter('VisEditorsRegistry'); - -export const [getQueryService, setQueryService] = createGetterSetter< - DataPublicPluginStart['query'] ->('Query'); diff --git a/src/plugins/visualize/public/url_generator.test.ts b/src/plugins/visualize/public/url_generator.test.ts deleted file mode 100644 index 25db806109aa2..0000000000000 --- a/src/plugins/visualize/public/url_generator.test.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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { createVisualizeUrlGenerator } from './url_generator'; -import { esFilters } from '../../data/public'; - -const APP_BASE_PATH: string = 'test/app/visualize'; -const VISUALIZE_ID: string = '13823000-99b9-11ea-9eb6-d9e8adceb647'; -const INDEXPATTERN_ID: string = '13823000-99b9-11ea-9eb6-d9e8adceb647'; - -describe('visualize url generator', () => { - test('creates a link to a new visualization', async () => { - const generator = createVisualizeUrlGenerator(() => - Promise.resolve({ - appBasePath: APP_BASE_PATH, - useHashedUrl: false, - }) - ); - const url = await generator.createUrl!({ indexPatternId: INDEXPATTERN_ID, type: 'table' }); - expect(url).toMatchInlineSnapshot( - `"test/app/visualize#/create?_g=()&_a=()&indexPattern=${INDEXPATTERN_ID}&type=table"` - ); - }); - - test('creates a link with global time range set up', async () => { - const generator = createVisualizeUrlGenerator(() => - Promise.resolve({ - appBasePath: APP_BASE_PATH, - useHashedUrl: false, - }) - ); - const url = await generator.createUrl!({ - timeRange: { to: 'now', from: 'now-15m', mode: 'relative' }, - indexPatternId: INDEXPATTERN_ID, - type: 'table', - }); - expect(url).toMatchInlineSnapshot( - `"test/app/visualize#/create?_g=(time:(from:now-15m,mode:relative,to:now))&_a=()&indexPattern=${INDEXPATTERN_ID}&type=table"` - ); - }); - - test('creates a link with filters, time range, refresh interval and query to a saved visualization', async () => { - const generator = createVisualizeUrlGenerator(() => - Promise.resolve({ - appBasePath: APP_BASE_PATH, - useHashedUrl: false, - indexPatternId: INDEXPATTERN_ID, - type: 'table', - }) - ); - const url = await generator.createUrl!({ - timeRange: { to: 'now', from: 'now-15m', mode: 'relative' }, - refreshInterval: { pause: false, value: 300 }, - visualizationId: VISUALIZE_ID, - filters: [ - { - meta: { - alias: null, - disabled: false, - negate: false, - }, - query: { query: 'q1' }, - }, - { - meta: { - alias: null, - disabled: false, - negate: false, - }, - query: { query: 'q1' }, - $state: { - store: esFilters.FilterStateStore.GLOBAL_STATE, - }, - }, - ], - query: { query: 'q2', language: 'kuery' }, - indexPatternId: INDEXPATTERN_ID, - type: 'table', - }); - expect(url).toMatchInlineSnapshot( - `"test/app/visualize#/edit/${VISUALIZE_ID}?_g=(filters:!(('$state':(store:globalState),meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1))),refreshInterval:(pause:!f,value:300),time:(from:now-15m,mode:relative,to:now))&_a=(filters:!((meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1))),query:(language:kuery,query:q2))&indexPattern=${INDEXPATTERN_ID}&type=table"` - ); - }); -}); diff --git a/src/plugins/visualize/public/url_generator.ts b/src/plugins/visualize/public/url_generator.ts deleted file mode 100644 index 57fa9b2ae4801..0000000000000 --- a/src/plugins/visualize/public/url_generator.ts +++ /dev/null @@ -1,124 +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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { - TimeRange, - Filter, - Query, - esFilters, - QueryState, - RefreshInterval, -} from '../../data/public'; -import { setStateToKbnUrl } from '../../kibana_utils/public'; -import { UrlGeneratorsDefinition } from '../../share/public'; -import { STATE_STORAGE_KEY, GLOBAL_STATE_STORAGE_KEY } from '../common/constants'; - -export const VISUALIZE_APP_URL_GENERATOR = 'VISUALIZE_APP_URL_GENERATOR'; - -export interface VisualizeUrlGeneratorState { - /** - * If given, it will load the given visualization else will load the create a new visualization page. - */ - visualizationId?: string; - /** - * Optionally set the time range in the time picker. - */ - timeRange?: TimeRange; - - /** - * Optional set indexPatternId. - */ - indexPatternId?: string; - - /** - * Optional set visualization type. - */ - type?: string; - - /** - * Optionally set the visualization. - */ - vis?: unknown; - - /** - * Optionally set the refresh interval. - */ - refreshInterval?: RefreshInterval; - - /** - * Optionally apply filers. NOTE: if given and used in conjunction with `dashboardId`, and the - * saved dashboard has filters saved with it, this will _replace_ those filters. - */ - filters?: Filter[]; - /** - * Optionally set a query. NOTE: if given and used in conjunction with `dashboardId`, and the - * saved dashboard has a query saved with it, this will _replace_ that query. - */ - query?: Query; - /** - * If not given, will use the uiSettings configuration for `storeInSessionStorage`. useHash determines - * whether to hash the data in the url to avoid url length issues. - */ - hash?: boolean; -} - -export const createVisualizeUrlGenerator = ( - getStartServices: () => Promise<{ - appBasePath: string; - useHashedUrl: boolean; - }> -): UrlGeneratorsDefinition => ({ - id: VISUALIZE_APP_URL_GENERATOR, - createUrl: async ({ - visualizationId, - filters, - indexPatternId, - query, - refreshInterval, - vis, - type, - timeRange, - hash, - }: VisualizeUrlGeneratorState): Promise => { - const startServices = await getStartServices(); - const useHash = hash ?? startServices.useHashedUrl; - const appBasePath = startServices.appBasePath; - const mode = visualizationId ? `edit/${visualizationId}` : `create`; - - const appState: { - query?: Query; - filters?: Filter[]; - vis?: unknown; - } = {}; - const queryState: QueryState = {}; - - if (query) appState.query = query; - if (filters && filters.length) - appState.filters = filters?.filter((f) => !esFilters.isFilterPinned(f)); - if (vis) appState.vis = vis; - - if (timeRange) queryState.time = timeRange; - if (filters && filters.length) - queryState.filters = filters?.filter((f) => esFilters.isFilterPinned(f)); - if (refreshInterval) queryState.refreshInterval = refreshInterval; - - let url = `${appBasePath}#/${mode}`; - url = setStateToKbnUrl(GLOBAL_STATE_STORAGE_KEY, queryState, { useHash }, url); - url = setStateToKbnUrl(STATE_STORAGE_KEY, appState, { useHash }, url); - - if (indexPatternId) { - url = `${url}&indexPattern=${indexPatternId}`; - } - - if (type) { - url = `${url}&type=${type}`; - } - - return url; - }, -}); diff --git a/test/accessibility/apps/dashboard.ts b/test/accessibility/apps/dashboard.ts index 2a9c5b22380f8..a906219326dcd 100644 --- a/test/accessibility/apps/dashboard.ts +++ b/test/accessibility/apps/dashboard.ts @@ -110,7 +110,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('Exit out of edit mode', async () => { - await PageObjects.dashboard.clickCancelOutOfEditMode(); + await PageObjects.dashboard.clickDiscardChanges(); await a11y.testAppSnapshot(); }); diff --git a/test/accessibility/apps/kibana_overview.ts b/test/accessibility/apps/kibana_overview.ts index eb0b54ad07aa7..c26a042b10e72 100644 --- a/test/accessibility/apps/kibana_overview.ts +++ b/test/accessibility/apps/kibana_overview.ts @@ -16,7 +16,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); before(async () => { - await esArchiver.emptyKibanaIndex(); + await esArchiver.load('empty_kibana'); await PageObjects.common.navigateToApp('kibanaOverview'); }); @@ -25,6 +25,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { useActualUrl: true, }); await PageObjects.home.removeSampleDataSet('flights'); + await esArchiver.unload('empty_kibana'); }); it('Getting started view', async () => { diff --git a/test/api_integration/apis/home/sample_data.ts b/test/api_integration/apis/home/sample_data.ts index ebda93b12dc20..042aff1375267 100644 --- a/test/api_integration/apis/home/sample_data.ts +++ b/test/api_integration/apis/home/sample_data.ts @@ -11,15 +11,11 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const es = getService('es'); const MILLISECOND_IN_WEEK = 1000 * 60 * 60 * 24 * 7; describe('sample data apis', () => { - before(async () => { - await esArchiver.emptyKibanaIndex(); - }); describe('list', () => { it('should return list of sample data sets with installed status', async () => { const resp = await supertest.get(`/api/sample_data`).set('kbn-xsrf', 'kibana').expect(200); diff --git a/test/api_integration/apis/saved_objects/bulk_create.ts b/test/api_integration/apis/saved_objects/bulk_create.ts index d7cdee16214a8..148ed4717a70c 100644 --- a/test/api_integration/apis/saved_objects/bulk_create.ts +++ b/test/api_integration/apis/saved_objects/bulk_create.ts @@ -12,8 +12,8 @@ import { getKibanaVersion } from './lib/saved_objects_test_utils'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('es'); const esArchiver = getService('esArchiver'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); const BULK_REQUESTS = [ { @@ -97,11 +97,10 @@ export default function ({ getService }: FtrProviderContext) { before( async () => // just in case the kibana server has recreated it - await es.indices.delete({ index: '.kibana*' }, { ignore: [404] }) + await esDeleteAllIndices('.kibana') ); - it('should return 200 with errors', async () => { - await new Promise((resolve) => setTimeout(resolve, 2000)); + it('should return 200 with individual responses', async () => await supertest .post('/api/saved_objects/_bulk_create') .send(BULK_REQUESTS) @@ -110,27 +109,38 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.body).to.eql({ saved_objects: [ { - id: BULK_REQUESTS[0].id, - type: BULK_REQUESTS[0].type, - error: { - error: 'Internal Server Error', - message: 'An internal server error occurred', - statusCode: 500, + type: 'visualization', + id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', + updated_at: resp.body.saved_objects[0].updated_at, + version: resp.body.saved_objects[0].version, + attributes: { + title: 'An existing visualization', + }, + references: [], + namespaces: ['default'], + migrationVersion: { + visualization: resp.body.saved_objects[0].migrationVersion.visualization, }, + coreMigrationVersion: KIBANA_VERSION, // updated from 1.2.3 to the latest kibana version }, { - id: BULK_REQUESTS[1].id, - type: BULK_REQUESTS[1].type, - error: { - error: 'Internal Server Error', - message: 'An internal server error occurred', - statusCode: 500, + type: 'dashboard', + id: 'a01b2f57-fcfd-4864-b735-09e28f0d815e', + updated_at: resp.body.saved_objects[1].updated_at, + version: resp.body.saved_objects[1].version, + attributes: { + title: 'A great new dashboard', }, + references: [], + namespaces: ['default'], + migrationVersion: { + dashboard: resp.body.saved_objects[1].migrationVersion.dashboard, + }, + coreMigrationVersion: KIBANA_VERSION, }, ], }); - }); - }); + })); }); }); } diff --git a/test/api_integration/apis/saved_objects/bulk_get.ts b/test/api_integration/apis/saved_objects/bulk_get.ts index b9536843d30c9..9abb784cff786 100644 --- a/test/api_integration/apis/saved_objects/bulk_get.ts +++ b/test/api_integration/apis/saved_objects/bulk_get.ts @@ -12,8 +12,8 @@ import { getKibanaVersion } from './lib/saved_objects_test_utils'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('es'); const esArchiver = getService('esArchiver'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); const BULK_REQUESTS = [ { @@ -108,7 +108,7 @@ export default function ({ getService }: FtrProviderContext) { before( async () => // just in case the kibana server has recreated it - await es.indices.delete({ index: '.kibana*' }, { ignore: [404] }) + await esDeleteAllIndices('.kibana') ); it('should return 200 with individual responses', async () => diff --git a/test/api_integration/apis/saved_objects/bulk_update.ts b/test/api_integration/apis/saved_objects/bulk_update.ts index 2cf3ade406a93..b047b50829051 100644 --- a/test/api_integration/apis/saved_objects/bulk_update.ts +++ b/test/api_integration/apis/saved_objects/bulk_update.ts @@ -12,8 +12,8 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('es'); const esArchiver = getService('esArchiver'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); describe('bulkUpdate', () => { describe('with kibana index', () => { @@ -235,10 +235,10 @@ export default function ({ getService }: FtrProviderContext) { before( async () => // just in case the kibana server has recreated it - await es.indices.delete({ index: '.kibana*' }, { ignore: [404] }) + await esDeleteAllIndices('.kibana') ); - it('should return 200 with errors', async () => { + it('should return generic 404', async () => { const response = await supertest .put(`/api/saved_objects/_bulk_update`) .send([ @@ -267,9 +267,9 @@ export default function ({ getService }: FtrProviderContext) { id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', type: 'visualization', error: { - statusCode: 500, - error: 'Internal Server Error', - message: 'An internal server error occurred', + statusCode: 404, + error: 'Not Found', + message: 'Saved object [visualization/dd7caf20-9efd-11e7-acb3-3dab96693fab] not found', }, }); @@ -277,9 +277,9 @@ export default function ({ getService }: FtrProviderContext) { id: 'be3733a0-9efe-11e7-acb3-3dab96693fab', type: 'dashboard', error: { - statusCode: 500, - error: 'Internal Server Error', - message: 'An internal server error occurred', + statusCode: 404, + error: 'Not Found', + message: 'Saved object [dashboard/be3733a0-9efe-11e7-acb3-3dab96693fab] not found', }, }); }); diff --git a/test/api_integration/apis/saved_objects/create.ts b/test/api_integration/apis/saved_objects/create.ts index 833cb127d0023..e789d5719d7ff 100644 --- a/test/api_integration/apis/saved_objects/create.ts +++ b/test/api_integration/apis/saved_objects/create.ts @@ -14,6 +14,7 @@ export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const es = getService('es'); const esArchiver = getService('esArchiver'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); describe('create', () => { let KIBANA_VERSION: string; @@ -82,10 +83,10 @@ export default function ({ getService }: FtrProviderContext) { before( async () => // just in case the kibana server has recreated it - await es.indices.delete({ index: '.kibana*' }, { ignore: [404] }) + await esDeleteAllIndices('.kibana') ); - it('should return 500 and not auto-create saved objects index', async () => { + it('should return 200 and create kibana index', async () => { await supertest .post(`/api/saved_objects/visualization`) .send({ @@ -93,16 +94,50 @@ export default function ({ getService }: FtrProviderContext) { title: 'My favorite vis', }, }) - .expect(500) + .expect(200) .then((resp) => { + // loose uuid validation + expect(resp.body) + .to.have.property('id') + .match(/^[0-9a-f-]{36}$/); + + // loose ISO8601 UTC time with milliseconds validation + expect(resp.body) + .to.have.property('updated_at') + .match(/^[\d-]{10}T[\d:\.]{12}Z$/); + expect(resp.body).to.eql({ - error: 'Internal Server Error', - message: 'An internal server error occurred.', - statusCode: 500, + id: resp.body.id, + type: 'visualization', + migrationVersion: resp.body.migrationVersion, + coreMigrationVersion: KIBANA_VERSION, + updated_at: resp.body.updated_at, + version: resp.body.version, + attributes: { + title: 'My favorite vis', + }, + references: [], + namespaces: ['default'], }); + expect(resp.body.migrationVersion).to.be.ok(); }); - expect((await es.indices.exists({ index: '.kibana' })).body).to.be(false); + expect((await es.indices.exists({ index: '.kibana' })).body).to.be(true); + }); + + it('result should have the latest coreMigrationVersion', async () => { + await supertest + .post(`/api/saved_objects/visualization`) + .send({ + attributes: { + title: 'My favorite vis', + }, + coreMigrationVersion: '1.2.3', + }) + .expect(200) + .then((resp) => { + expect(resp.body.coreMigrationVersion).to.eql(KIBANA_VERSION); + }); }); }); }); diff --git a/test/api_integration/apis/saved_objects/delete.ts b/test/api_integration/apis/saved_objects/delete.ts index d2dd4454bdf1e..0b43734bb7000 100644 --- a/test/api_integration/apis/saved_objects/delete.ts +++ b/test/api_integration/apis/saved_objects/delete.ts @@ -11,8 +11,8 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('es'); const esArchiver = getService('esArchiver'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); describe('delete', () => { describe('with kibana index', () => { @@ -44,7 +44,7 @@ export default function ({ getService }: FtrProviderContext) { before( async () => // just in case the kibana server has recreated it - await es.indices.delete({ index: '.kibana*' }, { ignore: [404] }) + await esDeleteAllIndices('.kibana') ); it('returns generic 404 when kibana index is missing', async () => diff --git a/test/api_integration/apis/saved_objects/export.ts b/test/api_integration/apis/saved_objects/export.ts index c0d5430070951..2c2da82fb6734 100644 --- a/test/api_integration/apis/saved_objects/export.ts +++ b/test/api_integration/apis/saved_objects/export.ts @@ -15,8 +15,8 @@ function ndjsonToObject(input: string) { } export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('es'); const esArchiver = getService('esArchiver'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); describe('export', () => { let KIBANA_VERSION: string; @@ -534,7 +534,7 @@ export default function ({ getService }: FtrProviderContext) { before( async () => // just in case the kibana server has recreated it - await es.indices.delete({ index: '.kibana*' }, { ignore: [404] }) + await esDeleteAllIndices('.kibana') ); it('should return empty response', async () => { diff --git a/test/api_integration/apis/saved_objects/find.ts b/test/api_integration/apis/saved_objects/find.ts index 5f549dc6c5780..db53bcf154513 100644 --- a/test/api_integration/apis/saved_objects/find.ts +++ b/test/api_integration/apis/saved_objects/find.ts @@ -13,8 +13,8 @@ import { getKibanaVersion } from './lib/saved_objects_test_utils'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('es'); const esArchiver = getService('esArchiver'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); describe('find', () => { let KIBANA_VERSION: string; @@ -40,7 +40,7 @@ export default function ({ getService }: FtrProviderContext) { { type: 'visualization', id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', - version: 'WzE4LDJd', + version: 'WzIsMV0=', attributes: { title: 'Count of requests', }, @@ -137,7 +137,7 @@ export default function ({ getService }: FtrProviderContext) { { type: 'visualization', id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', - version: 'WzE4LDJd', + version: 'WzIsMV0=', attributes: { title: 'Count of requests', }, @@ -174,7 +174,7 @@ export default function ({ getService }: FtrProviderContext) { { type: 'visualization', id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', - version: 'WzE4LDJd', + version: 'WzIsMV0=', attributes: { title: 'Count of requests', }, @@ -209,7 +209,7 @@ export default function ({ getService }: FtrProviderContext) { score: 0, type: 'visualization', updated_at: '2017-09-21T18:51:23.794Z', - version: 'WzIyLDJd', + version: 'WzYsMV0=', }, ], }); @@ -256,7 +256,7 @@ export default function ({ getService }: FtrProviderContext) { migrationVersion: resp.body.saved_objects[0].migrationVersion, coreMigrationVersion: KIBANA_VERSION, updated_at: '2017-09-21T18:51:23.794Z', - version: 'WzE4LDJd', + version: 'WzIsMV0=', }, ], }); @@ -426,11 +426,11 @@ export default function ({ getService }: FtrProviderContext) { })); }); - describe('without kibana index', () => { + describe.skip('without kibana index', () => { before( async () => // just in case the kibana server has recreated it - await es.indices.delete({ index: '.kibana*' }, { ignore: [404] }) + await esDeleteAllIndices('.kibana') ); it('should return 200 with empty response', async () => diff --git a/test/api_integration/apis/saved_objects/get.ts b/test/api_integration/apis/saved_objects/get.ts index 9bb6e32004c81..b8ce3835b1ffa 100644 --- a/test/api_integration/apis/saved_objects/get.ts +++ b/test/api_integration/apis/saved_objects/get.ts @@ -12,8 +12,8 @@ import { getKibanaVersion } from './lib/saved_objects_test_utils'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('es'); const esArchiver = getService('esArchiver'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); describe('get', () => { let KIBANA_VERSION: string; @@ -78,7 +78,7 @@ export default function ({ getService }: FtrProviderContext) { before( async () => // just in case the kibana server has recreated it - await es.indices.delete({ index: '.kibana*' }, { ignore: [404] }) + await esDeleteAllIndices('.kibana') ); it('should return basic 404 without mentioning index', async () => diff --git a/test/api_integration/apis/saved_objects/migrations.ts b/test/api_integration/apis/saved_objects/migrations.ts index 0b06b675f60c0..e5aa29f836ddf 100644 --- a/test/api_integration/apis/saved_objects/migrations.ts +++ b/test/api_integration/apis/saved_objects/migrations.ts @@ -63,9 +63,10 @@ function getLogMock() { } export default ({ getService }: FtrProviderContext) => { const esClient = getService('es'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); describe('Kibana index migration', () => { - before(() => esClient.indices.delete({ index: '.migrate-*' })); + before(() => esDeleteAllIndices('.migrate-*')); it('Migrates an existing index that has never been migrated before', async () => { const index = '.migration-a'; @@ -99,7 +100,7 @@ export default ({ getService }: FtrProviderContext) => { }, ]; - await createIndex({ esClient, index }); + await createIndex({ esClient, index, esDeleteAllIndices }); await createDocs({ esClient, index, docs: originalDocs }); // Test that unrelated index templates are unaffected @@ -233,7 +234,7 @@ export default ({ getService }: FtrProviderContext) => { }, ]; - await createIndex({ esClient, index }); + await createIndex({ esClient, index, esDeleteAllIndices }); await createDocs({ esClient, index, docs: originalDocs }); await migrateIndex({ esClient, index, savedObjectTypes, mappingProperties }); @@ -347,7 +348,7 @@ export default ({ getService }: FtrProviderContext) => { }, ]; - await createIndex({ esClient, index }); + await createIndex({ esClient, index, esDeleteAllIndices }); await createDocs({ esClient, index, docs: originalDocs }); const result = await Promise.all([ @@ -445,7 +446,7 @@ export default ({ getService }: FtrProviderContext) => { BAZ_TYPE, // must be registered for reference transforms to be applied to objects of this type ]; - await createIndex({ esClient, index }); + await createIndex({ esClient, index, esDeleteAllIndices }); await createDocs({ esClient, index, docs: originalDocs }); await migrateIndex({ @@ -554,8 +555,17 @@ export default ({ getService }: FtrProviderContext) => { }); }; -async function createIndex({ esClient, index }: { esClient: ElasticsearchClient; index: string }) { - await esClient.indices.delete({ index: `${index}*` }, { ignore: [404] }); +async function createIndex({ + esClient, + index, + esDeleteAllIndices, +}: { + esClient: ElasticsearchClient; + index: string; + esDeleteAllIndices: (pattern: string) => Promise; +}) { + await esDeleteAllIndices(`${index}*`); + const properties = { type: { type: 'keyword' }, foo: { properties: { name: { type: 'keyword' } } }, diff --git a/test/api_integration/apis/saved_objects/resolve.ts b/test/api_integration/apis/saved_objects/resolve.ts index fb8baef465f38..f6b4fb3b2cb2a 100644 --- a/test/api_integration/apis/saved_objects/resolve.ts +++ b/test/api_integration/apis/saved_objects/resolve.ts @@ -12,8 +12,8 @@ import { getKibanaVersion } from './lib/saved_objects_test_utils'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('es'); const esArchiver = getService('esArchiver'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); describe('resolve', () => { let KIBANA_VERSION: string; @@ -81,7 +81,7 @@ export default function ({ getService }: FtrProviderContext) { before( async () => // just in case the kibana server has recreated it - await es.indices.delete({ index: '.kibana' }, { ignore: [404] }) + await esDeleteAllIndices('.kibana') ); it('should return basic 404 without mentioning index', async () => diff --git a/test/api_integration/apis/saved_objects/resolve_import_errors.ts b/test/api_integration/apis/saved_objects/resolve_import_errors.ts index 042741476bb8e..3686c46b229b1 100644 --- a/test/api_integration/apis/saved_objects/resolve_import_errors.ts +++ b/test/api_integration/apis/saved_objects/resolve_import_errors.ts @@ -13,7 +13,6 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - const es = getService('legacyEs'); describe('resolve_import_errors', () => { // mock success results including metadata @@ -35,14 +34,7 @@ export default function ({ getService }: FtrProviderContext) { describe('without kibana index', () => { // Cleanup data that got created in import - before( - async () => - // just in case the kibana server has recreated it - await es.indices.delete({ - index: '.kibana*', - ignore: [404], - }) - ); + after(() => esArchiver.unload('saved_objects/basic')); it('should return 200 and import nothing when empty parameters are passed in', async () => { await supertest @@ -59,7 +51,7 @@ export default function ({ getService }: FtrProviderContext) { }); }); - it('should return 200 with internal server errors', async () => { + it('should return 200 and import everything when overwrite parameters contains all objects', async () => { await supertest .post('/api/saved_objects/_resolve_import_errors') .field( @@ -86,42 +78,12 @@ export default function ({ getService }: FtrProviderContext) { .expect(200) .then((resp) => { expect(resp.body).to.eql({ - successCount: 0, - success: false, - errors: [ - { - ...indexPattern, - ...{ title: indexPattern.meta.title }, - overwrite: true, - error: { - statusCode: 500, - error: 'Internal Server Error', - message: 'An internal server error occurred', - type: 'unknown', - }, - }, - { - ...visualization, - ...{ title: visualization.meta.title }, - overwrite: true, - error: { - statusCode: 500, - error: 'Internal Server Error', - message: 'An internal server error occurred', - type: 'unknown', - }, - }, - { - ...dashboard, - ...{ title: dashboard.meta.title }, - overwrite: true, - error: { - statusCode: 500, - error: 'Internal Server Error', - message: 'An internal server error occurred', - type: 'unknown', - }, - }, + success: true, + successCount: 3, + successResults: [ + { ...indexPattern, overwrite: true }, + { ...visualization, overwrite: true }, + { ...dashboard, overwrite: true }, ], warnings: [], }); diff --git a/test/api_integration/apis/saved_objects/update.ts b/test/api_integration/apis/saved_objects/update.ts index da7285a430fdd..ec882b0bb56b4 100644 --- a/test/api_integration/apis/saved_objects/update.ts +++ b/test/api_integration/apis/saved_objects/update.ts @@ -11,8 +11,8 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('es'); const esArchiver = getService('esArchiver'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); describe('update', () => { describe('with kibana index', () => { @@ -121,10 +121,10 @@ export default function ({ getService }: FtrProviderContext) { before( async () => // just in case the kibana server has recreated it - await es.indices.delete({ index: '.kibana*' }, { ignore: [404] }) + await esDeleteAllIndices('.kibana') ); - it('should return 500', async () => + it('should return generic 404', async () => await supertest .put(`/api/saved_objects/visualization/dd7caf20-9efd-11e7-acb3-3dab96693fab`) .send({ @@ -132,12 +132,13 @@ export default function ({ getService }: FtrProviderContext) { title: 'My second favorite vis', }, }) - .expect(500) + .expect(404) .then((resp) => { expect(resp.body).eql({ - statusCode: 500, - error: 'Internal Server Error', - message: 'An internal server error occurred.', + statusCode: 404, + error: 'Not Found', + message: + 'Saved object [visualization/dd7caf20-9efd-11e7-acb3-3dab96693fab] not found', }); })); }); diff --git a/test/api_integration/apis/saved_objects_management/find.ts b/test/api_integration/apis/saved_objects_management/find.ts index 8453b542903a4..911d3b56e1fee 100644 --- a/test/api_integration/apis/saved_objects_management/find.ts +++ b/test/api_integration/apis/saved_objects_management/find.ts @@ -11,7 +11,7 @@ import { Response } from 'supertest'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { - const es = getService('es'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); @@ -42,7 +42,7 @@ export default function ({ getService }: FtrProviderContext) { { type: 'visualization', id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab', - version: 'WzE4LDJd', + version: 'WzIsMV0=', attributes: { title: 'Count of requests', }, @@ -184,7 +184,7 @@ export default function ({ getService }: FtrProviderContext) { before( async () => // just in case the kibana server has recreated it - await es.indices.delete({ index: '.kibana*' }, { ignore: [404] }) + await esDeleteAllIndices('.kibana') ); it('should return 200 with empty response', async () => diff --git a/test/api_integration/apis/saved_objects_management/get.ts b/test/api_integration/apis/saved_objects_management/get.ts index 70e1faa9fd22b..c9bd0a17d31b1 100644 --- a/test/api_integration/apis/saved_objects_management/get.ts +++ b/test/api_integration/apis/saved_objects_management/get.ts @@ -11,7 +11,7 @@ import { Response } from 'supertest'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { - const es = getService('es'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); @@ -45,7 +45,7 @@ export default function ({ getService }: FtrProviderContext) { before( async () => // just in case the kibana server has recreated it - await es.indices.delete({ index: '.kibana*' }, { ignore: [404] }) + await esDeleteAllIndices('.kibana') ); it('should return 404 for object that no longer exists', async () => diff --git a/test/api_integration/apis/search/search.ts b/test/api_integration/apis/search/search.ts index e43c449309306..155705f81fa8a 100644 --- a/test/api_integration/apis/search/search.ts +++ b/test/api_integration/apis/search/search.ts @@ -17,7 +17,6 @@ export default function ({ getService }: FtrProviderContext) { describe('search', () => { before(async () => { - await esArchiver.emptyKibanaIndex(); await esArchiver.loadIfNeeded('../../../functional/fixtures/es_archiver/logstash_functional'); }); diff --git a/test/api_integration/apis/telemetry/opt_in.ts b/test/api_integration/apis/telemetry/opt_in.ts index ba5f46c38211f..f03b33e61965e 100644 --- a/test/api_integration/apis/telemetry/opt_in.ts +++ b/test/api_integration/apis/telemetry/opt_in.ts @@ -14,13 +14,10 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function optInTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const kibanaServer = getService('kibanaServer'); - const esArchiver = getService('esArchiver'); - describe('/api/telemetry/v2/optIn API', () => { let defaultAttributes: TelemetrySavedObjectAttributes; let kibanaVersion: any; before(async () => { - await esArchiver.emptyKibanaIndex(); const kibanaVersionAccessor = kibanaServer.version; kibanaVersion = await kibanaVersionAccessor.get(); defaultAttributes = diff --git a/test/api_integration/apis/telemetry/telemetry_local.ts b/test/api_integration/apis/telemetry/telemetry_local.ts index 650846015a4a2..25d29a807bdad 100644 --- a/test/api_integration/apis/telemetry/telemetry_local.ts +++ b/test/api_integration/apis/telemetry/telemetry_local.ts @@ -177,7 +177,6 @@ export default function ({ getService }: FtrProviderContext) { describe('basic behaviour', () => { let savedObjectIds: string[] = []; before('create application usage entries', async () => { - await esArchiver.emptyKibanaIndex(); savedObjectIds = await Promise.all([ createSavedObject(), createSavedObject('appView1'), diff --git a/test/api_integration/apis/ui_counters/ui_counters.ts b/test/api_integration/apis/ui_counters/ui_counters.ts index 8d60c79c9698d..1cf16fe433bf9 100644 --- a/test/api_integration/apis/ui_counters/ui_counters.ts +++ b/test/api_integration/apis/ui_counters/ui_counters.ts @@ -13,7 +13,6 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const es = getService('es'); const createUiCounterEvent = (eventName: string, type: UiCounterMetricType, count = 1) => ({ @@ -24,10 +23,6 @@ export default function ({ getService }: FtrProviderContext) { }); describe('UI Counters API', () => { - before(async () => { - await esArchiver.emptyKibanaIndex(); - }); - const dayDate = moment().format('DDMMYYYY'); it('stores ui counter events in savedObjects', async () => { diff --git a/test/api_integration/apis/ui_metric/ui_metric.ts b/test/api_integration/apis/ui_metric/ui_metric.ts index e3b3b2ec4c542..d330cb037d1a1 100644 --- a/test/api_integration/apis/ui_metric/ui_metric.ts +++ b/test/api_integration/apis/ui_metric/ui_metric.ts @@ -13,7 +13,6 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const es = getService('es'); const createStatsMetric = ( @@ -35,10 +34,6 @@ export default function ({ getService }: FtrProviderContext) { }); describe('ui_metric savedObject data', () => { - before(async () => { - await esArchiver.emptyKibanaIndex(); - }); - it('increments the count field in the document defined by the {app}/{action_type} path', async () => { const reportManager = new ReportManager(); const uiStatsMetric = createStatsMetric('myEvent'); diff --git a/test/common/config.js b/test/common/config.js index 8a42e6c87b214..b6d12444b7017 100644 --- a/test/common/config.js +++ b/test/common/config.js @@ -61,6 +61,8 @@ export default function () { ...(!!process.env.CODE_COVERAGE ? [`--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'coverage')}`] : []), + // Disable v2 migrations in tests for now + '--migrations.enableV2=false', ], }, services, diff --git a/test/common/services/es_archiver.ts b/test/common/services/es_archiver.ts index e1b85ddf8bc9d..e6d4a8a56af29 100644 --- a/test/common/services/es_archiver.ts +++ b/test/common/services/es_archiver.ts @@ -14,7 +14,7 @@ import * as KibanaServer from './kibana_server'; export function EsArchiverProvider({ getService }: FtrProviderContext): EsArchiver { const config = getService('config'); - const client = getService('legacyEs'); + const client = getService('es'); const log = getService('log'); const kibanaServer = getService('kibanaServer'); const retry = getService('retry'); diff --git a/test/common/services/es_delete_all_indices.ts b/test/common/services/es_delete_all_indices.ts new file mode 100644 index 0000000000000..fda28aa41420f --- /dev/null +++ b/test/common/services/es_delete_all_indices.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { FtrProviderContext } from '../ftr_provider_context'; + +export function EsDeleteAllIndicesProvider({ getService }: FtrProviderContext) { + const es = getService('es'); + const log = getService('log'); + + async function deleteConcreteIndices(indices: string[]) { + try { + await es.indices.delete({ + index: indices, + ignore_unavailable: true, + }); + } catch (error) { + log.debug(`Failed to delete indices [${indices}], but ignoring error: ${error.message}`); + } + } + + return async (patterns: string | string[]) => { + for (const pattern of [patterns].flat()) { + for (let attempt = 1; ; attempt++) { + if (attempt > 5) { + throw new Error(`Failed to delete all indices with pattern [${pattern}]`); + } + + // resolve pattern to concrete index names + const resp = await es.indices.getAlias( + { + index: pattern, + }, + { + ignore: [404], + } + ); + const indices = Object.keys(resp.body) as string[]; + + // if no indexes exits then we're done with this pattern + if (resp.statusCode === 404 || !indices.length) { + if (attempt === 1) { + log.debug(`No indices to delete [pattern=${pattern}]`); + } + break; + } + + log.debug( + `Deleting indices [attempt=${attempt}] [pattern=${pattern}] "${indices.join('", "')}"` + ); + + // delete the concrete indexes we found and try again until this pattern resolves to no indexes + await deleteConcreteIndices(indices); + } + } + }; +} diff --git a/test/common/services/index.ts b/test/common/services/index.ts index 9b3c5a9e28a2b..110e4ff7d01e8 100644 --- a/test/common/services/index.ts +++ b/test/common/services/index.ts @@ -14,6 +14,7 @@ import { KibanaServerProvider } from './kibana_server'; import { RetryProvider } from './retry'; import { RandomnessProvider } from './randomness'; import { SecurityServiceProvider } from './security'; +import { EsDeleteAllIndicesProvider } from './es_delete_all_indices'; export const services = { deployment: DeploymentProvider, @@ -24,4 +25,5 @@ export const services = { retry: RetryProvider, randomness: RandomnessProvider, security: SecurityServiceProvider, + esDeleteAllIndices: EsDeleteAllIndicesProvider, }; diff --git a/test/common/services/kibana_server/extend_es_archiver.js b/test/common/services/kibana_server/extend_es_archiver.js index 1d76bc4473767..5390b43a87187 100644 --- a/test/common/services/kibana_server/extend_es_archiver.js +++ b/test/common/services/kibana_server/extend_es_archiver.js @@ -6,7 +6,7 @@ * Public License, v 1. */ -const ES_ARCHIVER_LOAD_METHODS = ['load', 'loadIfNeeded', 'unload', 'emptyKibanaIndex']; +const ES_ARCHIVER_LOAD_METHODS = ['load', 'loadIfNeeded', 'unload']; const KIBANA_INDEX = '.kibana'; export function extendEsArchiver({ esArchiver, kibanaServer, retry, defaults }) { @@ -25,7 +25,7 @@ export function extendEsArchiver({ esArchiver, kibanaServer, retry, defaults }) const statsKeys = Object.keys(stats); const kibanaKeys = statsKeys.filter( // this also matches stats keys like '.kibana_1' and '.kibana_2,.kibana_1' - (key) => key.includes(KIBANA_INDEX) && stats[key].created + (key) => key.includes(KIBANA_INDEX) && (stats[key].created || stats[key].deleted) ); // if the kibana index was created by the esArchiver then update the uiSettings diff --git a/test/examples/expressions_explorer/expressions.ts b/test/examples/expressions_explorer/expressions.ts index 7261564e6db38..141a037bd8696 100644 --- a/test/examples/expressions_explorer/expressions.ts +++ b/test/examples/expressions_explorer/expressions.ts @@ -7,13 +7,14 @@ */ import expect from '@kbn/expect'; - +import testSubjSelector from '@kbn/test-subj-selector'; import { PluginFunctionalProviderContext } from 'test/plugin_functional/services'; // eslint-disable-next-line import/no-default-export export default function ({ getService }: PluginFunctionalProviderContext) { const testSubjects = getService('testSubjects'); const retry = getService('retry'); + const find = getService('find'); const browser = getService('browser'); describe('', () => { @@ -33,8 +34,23 @@ export default function ({ getService }: PluginFunctionalProviderContext) { }); }); + it('updates the variable', async () => { + const selector = `${testSubjSelector('expressionsVariablesTest')} ${testSubjSelector( + 'testExpressionButton' + )}`; + await find.clickByCssSelector(selector); + await retry.try(async () => { + const el = await find.byCssSelector(selector); + const style = await el.getAttribute('style'); + expect(style).to.contain('red'); + }); + }); + it('emits an action and navigates', async () => { - await testSubjects.click('testExpressionButton'); + const selector = `${testSubjSelector('expressionsActionsTest')} ${testSubjSelector( + 'testExpressionButton' + )}`; + await find.clickByCssSelector(selector); await retry.try(async () => { const text = await browser.getCurrentUrl(); expect(text).to.be('https://www.google.com/?gws_rd=ssl'); diff --git a/test/functional/apps/dashboard/dashboard_filtering.ts b/test/functional/apps/dashboard/dashboard_filtering.ts index 1dd84460314b9..cef3b11ab2cb0 100644 --- a/test/functional/apps/dashboard/dashboard_filtering.ts +++ b/test/functional/apps/dashboard/dashboard_filtering.ts @@ -31,6 +31,27 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('dashboard filtering', function () { this.tags('includeFirefox'); + const populateDashboard = async () => { + await PageObjects.dashboard.clickNewDashboard(); + await PageObjects.timePicker.setDefaultDataRange(); + await dashboardAddPanel.addEveryVisualization('"Filter Bytes Test"'); + await dashboardAddPanel.addEverySavedSearch('"Filter Bytes Test"'); + + await dashboardAddPanel.closeAddPanel(); + }; + + const addFilterAndRefresh = async () => { + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.waitForRenderComplete(); + await filterBar.addFilter('bytes', 'is', '12345678'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.waitForRenderComplete(); + // first round of requests sometimes times out, refresh all visualizations to fetch again + await queryBar.clickQuerySubmitButton(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.waitForRenderComplete(); + }; + before(async () => { await esArchiver.load('dashboard/current/kibana'); await security.testUser.setRoles(['kibana_admin', 'test_logstash_reader', 'animals']); @@ -48,22 +69,12 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('adding a filter that excludes all data', () => { before(async () => { - await PageObjects.dashboard.clickNewDashboard(); - await PageObjects.timePicker.setDefaultDataRange(); - await dashboardAddPanel.addEveryVisualization('"Filter Bytes Test"'); - await dashboardAddPanel.addEverySavedSearch('"Filter Bytes Test"'); - - await dashboardAddPanel.closeAddPanel(); + await populateDashboard(); + await addFilterAndRefresh(); + }); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.dashboard.waitForRenderComplete(); - await filterBar.addFilter('bytes', 'is', '12345678'); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.dashboard.waitForRenderComplete(); - // first round of requests sometimes times out, refresh all visualizations to fetch again - await queryBar.clickQuerySubmitButton(); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.dashboard.waitForRenderComplete(); + after(async () => { + await PageObjects.dashboard.gotoDashboardLandingPage(); }); it('filters on pie charts', async () => { @@ -118,6 +129,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('using a pinned filter that excludes all data', () => { before(async () => { + // Functional tests clear session storage after each suite, so it is important to repopulate unsaved panels + await populateDashboard(); + await addFilterAndRefresh(); + await filterBar.toggleFilterPinned('bytes'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.dashboard.waitForRenderComplete(); @@ -125,6 +140,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { after(async () => { await filterBar.toggleFilterPinned('bytes'); + await PageObjects.dashboard.gotoDashboardLandingPage(); }); it('filters on pie charts', async () => { @@ -175,6 +191,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('disabling a filter unfilters the data on', function () { before(async () => { + // Functional tests clear session storage after each suite, so it is important to repopulate unsaved panels + await populateDashboard(); + await addFilterAndRefresh(); + await filterBar.toggleFilterEnabled('bytes'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.dashboard.waitForRenderComplete(); diff --git a/test/functional/apps/dashboard/dashboard_state.ts b/test/functional/apps/dashboard/dashboard_state.ts index 88823baf32a07..3c9b96d7277ef 100644 --- a/test/functional/apps/dashboard/dashboard_state.ts +++ b/test/functional/apps/dashboard/dashboard_state.ts @@ -20,6 +20,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'discover', 'tileMap', 'visChart', + 'share', 'timePicker', ]); const testSubjects = getService('testSubjects'); @@ -127,8 +128,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('Saved search with column changes will not update when the saved object changes', async () => { - await PageObjects.discover.removeHeaderColumn('bytes'); await PageObjects.dashboard.switchToEditMode(); + await PageObjects.discover.removeHeaderColumn('bytes'); await PageObjects.dashboard.saveDashboard('Has local edits'); await PageObjects.header.clickDiscover(); @@ -191,6 +192,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(changedTileMapData.length).to.not.equal(tileMapData.length); }); + const getUrlFromShare = async () => { + await PageObjects.share.clickShareTopNavButton(); + const sharedUrl = await PageObjects.share.getSharedUrl(); + await PageObjects.share.clickShareTopNavButton(); + return sharedUrl; + }; + describe('Directly modifying url updates dashboard state', () => { it('for query parameter', async function () { await PageObjects.dashboard.gotoDashboardLandingPage(); @@ -209,7 +217,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('for panel size parameters', async function () { await dashboardAddPanel.addVisualization(PIE_CHART_VIS_NAME); - const currentUrl = await browser.getCurrentUrl(); + const currentUrl = await getUrlFromShare(); const currentPanelDimensions = await PageObjects.dashboard.getPanelDimensions(); const newUrl = currentUrl.replace( `w:${DEFAULT_PANEL_WIDTH}`, @@ -235,7 +243,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('when removing a panel', async function () { - const currentUrl = await browser.getCurrentUrl(); + await PageObjects.dashboard.waitForRenderComplete(); + const currentUrl = await getUrlFromShare(); const newUrl = currentUrl.replace(/panels:\!\(.*\),query/, 'panels:!(),query'); await browser.get(newUrl.toString(), false); @@ -253,7 +262,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { `[data-title="${PIE_CHART_VIS_NAME}"]` ); await PageObjects.visChart.selectNewLegendColorChoice('#F9D9F9'); - const currentUrl = await browser.getCurrentUrl(); + const currentUrl = await getUrlFromShare(); const newUrl = currentUrl.replace('F9D9F9', 'FFFFFF'); await browser.get(newUrl.toString(), false); await PageObjects.header.waitUntilLoadingHasFinished(); @@ -279,13 +288,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('resets a pie slice color to the original when removed', async function () { - const currentUrl = await browser.getCurrentUrl(); - const newUrl = currentUrl.replace('vis:(colors:(%2780,000%27:%23FFFFFF))', ''); + const currentUrl = await getUrlFromShare(); + const newUrl = currentUrl.replace(`vis:(colors:('80,000':%23FFFFFF))`, ''); await browser.get(newUrl.toString(), false); await PageObjects.header.waitUntilLoadingHasFinished(); await retry.try(async () => { - const pieSliceStyle = await pieChart.getPieSliceStyle('80,000'); + const pieSliceStyle = await pieChart.getPieSliceStyle(`80,000`); // The default green color that was stored with the visualization before any dashboard overrides. expect(pieSliceStyle.indexOf('rgb(87, 193, 123)')).to.be.greaterThan(0); }); diff --git a/test/functional/apps/dashboard/dashboard_unsaved_listing.ts b/test/functional/apps/dashboard/dashboard_unsaved_listing.ts new file mode 100644 index 0000000000000..19e532f0af213 --- /dev/null +++ b/test/functional/apps/dashboard/dashboard_unsaved_listing.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['dashboard', 'header', 'visualize', 'settings', 'common']); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const dashboardAddPanel = getService('dashboardAddPanel'); + const dashboardPanelActions = getService('dashboardPanelActions'); + + let existingDashboardPanelCount = 0; + const dashboardTitle = 'few panels'; + const unsavedDashboardTitle = 'New Dashboard'; + const newDashboartTitle = 'A Wild Dashboard'; + + describe('dashboard unsaved listing', () => { + const addSomePanels = async () => { + // add an area chart by value + await dashboardAddPanel.clickCreateNewLink(); + await PageObjects.visualize.clickAggBasedVisualizations(); + await PageObjects.visualize.clickAreaChart(); + await PageObjects.visualize.clickNewSearch(); + await PageObjects.visualize.saveVisualizationAndReturn(); + + // add a metric by reference + await dashboardAddPanel.addVisualization('Rendering-Test: metric'); + }; + + before(async () => { + await esArchiver.load('dashboard/current/kibana'); + await kibanaServer.uiSettings.replace({ + defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', + }); + await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.preserveCrossAppState(); + }); + + it('lists unsaved changes to existing dashboards', async () => { + await PageObjects.dashboard.loadSavedDashboard(dashboardTitle); + await PageObjects.dashboard.switchToEditMode(); + await addSomePanels(); + existingDashboardPanelCount = await PageObjects.dashboard.getPanelCount(); + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.expectUnsavedChangesListingExists(dashboardTitle); + }); + + it('restores unsaved changes to existing dashboards', async () => { + await PageObjects.dashboard.clickUnsavedChangesContinueEditing(dashboardTitle); + await PageObjects.header.waitUntilLoadingHasFinished(); + const currentPanelCount = await PageObjects.dashboard.getPanelCount(); + expect(currentPanelCount).to.eql(existingDashboardPanelCount); + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.header.waitUntilLoadingHasFinished(); + }); + + it('lists unsaved changes to new dashboards', async () => { + await PageObjects.dashboard.clickNewDashboard(); + await addSomePanels(); + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.expectUnsavedChangesListingExists(unsavedDashboardTitle); + }); + + it('restores unsaved changes to new dashboards', async () => { + await PageObjects.dashboard.clickUnsavedChangesContinueEditing(unsavedDashboardTitle); + await PageObjects.header.waitUntilLoadingHasFinished(); + expect(await PageObjects.dashboard.getPanelCount()).to.eql(2); + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.header.waitUntilLoadingHasFinished(); + }); + + it('shows a warning on create new, and restores panels if continue is selected', async () => { + await PageObjects.dashboard.clickNewDashboardExpectWarning(true); + await PageObjects.header.waitUntilLoadingHasFinished(); + expect(await PageObjects.dashboard.getPanelCount()).to.eql(2); + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.header.waitUntilLoadingHasFinished(); + }); + + it('shows a warning on create new, and clears unsaved panels if discard is selected', async () => { + await PageObjects.dashboard.clickNewDashboardExpectWarning(); + await PageObjects.header.waitUntilLoadingHasFinished(); + expect(await PageObjects.dashboard.getPanelCount()).to.eql(0); + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.header.waitUntilLoadingHasFinished(); + }); + + it('does not show unsaved changes on new dashboard when no panels have been added', async () => { + await PageObjects.dashboard.expectUnsavedChangesDoesNotExist(unsavedDashboardTitle); + }); + + it('can discard unsaved changes using the discard link', async () => { + await PageObjects.dashboard.clickUnsavedChangesDiscard(dashboardTitle); + await PageObjects.dashboard.expectUnsavedChangesDoesNotExist(dashboardTitle); + await PageObjects.dashboard.loadSavedDashboard(dashboardTitle); + await PageObjects.dashboard.switchToEditMode(); + const currentPanelCount = await PageObjects.dashboard.getPanelCount(); + expect(currentPanelCount).to.eql(existingDashboardPanelCount - 2); + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.header.waitUntilLoadingHasFinished(); + }); + + it('loses unsaved changes to new dashboard upon saving', async () => { + await PageObjects.dashboard.clickNewDashboard(); + await addSomePanels(); + + // ensure that the unsaved listing exists first + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.clickUnsavedChangesContinueEditing(unsavedDashboardTitle); + await PageObjects.header.waitUntilLoadingHasFinished(); + + // Save the dashboard, and check that it now does not exist + await PageObjects.dashboard.saveDashboard(newDashboartTitle); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.dashboard.expectUnsavedChangesDoesNotExist(unsavedDashboardTitle); + }); + + it('does not list unsaved changes when unsaved version of the dashboard is the same', async () => { + await PageObjects.dashboard.loadSavedDashboard(newDashboartTitle); + await PageObjects.dashboard.switchToEditMode(); + + // add another panel so we can delete it later + await dashboardAddPanel.clickCreateNewLink(); + await PageObjects.visualize.clickAggBasedVisualizations(); + await PageObjects.visualize.clickAreaChart(); + await PageObjects.visualize.clickNewSearch(); + await PageObjects.visualize.saveVisualizationExpectSuccess('Wildvis', { + redirectToOrigin: true, + }); + + // ensure that the unsaved listing exists + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.expectUnsavedChangesListingExists(newDashboartTitle); + await PageObjects.dashboard.clickUnsavedChangesContinueEditing(newDashboartTitle); + await PageObjects.header.waitUntilLoadingHasFinished(); + + // Remove the panel that was just added + await dashboardPanelActions.removePanelByTitle('Wildvis'); + await PageObjects.header.waitUntilLoadingHasFinished(); + + // Check that it now does not exist + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.expectUnsavedChangesDoesNotExist(newDashboartTitle); + }); + }); +} diff --git a/test/functional/apps/dashboard/dashboard_unsaved_state.ts b/test/functional/apps/dashboard/dashboard_unsaved_state.ts new file mode 100644 index 0000000000000..21ff5be925db5 --- /dev/null +++ b/test/functional/apps/dashboard/dashboard_unsaved_state.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const PageObjects = getPageObjects(['dashboard', 'header', 'visualize', 'settings', 'common']); + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const dashboardAddPanel = getService('dashboardAddPanel'); + + let originalPanelCount = 0; + let unsavedPanelCount = 0; + + describe('dashboard unsaved panels', () => { + before(async () => { + await esArchiver.load('dashboard/current/kibana'); + await kibanaServer.uiSettings.replace({ + defaultIndex: '0bf35f60-3dc9-11e8-8660-4d65aa086b3c', + }); + await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.preserveCrossAppState(); + await PageObjects.dashboard.loadSavedDashboard('few panels'); + await PageObjects.dashboard.switchToEditMode(); + + originalPanelCount = await PageObjects.dashboard.getPanelCount(); + + // add an area chart by value + await dashboardAddPanel.clickCreateNewLink(); + await PageObjects.visualize.clickAggBasedVisualizations(); + await PageObjects.visualize.clickAreaChart(); + await PageObjects.visualize.clickNewSearch(); + await PageObjects.visualize.saveVisualizationAndReturn(); + + // add a metric by reference + await dashboardAddPanel.addVisualization('Rendering-Test: metric'); + }); + + it('has correct number of panels', async () => { + unsavedPanelCount = await PageObjects.dashboard.getPanelCount(); + expect(unsavedPanelCount).to.eql(originalPanelCount + 2); + }); + + it('retains unsaved panel count after navigating to listing page and back', async () => { + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.loadSavedDashboard('few panels'); + await PageObjects.dashboard.switchToEditMode(); + const currentPanelCount = await PageObjects.dashboard.getPanelCount(); + expect(currentPanelCount).to.eql(unsavedPanelCount); + }); + + it('retains unsaved panel count after navigating to another app and back', async () => { + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.visualize.gotoVisualizationLandingPage(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.common.navigateToApp('dashboards'); + await PageObjects.dashboard.loadSavedDashboard('few panels'); + await PageObjects.dashboard.switchToEditMode(); + const currentPanelCount = await PageObjects.dashboard.getPanelCount(); + expect(currentPanelCount).to.eql(unsavedPanelCount); + }); + + it('resets to original panel count upon entering view mode', async () => { + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.clickCancelOutOfEditMode(); + const currentPanelCount = await PageObjects.dashboard.getPanelCount(); + expect(currentPanelCount).to.eql(originalPanelCount); + }); + + it('retains unsaved panel count after returning to edit mode', async () => { + await PageObjects.header.waitUntilLoadingHasFinished(); + await PageObjects.dashboard.switchToEditMode(); + const currentPanelCount = await PageObjects.dashboard.getPanelCount(); + expect(currentPanelCount).to.eql(unsavedPanelCount); + }); + }); +} diff --git a/test/functional/apps/dashboard/index.ts b/test/functional/apps/dashboard/index.ts index 86b12da791752..29f4180019b11 100644 --- a/test/functional/apps/dashboard/index.ts +++ b/test/functional/apps/dashboard/index.ts @@ -46,6 +46,8 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./embeddable_data_grid')); loadTestFile(require.resolve('./create_and_add_embeddables')); loadTestFile(require.resolve('./edit_embeddable_redirects')); + loadTestFile(require.resolve('./dashboard_unsaved_state')); + loadTestFile(require.resolve('./dashboard_unsaved_listing')); loadTestFile(require.resolve('./edit_visualizations')); loadTestFile(require.resolve('./time_zones')); loadTestFile(require.resolve('./dashboard_options')); diff --git a/test/functional/apps/dashboard/panel_context_menu.ts b/test/functional/apps/dashboard/panel_context_menu.ts index 17072731555bd..0f1577ca49188 100644 --- a/test/functional/apps/dashboard/panel_context_menu.ts +++ b/test/functional/apps/dashboard/panel_context_menu.ts @@ -105,6 +105,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.header.clickDashboard(); + // The following tests require a fresh dashboard. + await PageObjects.dashboard.gotoDashboardLandingPage(); + await PageObjects.dashboard.clickNewDashboard(); + const inViewMode = await PageObjects.dashboard.getIsInViewMode(); if (inViewMode) await PageObjects.dashboard.switchToEditMode(); await dashboardAddPanel.addSavedSearch(searchName); @@ -140,7 +144,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before('and add one panel and save to put dashboard in "view" mode', async () => { await dashboardAddPanel.addVisualization(PIE_CHART_VIS_NAME); - await PageObjects.dashboard.saveDashboard(dashboardName); + await PageObjects.dashboard.saveDashboard(dashboardName + '2'); }); before('expand panel to "full screen"', async () => { diff --git a/test/functional/apps/dashboard/view_edit.ts b/test/functional/apps/dashboard/view_edit.ts index 46d125e4c0715..e62f8ef6de2b6 100644 --- a/test/functional/apps/dashboard/view_edit.ts +++ b/test/functional/apps/dashboard/view_edit.ts @@ -72,7 +72,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'Sep 19, 2013 @ 06:31:44.000', 'Sep 19, 2013 @ 06:31:44.000' ); - await PageObjects.dashboard.clickCancelOutOfEditMode(); + await PageObjects.dashboard.clickDiscardChanges(); // confirm lose changes await PageObjects.common.clickConfirmOnModal(); @@ -88,7 +88,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await queryBar.setQuery(`${originalQuery}and extra stuff`); await queryBar.submitQuery(); - await PageObjects.dashboard.clickCancelOutOfEditMode(); + await PageObjects.dashboard.clickDiscardChanges(); // confirm lose changes await PageObjects.common.clickConfirmOnModal(); @@ -111,7 +111,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { hasFilter = await filterBar.hasFilter('animal', 'dog'); expect(hasFilter).to.be(false); - await PageObjects.dashboard.clickCancelOutOfEditMode(); + await PageObjects.dashboard.clickDiscardChanges(); // confirm lose changes await PageObjects.common.clickConfirmOnModal(); @@ -133,7 +133,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { redirectToOrigin: true, }); - await PageObjects.dashboard.clickCancelOutOfEditMode(); + await PageObjects.dashboard.clickDiscardChanges(); // for this sleep see https://github.com/elastic/kibana/issues/22299 await PageObjects.common.sleep(500); @@ -148,7 +148,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const originalPanelCount = await PageObjects.dashboard.getPanelCount(); await dashboardAddPanel.addVisualization('new viz panel'); - await PageObjects.dashboard.clickCancelOutOfEditMode(); + await PageObjects.dashboard.clickDiscardChanges(); // confirm lose changes await PageObjects.common.clickConfirmOnModal(); @@ -171,7 +171,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'Sep 19, 2015 @ 06:31:44.000', 'Sep 19, 2015 @ 06:31:44.000' ); - await PageObjects.dashboard.clickCancelOutOfEditMode(); + await PageObjects.dashboard.clickDiscardChanges(); await PageObjects.common.clickCancelOnModal(); await PageObjects.dashboard.saveDashboard(dashboardName, { @@ -200,7 +200,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); const newTime = await PageObjects.timePicker.getTimeConfig(); - await PageObjects.dashboard.clickCancelOutOfEditMode(); + await PageObjects.dashboard.clickDiscardChanges(); await PageObjects.common.clickCancelOnModal(); await PageObjects.dashboard.saveDashboard(dashboardName, { storeTimeWithDashboard: true }); diff --git a/test/functional/apps/discover/_discover.ts b/test/functional/apps/discover/_discover.ts index bf0a027553832..552022241a724 100644 --- a/test/functional/apps/discover/_discover.ts +++ b/test/functional/apps/discover/_discover.ts @@ -18,6 +18,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const kibanaServer = getService('kibanaServer'); const queryBar = getService('queryBar'); const inspector = getService('inspector'); + const elasticChart = getService('elasticChart'); const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker']); const defaultSettings = { defaultIndex: 'logstash-*', @@ -31,7 +32,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // and load a set of makelogs data await esArchiver.loadIfNeeded('logstash_functional'); await kibanaServer.uiSettings.replace(defaultSettings); - log.debug('discover'); await PageObjects.common.navigateToApp('discover'); await PageObjects.timePicker.setDefaultAbsoluteRange(); }); @@ -99,11 +99,25 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); - it.skip('should modify the time range when the histogram is brushed', async function () { + it('should modify the time range when the histogram is brushed', async function () { + // this is the number of renderings of the histogram needed when new data is fetched + // this needs to be improved + const renderingCountInc = 3; + const prevRenderingCount = await elasticChart.getVisualizationRenderingCount(); await PageObjects.timePicker.setDefaultAbsoluteRange(); + await PageObjects.discover.waitUntilSearchingHasFinished(); + await retry.waitFor('chart rendering complete', async () => { + const actualRenderingCount = await elasticChart.getVisualizationRenderingCount(); + log.debug(`Number of renderings before brushing: ${actualRenderingCount}`); + return actualRenderingCount === prevRenderingCount + renderingCountInc; + }); await PageObjects.discover.brushHistogram(); await PageObjects.discover.waitUntilSearchingHasFinished(); - + await retry.waitFor('chart rendering complete after being brushed', async () => { + const actualRenderingCount = await elasticChart.getVisualizationRenderingCount(); + log.debug(`Number of renderings after brushing: ${actualRenderingCount}`); + return actualRenderingCount === prevRenderingCount + 6; + }); const newDurationHours = await PageObjects.timePicker.getTimeDurationInHours(); expect(Math.round(newDurationHours)).to.be(26); diff --git a/test/functional/apps/discover/_doc_table.ts b/test/functional/apps/discover/_doc_table.ts index f8f45d2bc7108..d4818d99565e4 100644 --- a/test/functional/apps/discover/_doc_table.ts +++ b/test/functional/apps/discover/_doc_table.ts @@ -15,6 +15,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); const docTable = getService('docTable'); + const queryBar = getService('queryBar'); const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker']); const defaultSettings = { defaultIndex: 'logstash-*', @@ -107,6 +108,24 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // TODO: test something more meaninful here? }); }); + + it('should not close the detail panel actions when data is re-requested', async function () { + await retry.try(async function () { + const nrOfFetches = await PageObjects.discover.getNrOfFetches(); + await docTable.clickRowToggle({ isAnchorRow: false, rowIndex: rowToInspect - 1 }); + const detailsEl = await docTable.getDetailsRows(); + const defaultMessageEl = await detailsEl[0].findByTestSubject('docTableRowDetailsTitle'); + expect(defaultMessageEl).to.be.ok(); + await queryBar.submitQuery(); + const nrOfFetchesResubmit = await PageObjects.discover.getNrOfFetches(); + expect(nrOfFetchesResubmit).to.be.above(nrOfFetches); + const defaultMessageElResubmit = await detailsEl[0].findByTestSubject( + 'docTableRowDetailsTitle' + ); + + expect(defaultMessageElResubmit).to.be.ok(); + }); + }); }); describe('add and remove columns', function () { diff --git a/test/functional/apps/discover/_field_visualize.ts b/test/functional/apps/discover/_field_visualize.ts deleted file mode 100644 index e11ef249d8c78..0000000000000 --- a/test/functional/apps/discover/_field_visualize.ts +++ /dev/null @@ -1,159 +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 - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../ftr_provider_context'; - -export default function ({ getService, getPageObjects }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const filterBar = getService('filterBar'); - const inspector = getService('inspector'); - const kibanaServer = getService('kibanaServer'); - const log = getService('log'); - const queryBar = getService('queryBar'); - const PageObjects = getPageObjects(['common', 'discover', 'header', 'timePicker', 'visualize']); - const defaultSettings = { - defaultIndex: 'logstash-*', - }; - - describe('discover field visualize button', function () { - // unskipped on cloud as these tests test the navigation - // from Discover to Visualize which happens only on OSS - this.tags(['skipCloud']); - before(async function () { - log.debug('load kibana index with default index pattern'); - await esArchiver.load('discover'); - - // and load a set of makelogs data - await esArchiver.loadIfNeeded('logstash_functional'); - await kibanaServer.uiSettings.replace(defaultSettings); - }); - - beforeEach(async () => { - log.debug('go to discover'); - await PageObjects.common.navigateToApp('discover'); - await PageObjects.timePicker.setDefaultAbsoluteRange(); - }); - - it('should be able to visualize a field and save the visualization', async () => { - await PageObjects.discover.findFieldByName('type'); - log.debug('visualize a type field'); - await PageObjects.discover.clickFieldListItemVisualize('type'); - await PageObjects.visualize.saveVisualizationExpectSuccess('Top 5 server types'); - }); - - it('should visualize a field in area chart', async () => { - await PageObjects.discover.findFieldByName('phpmemory'); - log.debug('visualize a phpmemory field'); - await PageObjects.discover.clickFieldListItemVisualize('phpmemory'); - await PageObjects.header.waitUntilLoadingHasFinished(); - const expectedTableData = [ - ['0', '10'], - ['58,320', '2'], - ['171,080', '2'], - ['3,240', '1'], - ['3,520', '1'], - ['3,880', '1'], - ['4,120', '1'], - ['4,640', '1'], - ['4,760', '1'], - ['5,680', '1'], - ['7,160', '1'], - ['7,400', '1'], - ['8,400', '1'], - ['8,800', '1'], - ['8,960', '1'], - ['9,400', '1'], - ['10,280', '1'], - ['10,840', '1'], - ['13,080', '1'], - ['13,360', '1'], - ]; - await inspector.open(); - await inspector.expectTableData(expectedTableData); - await inspector.close(); - }); - - it('should not show the "Visualize" button for geo field', async () => { - await PageObjects.discover.findFieldByName('geo.coordinates'); - log.debug('visualize a geo field'); - await PageObjects.discover.expectMissingFieldListItemVisualize('geo.coordinates'); - }); - - it('should preserve app filters in visualize', async () => { - await filterBar.addFilter('bytes', 'is between', '3500', '4000'); - await PageObjects.discover.findFieldByName('geo.src'); - log.debug('visualize a geo.src field with filter applied'); - await PageObjects.discover.clickFieldListItemVisualize('geo.src'); - await PageObjects.header.waitUntilLoadingHasFinished(); - - expect(await filterBar.hasFilter('bytes', '3,500 to 4,000')).to.be(true); - const expectedTableData = [ - ['CN', '133'], - ['IN', '120'], - ['US', '58'], - ['ID', '28'], - ['BD', '25'], - ['BR', '22'], - ['EG', '14'], - ['NG', '14'], - ['PK', '13'], - ['IR', '12'], - ['PH', '12'], - ['JP', '11'], - ['RU', '11'], - ['DE', '8'], - ['FR', '8'], - ['MX', '8'], - ['TH', '8'], - ['TR', '8'], - ['CA', '6'], - ['SA', '6'], - ]; - await inspector.open(); - await inspector.expectTableData(expectedTableData); - await inspector.close(); - }); - - it('should preserve query in visualize', async () => { - await queryBar.setQuery('machine.os : ios'); - await queryBar.submitQuery(); - await PageObjects.discover.findFieldByName('geo.dest'); - log.debug('visualize a geo.dest field with query applied'); - await PageObjects.discover.clickFieldListItemVisualize('geo.dest'); - await PageObjects.header.waitUntilLoadingHasFinished(); - - expect(await queryBar.getQueryString()).to.equal('machine.os : ios'); - const expectedTableData = [ - ['CN', '519'], - ['IN', '495'], - ['US', '275'], - ['ID', '82'], - ['PK', '75'], - ['BR', '71'], - ['NG', '54'], - ['BD', '51'], - ['JP', '47'], - ['MX', '47'], - ['IR', '44'], - ['PH', '44'], - ['RU', '42'], - ['ET', '33'], - ['TH', '33'], - ['EG', '32'], - ['VN', '32'], - ['DE', '31'], - ['FR', '30'], - ['GB', '30'], - ]; - await inspector.open(); - await inspector.expectTableData(expectedTableData); - await inspector.close(); - }); - }); -} diff --git a/test/functional/apps/discover/_indexpattern_with_unmapped_fields.ts b/test/functional/apps/discover/_indexpattern_with_unmapped_fields.ts new file mode 100644 index 0000000000000..bc1767342dd4f --- /dev/null +++ b/test/functional/apps/discover/_indexpattern_with_unmapped_fields.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); + const testSubjects = getService('testSubjects'); + const log = getService('log'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['common', 'timePicker', 'discover']); + + describe('index pattern with unmapped fields', () => { + const unmappedFieldsSwitchSelector = 'unmappedFieldsSwitch'; + + before(async () => { + await esArchiver.loadIfNeeded('unmapped_fields'); + await kibanaServer.uiSettings.replace({ defaultIndex: 'test-index-unmapped-fields' }); + await kibanaServer.uiSettings.update({ + 'discover:searchFieldsFromSource': false, + }); + log.debug('discover'); + const fromTime = 'Jan 20, 2021 @ 00:00:00.000'; + const toTime = 'Jan 25, 2021 @ 00:00:00.000'; + await PageObjects.common.navigateToApp('discover'); + await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); + }); + + after(async () => { + await esArchiver.unload('unmapped_fields'); + }); + + it('unmapped fields do not exist on a new saved search', async () => { + const expectedHitCount = '4'; + await retry.try(async function () { + expect(await PageObjects.discover.getHitCount()).to.be(expectedHitCount); + }); + const allFields = await PageObjects.discover.getAllFieldNames(); + // message is a mapped field + expect(allFields.includes('message')).to.be(true); + // sender is not a mapped field + expect(allFields.includes('sender')).to.be(false); + }); + + it('unmapped fields toggle does not exist on a new saved search', async () => { + await PageObjects.discover.openSidebarFieldFilter(); + await testSubjects.existOrFail('filterSelectionPanel'); + await testSubjects.missingOrFail('unmappedFieldsSwitch'); + }); + + it('unmapped fields exist on an existing saved search', async () => { + await PageObjects.discover.loadSavedSearch('Existing Saved Search'); + const expectedHitCount = '4'; + await retry.try(async function () { + expect(await PageObjects.discover.getHitCount()).to.be(expectedHitCount); + }); + const allFields = await PageObjects.discover.getAllFieldNames(); + expect(allFields.includes('message')).to.be(true); + expect(allFields.includes('sender')).to.be(true); + expect(allFields.includes('receiver')).to.be(true); + }); + + it('unmapped fields toggle exists on an existing saved search', async () => { + await PageObjects.discover.openSidebarFieldFilter(); + await testSubjects.existOrFail('filterSelectionPanel'); + await testSubjects.existOrFail(unmappedFieldsSwitchSelector); + expect(await testSubjects.isEuiSwitchChecked(unmappedFieldsSwitchSelector)).to.be(true); + }); + + it('switching unmapped fields toggle off hides unmapped fields', async () => { + await testSubjects.setEuiSwitch(unmappedFieldsSwitchSelector, 'uncheck'); + await PageObjects.discover.closeSidebarFieldFilter(); + const allFields = await PageObjects.discover.getAllFieldNames(); + expect(allFields.includes('message')).to.be(true); + expect(allFields.includes('sender')).to.be(false); + expect(allFields.includes('receiver')).to.be(false); + }); + }); +} diff --git a/test/functional/apps/discover/_shared_links.ts b/test/functional/apps/discover/_shared_links.ts index 72596f7597364..4f0095ad88049 100644 --- a/test/functional/apps/discover/_shared_links.ts +++ b/test/functional/apps/discover/_shared_links.ts @@ -77,7 +77,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { '/app/discover?_t=1453775307251#' + '/?_g=(filters:!(),refreshInterval:(pause:!t,value:0),time' + ":(from:'2015-09-19T06:31:44.000Z',to:'2015-09" + - "-23T18:31:44.000Z'))&_a=(columns:!(),filters:!(),index:'logstash-" + + "-23T18:31:44.000Z'))&_a=(columns:!(_source),filters:!(),index:'logstash-" + "*',interval:auto,query:(language:kuery,query:'')" + ",sort:!(!('@timestamp',desc)))"; const actualUrl = await PageObjects.share.getSharedUrl(); diff --git a/test/functional/apps/discover/index.ts b/test/functional/apps/discover/index.ts index 4f42b41a1e02d..ba958c3073c99 100644 --- a/test/functional/apps/discover/index.ts +++ b/test/functional/apps/discover/index.ts @@ -27,7 +27,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./_discover')); loadTestFile(require.resolve('./_discover_histogram')); loadTestFile(require.resolve('./_doc_table')); - loadTestFile(require.resolve('./_field_visualize')); loadTestFile(require.resolve('./_filter_editor')); loadTestFile(require.resolve('./_errors')); loadTestFile(require.resolve('./_field_data')); @@ -47,5 +46,6 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./_data_grid_field_data')); loadTestFile(require.resolve('./_data_grid_doc_navigation')); loadTestFile(require.resolve('./_data_grid_doc_table')); + loadTestFile(require.resolve('./_indexpattern_with_unmapped_fields')); }); } diff --git a/test/functional/apps/management/_import_objects.ts b/test/functional/apps/management/_import_objects.ts index e18f2a7485444..754406938e47b 100644 --- a/test/functional/apps/management/_import_objects.ts +++ b/test/functional/apps/management/_import_objects.ts @@ -27,9 +27,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe.skip('import objects', function describeIndexTests() { describe('.ndjson file', () => { beforeEach(async function () { - await esArchiver.load('management'); await kibanaServer.uiSettings.replace({}); await PageObjects.settings.navigateTo(); + await esArchiver.load('management'); await PageObjects.settings.clickKibanaSavedObjects(); }); @@ -213,9 +213,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('.json file', () => { beforeEach(async function () { - await esArchiver.load('saved_objects_imports'); + // delete .kibana index and then wait for Kibana to re-create it await kibanaServer.uiSettings.replace({}); await PageObjects.settings.navigateTo(); + await esArchiver.load('saved_objects_imports'); await PageObjects.settings.clickKibanaSavedObjects(); }); diff --git a/test/functional/apps/management/_index_pattern_filter.js b/test/functional/apps/management/_index_pattern_filter.js index 91ea13348d611..eae53682b6ccf 100644 --- a/test/functional/apps/management/_index_pattern_filter.js +++ b/test/functional/apps/management/_index_pattern_filter.js @@ -12,11 +12,10 @@ export default function ({ getService, getPageObjects }) { const kibanaServer = getService('kibanaServer'); const retry = getService('retry'); const PageObjects = getPageObjects(['settings']); - const esArchiver = getService('esArchiver'); describe('index pattern filter', function describeIndexTests() { before(async function () { - await esArchiver.emptyKibanaIndex(); + // delete .kibana index and then wait for Kibana to re-create it await kibanaServer.uiSettings.replace({}); await PageObjects.settings.navigateTo(); await PageObjects.settings.clickKibanaIndexPatterns(); diff --git a/test/functional/apps/management/_index_patterns_empty.ts b/test/functional/apps/management/_index_patterns_empty.ts index 4e86de6d70653..3b89e05d4b582 100644 --- a/test/functional/apps/management/_index_patterns_empty.ts +++ b/test/functional/apps/management/_index_patterns_empty.ts @@ -19,7 +19,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { describe('index pattern empty view', () => { before(async () => { - await esArchiver.emptyKibanaIndex(); + await esArchiver.load('empty_kibana'); await esArchiver.unload('logstash_functional'); await esArchiver.unload('makelogs'); await kibanaServer.uiSettings.replace({}); @@ -27,6 +27,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }); after(async () => { + await esArchiver.unload('empty_kibana'); await esArchiver.loadIfNeeded('makelogs'); // @ts-expect-error await es.transport.request({ diff --git a/test/functional/apps/management/_mgmt_import_saved_objects.js b/test/functional/apps/management/_mgmt_import_saved_objects.js index 87eca2c7a5a65..45a18d5932764 100644 --- a/test/functional/apps/management/_mgmt_import_saved_objects.js +++ b/test/functional/apps/management/_mgmt_import_saved_objects.js @@ -18,13 +18,14 @@ export default function ({ getService, getPageObjects }) { describe('mgmt saved objects', function describeIndexTests() { beforeEach(async function () { - await esArchiver.emptyKibanaIndex(); + await esArchiver.load('empty_kibana'); await esArchiver.load('discover'); await PageObjects.settings.navigateTo(); }); afterEach(async function () { await esArchiver.unload('discover'); + await esArchiver.load('empty_kibana'); }); it('should import saved objects mgmt', async function () { diff --git a/test/functional/apps/management/_scripted_fields.js b/test/functional/apps/management/_scripted_fields.js index 62edbc50879a0..d1a4c93cec048 100644 --- a/test/functional/apps/management/_scripted_fields.js +++ b/test/functional/apps/management/_scripted_fields.js @@ -27,13 +27,12 @@ import expect from '@kbn/expect'; export default function ({ getService, getPageObjects }) { const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); + const deployment = getService('deployment'); const log = getService('log'); const browser = getService('browser'); const retry = getService('retry'); - const inspector = getService('inspector'); const testSubjects = getService('testSubjects'); const filterBar = getService('filterBar'); - const deployment = getService('deployment'); const PageObjects = getPageObjects([ 'common', 'header', @@ -188,39 +187,11 @@ export default function ({ getService, getPageObjects }) { }); it('should visualize scripted field in vertical bar chart', async function () { - await filterBar.removeAllFilters(); - await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName); - await PageObjects.header.waitUntilLoadingHasFinished(); - - if (await deployment.isOss()) { - // OSS renders a vertical bar chart and we check the data in the Inspect panel - const expectedChartValues = [ - ['14', '31'], - ['10', '29'], - ['7', '24'], - ['11', '24'], - ['12', '23'], - ['20', '23'], - ['19', '21'], - ['6', '20'], - ['17', '20'], - ['30', '20'], - ['13', '19'], - ['18', '18'], - ['16', '17'], - ['5', '16'], - ['8', '16'], - ['15', '14'], - ['3', '13'], - ['2', '12'], - ['9', '10'], - ['4', '9'], - ]; - - await inspector.open(); - await inspector.setTablePageSize(50); - await inspector.expectTableData(expectedChartValues); - } else { + const isOss = await deployment.isOss(); + if (!isOss) { + await filterBar.removeAllFilters(); + await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName); + await PageObjects.header.waitUntilLoadingHasFinished(); // verify Lens opens a visualization expect(await testSubjects.getVisibleTextAll('lns-dimensionTrigger')).to.contain( 'Average of ram_Pain1' @@ -306,16 +277,10 @@ export default function ({ getService, getPageObjects }) { }); it('should visualize scripted field in vertical bar chart', async function () { - await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2); - await PageObjects.header.waitUntilLoadingHasFinished(); - if (await deployment.isOss()) { - // OSS renders a vertical bar chart and we check the data in the Inspect panel - await inspector.open(); - await inspector.expectTableData([ - ['good', '359'], - ['bad', '27'], - ]); - } else { + const isOss = await deployment.isOss(); + if (!isOss) { + await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2); + await PageObjects.header.waitUntilLoadingHasFinished(); // verify Lens opens a visualization expect(await testSubjects.getVisibleTextAll('lns-dimensionTrigger')).to.contain( 'Top values of painString' @@ -402,16 +367,10 @@ export default function ({ getService, getPageObjects }) { }); it('should visualize scripted field in vertical bar chart', async function () { - await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2); - await PageObjects.header.waitUntilLoadingHasFinished(); - if (await deployment.isOss()) { - // OSS renders a vertical bar chart and we check the data in the Inspect panel - await inspector.open(); - await inspector.expectTableData([ - ['true', '359'], - ['false', '27'], - ]); - } else { + const isOss = await deployment.isOss(); + if (!isOss) { + await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2); + await PageObjects.header.waitUntilLoadingHasFinished(); // verify Lens opens a visualization expect(await testSubjects.getVisibleTextAll('lns-dimensionTrigger')).to.contain( 'Top values of painBool' @@ -501,36 +460,10 @@ export default function ({ getService, getPageObjects }) { }); it('should visualize scripted field in vertical bar chart', async function () { - await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2); - await PageObjects.header.waitUntilLoadingHasFinished(); - - if (await deployment.isOss()) { - // OSS renders a vertical bar chart and we check the data in the Inspect panel - await inspector.open(); - await inspector.setTablePageSize(50); - await inspector.expectTableData([ - ['2015-09-17 20:00', '1'], - ['2015-09-17 21:00', '1'], - ['2015-09-17 23:00', '1'], - ['2015-09-18 00:00', '1'], - ['2015-09-18 03:00', '1'], - ['2015-09-18 04:00', '1'], - ['2015-09-18 04:00', '1'], - ['2015-09-18 04:00', '1'], - ['2015-09-18 04:00', '1'], - ['2015-09-18 05:00', '1'], - ['2015-09-18 05:00', '1'], - ['2015-09-18 05:00', '1'], - ['2015-09-18 05:00', '1'], - ['2015-09-18 06:00', '1'], - ['2015-09-18 06:00', '1'], - ['2015-09-18 06:00', '1'], - ['2015-09-18 06:00', '1'], - ['2015-09-18 07:00', '1'], - ['2015-09-18 07:00', '1'], - ['2015-09-18 07:00', '1'], - ]); - } else { + const isOss = await deployment.isOss(); + if (!isOss) { + await PageObjects.discover.clickFieldListItemVisualize(scriptedPainlessFieldName2); + await PageObjects.header.waitUntilLoadingHasFinished(); // verify Lens opens a visualization expect(await testSubjects.getVisibleTextAll('lns-dimensionTrigger')).to.contain( 'painDate' diff --git a/test/functional/apps/management/_test_huge_fields.js b/test/functional/apps/management/_test_huge_fields.js index c1fca31e695cb..2ab619276d2b9 100644 --- a/test/functional/apps/management/_test_huge_fields.js +++ b/test/functional/apps/management/_test_huge_fields.js @@ -20,7 +20,6 @@ export default function ({ getService, getPageObjects }) { const EXPECTED_FIELD_COUNT = '10006'; before(async function () { await security.testUser.setRoles(['kibana_admin', 'test_testhuge_reader']); - await esArchiver.emptyKibanaIndex(); await esArchiver.loadIfNeeded('large_fields'); await PageObjects.settings.createIndexPattern('testhuge', 'date'); }); diff --git a/test/functional/apps/management/index.ts b/test/functional/apps/management/index.ts index 06e652f9f3e59..3de11fbf4c991 100644 --- a/test/functional/apps/management/index.ts +++ b/test/functional/apps/management/index.ts @@ -14,11 +14,13 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { describe('management', function () { before(async () => { await esArchiver.unload('logstash_functional'); + await esArchiver.load('empty_kibana'); await esArchiver.loadIfNeeded('makelogs'); }); after(async () => { await esArchiver.unload('makelogs'); + await esArchiver.unload('empty_kibana'); }); describe('', function () { @@ -29,10 +31,10 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./_index_pattern_results_sort')); loadTestFile(require.resolve('./_index_pattern_popularity')); loadTestFile(require.resolve('./_kibana_settings')); - loadTestFile(require.resolve('./_scripted_fields')); loadTestFile(require.resolve('./_scripted_fields_preview')); loadTestFile(require.resolve('./_mgmt_import_saved_objects')); loadTestFile(require.resolve('./_index_patterns_empty')); + loadTestFile(require.resolve('./_scripted_fields')); }); describe('', function () { 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 613b1a162eb63..9b48e78246b37 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 @@ -12,6 +12,7 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); + const kibanaServer = getService('kibanaServer'); const find = getService('find'); const security = getService('security'); const { visualize, visEditor } = getPageObjects(['visualize', 'visEditor']); @@ -52,6 +53,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await esArchiver.loadIfNeeded('logstash_functional'); await esArchiver.loadIfNeeded('long_window_logstash'); await esArchiver.load('visualize'); + await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*' }); await security.testUser.restoreDefaults(); }); }); diff --git a/test/functional/fixtures/es_archiver/data/data.json.gz b/test/functional/fixtures/es_archiver/data/data.json.gz new file mode 100644 index 0000000000000..629276ccd186e Binary files /dev/null and b/test/functional/fixtures/es_archiver/data/data.json.gz differ diff --git a/test/functional/fixtures/es_archiver/data/mappings.json b/test/functional/fixtures/es_archiver/data/mappings.json new file mode 100644 index 0000000000000..256978162b981 --- /dev/null +++ b/test/functional/fixtures/es_archiver/data/mappings.json @@ -0,0 +1,450 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "application_usage_daily": "43b8830d5d0df85a6823d290885fc9fd", + "application_usage_totals": "3d1b76c39bfb2cc8296b024d73854724", + "application_usage_transactional": "3d1b76c39bfb2cc8296b024d73854724", + "config": "c63748b75f39d0c54de12d12c1ccbc20", + "core-usage-stats": "3d1b76c39bfb2cc8296b024d73854724", + "coreMigrationVersion": "2f4316de49999235636386fe51dc06c1", + "dashboard": "40554caf09725935e2c02e02563a2d07", + "index-pattern": "45915a1ad866812242df474eb0479052", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "legacy-url-alias": "3d1b76c39bfb2cc8296b024d73854724", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "namespace": "2f4316de49999235636386fe51dc06c1", + "namespaces": "2f4316de49999235636386fe51dc06c1", + "originId": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "e5b843b43566421ffa75fb499271dc34", + "search-telemetry": "3d1b76c39bfb2cc8296b024d73854724", + "telemetry": "36a616f7026dfa617d6655df850fe16d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-counter": "0d409297dc5ebe1e3a1da691c6ee32e3", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "visualization": "f819cf6636b75c9e76ba733a0c6ef355" + } + }, + "dynamic": "strict", + "properties": { + "application_usage_daily": { + "dynamic": "false", + "properties": { + "timestamp": { + "type": "date" + } + } + }, + "application_usage_totals": { + "dynamic": "false", + "type": "object" + }, + "application_usage_transactional": { + "dynamic": "false", + "type": "object" + }, + "config": { + "dynamic": "false", + "properties": { + "buildNum": { + "type": "keyword" + } + } + }, + "core-usage-stats": { + "dynamic": "false", + "type": "object" + }, + "coreMigrationVersion": { + "type": "keyword" + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "doc_values": false, + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "optionsJSON": { + "index": false, + "type": "text" + }, + "panelsJSON": { + "index": false, + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "pause": { + "doc_values": false, + "index": false, + "type": "boolean" + }, + "section": { + "doc_values": false, + "index": false, + "type": "integer" + }, + "value": { + "doc_values": false, + "index": false, + "type": "integer" + } + } + }, + "timeFrom": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "timeRestore": { + "doc_values": false, + "index": false, + "type": "boolean" + }, + "timeTo": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "index-pattern": { + "dynamic": "false", + "properties": { + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "legacy-url-alias": { + "dynamic": "false", + "type": "object" + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "config": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "namespace": { + "type": "keyword" + }, + "namespaces": { + "type": "keyword" + }, + "originId": { + "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": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "description": { + "type": "text" + }, + "grid": { + "enabled": false, + "type": "object" + }, + "hits": { + "doc_values": false, + "index": false, + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "index": false, + "type": "text" + } + } + }, + "pre712": { + "type": "boolean" + }, + "sort": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "search-telemetry": { + "dynamic": "false", + "type": "object" + }, + "telemetry": { + "properties": { + "allowChangingOptInStatus": { + "type": "boolean" + }, + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "type": "keyword" + }, + "reportFailureCount": { + "type": "integer" + }, + "reportFailureVersion": { + "type": "keyword" + }, + "sendUsageFrom": { + "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-counter": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "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": { + "index": false, + "type": "text" + } + } + }, + "savedSearchRefName": { + "doc_values": false, + "index": false, + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "index": false, + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "index": false, + "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/test/functional/fixtures/es_archiver/unmapped_fields/data.json b/test/functional/fixtures/es_archiver/unmapped_fields/data.json new file mode 100644 index 0000000000000..10c33280696b1 --- /dev/null +++ b/test/functional/fixtures/es_archiver/unmapped_fields/data.json @@ -0,0 +1,105 @@ +{ + "type": "doc", + "value": { + "id": "search:cd43f5c2-h761-13f6-9486-733b1ac9221a", + "index": ".kibana", + "source": { + "search": { + "columns": [ + "_source" + ], + "description": "Existing Saved Search", + "hits": 4, + "kibanaSavedObjectMeta": { + "searchSourceJSON": "{\n \"index\": \"test-index-unmapped-fields\",\n \"highlightAll\": true,\n \"filter\": [],\n \"query\": {\n \"query_string\": {\n \"query\": \"*\",\n \"analyze_wildcard\": true\n }\n }\n}" + }, + "sort": [ + "@timestamp", + "desc" + ], + "title": "Existing Saved Search", + "version": 1 + }, + "type": "search" + } + } +} + +{ + "type": "doc", + "value": { + "id": "index-pattern:test-index-unmapped-fields", + "index": ".kibana", + "source": { + "index-pattern": { + "fields": "[{\"name\":\"_id\",\"type\":\"string\",\"esTypes\":[\"_id\"],\"count\":1,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_index\",\"type\":\"string\",\"esTypes\":[\"_index\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"_score\",\"type\":\"number\",\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_source\",\"type\":\"_source\",\"esTypes\":[\"_source\"],\"count\":0,\"scripted\":false,\"searchable\":false,\"aggregatable\":false,\"readFromDocValues\":false},{\"name\":\"_type\",\"type\":\"string\",\"esTypes\":[\"_type\"],\"count\":0,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":false},{\"name\":\"timestamp\",\"type\":\"date\",\"esTypes\":[\"date\"],\"count\":4,\"scripted\":false,\"searchable\":true,\"aggregatable\":true,\"readFromDocValues\":true}]", + "timeFieldName": "timestamp", + "title": "test-index-unmapped-fields", + "fieldFormatMap": "{\"timestamp\":{\"id\":\"date\"}}" + }, + "type": "index-pattern" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "1", + "index": "test-index-unmapped-fields", + "source": { + "timestamp": "2021-01-21T12:00:00.000Z", + "message": "Something bad is coming", + "address": "Elm Street 1" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "2", + "index": "test-index-unmapped-fields", + "source": { + "timestamp": "2021-01-22T12:00:00.000Z", + "message": "We have a new case", + "address": "221b Baker Street" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "3", + "index": "test-index-unmapped-fields", + "source": { + "timestamp": "2021-01-23T12:00:00.000Z", + "message": "We have a new case", + "address": "221b Baker Street", + "sender": "John Doe", + "receiver": "Sherlock Holmes" + }, + "type": "_doc" + } +} + +{ + "type": "doc", + "value": { + "id": "4", + "index": "test-index-unmapped-fields", + "source": { + "timestamp": "2021-01-24T12:00:00.000Z", + "message": "I am coming for you", + "address": "13 Elm Street", + "sender": "Freddy Krueger", + "receiver": "Nancy Thompson" + }, + "type": "_doc" + } +} + diff --git a/test/functional/fixtures/es_archiver/unmapped_fields/mappings.json b/test/functional/fixtures/es_archiver/unmapped_fields/mappings.json new file mode 100644 index 0000000000000..f92c2eff48889 --- /dev/null +++ b/test/functional/fixtures/es_archiver/unmapped_fields/mappings.json @@ -0,0 +1,20 @@ +{ + "type": "index", + "value": { + "index": "test-index-unmapped-fields", + "mappings": { + "dynamic": "false", + "properties": { + "timestamp": {"type": "date"}, + "message": { "type": "text" }, + "address": { "type": "text" } + } + }, + "settings": { + "index": { + "number_of_replicas": "1", + "number_of_shards": "1" + } + } + } +} diff --git a/test/functional/page_objects/dashboard_page.ts b/test/functional/page_objects/dashboard_page.ts index 7be8603887bdf..c115100653729 100644 --- a/test/functional/page_objects/dashboard_page.ts +++ b/test/functional/page_objects/dashboard_page.ts @@ -111,6 +111,33 @@ export function DashboardPageProvider({ getService, getPageObjects }: FtrProvide return id; } + public async expectUnsavedChangesListingExists(title: string) { + log.debug(`Expect Unsaved Changes Listing Exists for `, title); + await testSubjects.existOrFail(`edit-unsaved-${title.split(' ').join('-')}`); + } + + public async expectUnsavedChangesDoesNotExist(title: string) { + log.debug(`Expect Unsaved Changes Listing Does Not Exist for `, title); + await testSubjects.missingOrFail(`edit-unsaved-${title.split(' ').join('-')}`); + } + + public async clickUnsavedChangesContinueEditing(title: string) { + log.debug(`Click Unsaved Changes Continue Editing `, title); + await testSubjects.existOrFail(`edit-unsaved-${title.split(' ').join('-')}`); + await testSubjects.click(`edit-unsaved-${title.split(' ').join('-')}`); + } + + public async clickUnsavedChangesDiscard(title: string, confirmDiscard = true) { + log.debug(`Click Unsaved Changes Discard for `, title); + await testSubjects.existOrFail(`discard-unsaved-${title.split(' ').join('-')}`); + await testSubjects.click(`discard-unsaved-${title.split(' ').join('-')}`); + if (confirmDiscard) { + await PageObjects.common.clickConfirmOnModal(); + } else { + await PageObjects.common.clickCancelOnModal(); + } + } + /** * Returns true if already on the dashboard landing page (that page doesn't have a link to itself). * @returns {Promise} @@ -216,8 +243,32 @@ export function DashboardPageProvider({ getService, getPageObjects }: FtrProvide await testSubjects.click('dashboardViewOnlyMode'); } - public async clickNewDashboard() { + public async clickDiscardChanges() { + log.debug('clickDiscardChanges'); + await testSubjects.click('dashboardDiscardChanges'); + } + + public async clickNewDashboard(continueEditing = false) { + await listingTable.clickNewButton('createDashboardPromptButton'); + if (await testSubjects.exists('dashboardCreateConfirm')) { + if (continueEditing) { + await testSubjects.click('dashboardCreateConfirmContinue'); + } else { + await testSubjects.click('dashboardCreateConfirmStartOver'); + } + } + // make sure the dashboard page is shown + await this.waitForRenderComplete(); + } + + public async clickNewDashboardExpectWarning(continueEditing = false) { await listingTable.clickNewButton('createDashboardPromptButton'); + await testSubjects.existOrFail('dashboardCreateConfirm'); + if (continueEditing) { + await testSubjects.click('dashboardCreateConfirmContinue'); + } else { + await testSubjects.click('dashboardCreateConfirmStartOver'); + } // make sure the dashboard page is shown await this.waitForRenderComplete(); } diff --git a/test/functional/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index 88a138ee09bfc..33cee4f40d083 100644 --- a/test/functional/page_objects/discover_page.ts +++ b/test/functional/page_objects/discover_page.ts @@ -136,12 +136,14 @@ export function DiscoverPageProvider({ getService, getPageObjects }: FtrProvider } public async clickHistogramBar() { + await elasticChart.waitForRenderComplete(); const el = await elasticChart.getCanvas(); await browser.getActions().move({ x: 0, y: 20, origin: el._webElement }).click().perform(); } public async brushHistogram() { + await elasticChart.waitForRenderComplete(); const el = await elasticChart.getCanvas(); await browser.dragAndDrop( diff --git a/test/functional/services/dashboard/panel_actions.ts b/test/functional/services/dashboard/panel_actions.ts index d5bd4ab566883..ad6a70c2c97e5 100644 --- a/test/functional/services/dashboard/panel_actions.ts +++ b/test/functional/services/dashboard/panel_actions.ts @@ -99,9 +99,9 @@ export function DashboardPanelActionsProvider({ getService, getPageObjects }: Ft await testSubjects.click(TOGGLE_EXPAND_PANEL_DATA_TEST_SUBJ); } - async removePanel() { + async removePanel(parent?: WebElementWrapper) { log.debug('removePanel'); - await this.openContextMenu(); + await this.openContextMenu(parent); const isActionVisible = await testSubjects.exists(REMOVE_PANEL_DATA_TEST_SUBJ); if (!isActionVisible) await this.clickContextMenuMoreItem(); const isPanelActionVisible = await testSubjects.exists(REMOVE_PANEL_DATA_TEST_SUBJ); @@ -111,10 +111,8 @@ export function DashboardPanelActionsProvider({ getService, getPageObjects }: Ft async removePanelByTitle(title: string) { const header = await this.getPanelHeading(title); - await this.openContextMenu(header); - const isActionVisible = await testSubjects.exists(REMOVE_PANEL_DATA_TEST_SUBJ); - if (!isActionVisible) await this.clickContextMenuMoreItem(); - await testSubjects.click(REMOVE_PANEL_DATA_TEST_SUBJ); + log.debug('found header? ', Boolean(header)); + await this.removePanel(header); } async customizePanel(parent?: WebElementWrapper) { diff --git a/test/plugin_functional/test_suites/core_plugins/applications.ts b/test/plugin_functional/test_suites/core_plugins/applications.ts index 52924d8c93280..e72d032f63469 100644 --- a/test/plugin_functional/test_suites/core_plugins/applications.ts +++ b/test/plugin_functional/test_suites/core_plugins/applications.ts @@ -19,7 +19,6 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide const find = getService('find'); const retry = getService('retry'); const deployment = getService('deployment'); - const esArchiver = getService('esArchiver'); const loadingScreenNotShown = async () => expect(await testSubjects.exists('kbnLoadingMessage')).to.be(false); @@ -51,7 +50,6 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide describe('ui applications', function describeIndexTests() { before(async () => { - await esArchiver.emptyKibanaIndex(); await PageObjects.common.navigateToApp('foo'); }); diff --git a/test/plugin_functional/test_suites/data_plugin/index_patterns.ts b/test/plugin_functional/test_suites/data_plugin/index_patterns.ts index 0cd53a5e1b764..ba12e2df16d41 100644 --- a/test/plugin_functional/test_suites/data_plugin/index_patterns.ts +++ b/test/plugin_functional/test_suites/data_plugin/index_patterns.ts @@ -12,12 +12,8 @@ import '../../plugins/core_provider_plugin/types'; export default function ({ getService }: PluginFunctionalProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); describe('index patterns', function () { - before(async () => { - await esArchiver.emptyKibanaIndex(); - }); let indexPatternId = ''; it('can create an index pattern', async () => { diff --git a/test/plugin_functional/test_suites/saved_objects_management/import_warnings.ts b/test/plugin_functional/test_suites/saved_objects_management/import_warnings.ts index b60e4b4a1d8b7..71663b19b35cb 100644 --- a/test/plugin_functional/test_suites/saved_objects_management/import_warnings.ts +++ b/test/plugin_functional/test_suites/saved_objects_management/import_warnings.ts @@ -10,15 +10,10 @@ import path from 'path'; import expect from '@kbn/expect'; import { PluginFunctionalProviderContext } from '../../services'; -export default function ({ getPageObjects, getService }: PluginFunctionalProviderContext) { +export default function ({ getPageObjects }: PluginFunctionalProviderContext) { const PageObjects = getPageObjects(['common', 'settings', 'header', 'savedObjects']); - const esArchiver = getService('esArchiver'); describe('import warnings', () => { - before(async () => { - await esArchiver.emptyKibanaIndex(); - }); - beforeEach(async () => { await PageObjects.settings.navigateTo(); await PageObjects.settings.clickKibanaSavedObjects(); diff --git a/test/scripts/jenkins_build_load_testing.sh b/test/scripts/jenkins_build_load_testing.sh index aeb584b106498..659321f1d3975 100755 --- a/test/scripts/jenkins_build_load_testing.sh +++ b/test/scripts/jenkins_build_load_testing.sh @@ -1,5 +1,13 @@ #!/usr/bin/env bash +while getopts s: flag +do + case "${flag}" in + s) simulations=${OPTARG};; + esac +done +echo "Simulation classes: $simulations"; + cd "$KIBANA_DIR" source src/dev/ci_setup/setup_env.sh @@ -25,6 +33,7 @@ echo " -> test setup" source test/scripts/jenkins_test_setup_xpack.sh echo " -> run gatling load testing" +export GATLING_SIMULATIONS="$simulations" node scripts/functional_tests \ - --kibana-install-dir "$KIBANA_INSTALL_DIR" \ - --config test/load/config.ts + --kibana-install-dir "$KIBANA_INSTALL_DIR" \ + --config test/load/config.ts diff --git a/test/security_functional/insecure_cluster_warning.ts b/test/security_functional/insecure_cluster_warning.ts index 2f7656b743a51..181c4cf2b46b7 100644 --- a/test/security_functional/insecure_cluster_warning.ts +++ b/test/security_functional/insecure_cluster_warning.ts @@ -31,7 +31,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { before(async () => { await browser.setLocalStorageItem('insecureClusterWarningVisibility', ''); await esArchiver.unload('hamlet'); - await esArchiver.emptyKibanaIndex(); }); it('should not warn when the cluster contains no user data', async () => { diff --git a/test/tsconfig.json b/test/tsconfig.json index 1dc58f7b25c24..c3acf94f8c267 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -26,6 +26,7 @@ { "path": "../src/plugins/expressions/tsconfig.json" }, { "path": "../src/plugins/home/tsconfig.json" }, { "path": "../src/plugins/inspector/tsconfig.json" }, + { "path": "../src/plugins/kibana_overview/tsconfig.json" }, { "path": "../src/plugins/kibana_react/tsconfig.json" }, { "path": "../src/plugins/kibana_usage_collection/tsconfig.json" }, { "path": "../src/plugins/kibana_utils/tsconfig.json" }, diff --git a/test/visual_regression/services/visual_testing/visual_testing.ts b/test/visual_regression/services/visual_testing/visual_testing.ts index 777306017083e..064f43040c47c 100644 --- a/test/visual_regression/services/visual_testing/visual_testing.ts +++ b/test/visual_regression/services/visual_testing/visual_testing.ts @@ -7,10 +7,8 @@ */ import { postSnapshot } from '@percy/agent/dist/utils/sdk-utils'; -import { Test } from 'mocha'; - import testSubjSelector from '@kbn/test-subj-selector'; - +import { Test } from '@kbn/test/types/ftr'; import { pkg } from '../../../../src/core/server/utils'; import { FtrProviderContext } from '../../ftr_provider_context'; diff --git a/tsconfig.json b/tsconfig.json index 21760919c89e9..f6e0fbc8d9e97 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -24,6 +24,7 @@ "src/plugins/input_control_vis/**/*", "src/plugins/inspector/**/*", "src/plugins/kibana_legacy/**/*", + "src/plugins/kibana_overview/**/*", "src/plugins/kibana_react/**/*", "src/plugins/kibana_usage_collection/**/*", "src/plugins/kibana_utils/**/*", @@ -84,6 +85,7 @@ { "path": "./src/plugins/home/tsconfig.json" }, { "path": "./src/plugins/inspector/tsconfig.json" }, { "path": "./src/plugins/kibana_legacy/tsconfig.json" }, + { "path": "./src/plugins/kibana_overview/tsconfig.json" }, { "path": "./src/plugins/kibana_react/tsconfig.json" }, { "path": "./src/plugins/kibana_usage_collection/tsconfig.json" }, { "path": "./src/plugins/kibana_utils/tsconfig.json" }, diff --git a/tsconfig.refs.json b/tsconfig.refs.json index 1d08e764709ca..17b1fc5dc1fe9 100644 --- a/tsconfig.refs.json +++ b/tsconfig.refs.json @@ -17,6 +17,7 @@ { "path": "./src/plugins/home/tsconfig.json" }, { "path": "./src/plugins/inspector/tsconfig.json" }, { "path": "./src/plugins/kibana_legacy/tsconfig.json" }, + { "path": "./src/plugins/kibana_overview/tsconfig.json" }, { "path": "./src/plugins/kibana_react/tsconfig.json" }, { "path": "./src/plugins/kibana_usage_collection/tsconfig.json" }, { "path": "./src/plugins/kibana_utils/tsconfig.json" }, diff --git a/vars/githubPr.groovy b/vars/githubPr.groovy index 5224aa7463d79..eead00c082ba7 100644 --- a/vars/githubPr.groovy +++ b/vars/githubPr.groovy @@ -300,7 +300,12 @@ def getDocsChangesLink() { try { // httpRequest throws on status codes >400 and failures - httpRequest([ method: "GET", url: url ]) + def resp = httpRequest([ method: "GET", url: url ]) + + if (resp.contains("There aren't any differences!")) { + return "" + } + return "* [Documentation Changes](${url})" } catch (ex) { print "Failed to reach ${url}" diff --git a/vars/kibanaPipeline.groovy b/vars/kibanaPipeline.groovy index 3032d88c26d98..17349f6b566dc 100644 --- a/vars/kibanaPipeline.groovy +++ b/vars/kibanaPipeline.groovy @@ -128,9 +128,11 @@ def functionalTestProcess(String name, String script) { } } -def ossCiGroupProcess(ciGroup) { +def ossCiGroupProcess(ciGroup, withDelay = false) { return functionalTestProcess("ciGroup" + ciGroup) { - sleep((ciGroup-1)*30) // smooth out CPU spikes from ES startup + if (withDelay) { + sleep((ciGroup-1)*30) // smooth out CPU spikes from ES startup + } withEnv([ "CI_GROUP=${ciGroup}", @@ -143,9 +145,11 @@ def ossCiGroupProcess(ciGroup) { } } -def xpackCiGroupProcess(ciGroup) { +def xpackCiGroupProcess(ciGroup, withDelay = false) { return functionalTestProcess("xpack-ciGroup" + ciGroup) { - sleep((ciGroup-1)*30) // smooth out CPU spikes from ES startup + if (withDelay) { + sleep((ciGroup-1)*30) // smooth out CPU spikes from ES startup + } withEnv([ "CI_GROUP=${ciGroup}", "JOB=xpack-kibana-ciGroup${ciGroup}", diff --git a/vars/prChanges.groovy b/vars/prChanges.groovy index 2cc22e73857b0..d082672c065a8 100644 --- a/vars/prChanges.groovy +++ b/vars/prChanges.groovy @@ -11,10 +11,8 @@ def getSkippablePaths() { /^.ci\/.+\.yml$/, /^.ci\/es-snapshots\//, /^.ci\/pipeline-library\//, - /^.ci\/teamcity\//, /^.ci\/Jenkinsfile_[^\/]+$/, /^\.github\//, - /^\.teamcity\//, /\.md$/, ] } diff --git a/vars/tasks.groovy b/vars/tasks.groovy index 6c4f897691136..7c40966ff5e04 100644 --- a/vars/tasks.groovy +++ b/vars/tasks.groovy @@ -51,7 +51,7 @@ def functionalOss(Map params = [:]) { if (config.ciGroups) { def ciGroups = 1..12 - tasks(ciGroups.collect { kibanaPipeline.ossCiGroupProcess(it) }) + tasks(ciGroups.collect { kibanaPipeline.ossCiGroupProcess(it, true) }) } if (config.firefox) { @@ -92,7 +92,7 @@ def functionalXpack(Map params = [:]) { if (config.ciGroups) { def ciGroups = 1..13 - tasks(ciGroups.collect { kibanaPipeline.xpackCiGroupProcess(it) }) + tasks(ciGroups.collect { kibanaPipeline.xpackCiGroupProcess(it, true) }) } if (config.firefox) { diff --git a/x-pack/examples/embedded_lens_example/public/app.tsx b/x-pack/examples/embedded_lens_example/public/app.tsx index 9f35907ca335d..661e01038c5d6 100644 --- a/x-pack/examples/embedded_lens_example/public/app.tsx +++ b/x-pack/examples/embedded_lens_example/public/app.tsx @@ -19,7 +19,11 @@ import { } from '@elastic/eui'; import { IndexPattern } from 'src/plugins/data/public'; import { CoreStart } from 'kibana/public'; -import { TypedLensByValueInput } from '../../../plugins/lens/public'; +import { + TypedLensByValueInput, + PersistedIndexPatternLayer, + XYState, +} from '../../../plugins/lens/public'; import { StartDependencies } from './plugin'; // Generate a Lens state based on some app-specific input parameters. @@ -28,6 +32,48 @@ function getLensAttributes( defaultIndexPattern: IndexPattern, color: string ): TypedLensByValueInput['attributes'] { + const dataLayer: PersistedIndexPatternLayer = { + columnOrder: ['col1', 'col2'], + columns: { + col2: { + dataType: 'number', + isBucketed: false, + label: 'Count of records', + operationType: 'count', + scale: 'ratio', + sourceField: 'Records', + }, + col1: { + dataType: 'date', + isBucketed: true, + label: '@timestamp', + operationType: 'date_histogram', + params: { interval: 'auto' }, + scale: 'interval', + sourceField: defaultIndexPattern.timeFieldName!, + }, + }, + }; + + const xyConfig: XYState = { + axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + fittingFunction: 'None', + gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, + layers: [ + { + accessors: ['col2'], + layerId: 'layer1', + seriesType: 'bar_stacked', + xAccessor: 'col1', + yConfig: [{ forAccessor: 'col2', color }], + }, + ], + legend: { isVisible: true, position: 'right' }, + preferredSeriesType: 'bar_stacked', + tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true }, + valueLabels: 'hide', + }; + return { visualizationType: 'lnsXY', title: 'Prefilled from example app', @@ -47,51 +93,13 @@ function getLensAttributes( datasourceStates: { indexpattern: { layers: { - layer1: { - columnOrder: ['col1', 'col2'], - columns: { - col2: { - dataType: 'number', - isBucketed: false, - label: 'Count of records', - operationType: 'count', - scale: 'ratio', - sourceField: 'Records', - }, - col1: { - dataType: 'date', - isBucketed: true, - label: '@timestamp', - operationType: 'date_histogram', - params: { interval: 'auto' }, - scale: 'interval', - sourceField: defaultIndexPattern.timeFieldName!, - }, - }, - }, + layer1: dataLayer, }, }, }, filters: [], query: { language: 'kuery', query: '' }, - visualization: { - axisTitlesVisibilitySettings: { x: true, yLeft: true, yRight: true }, - fittingFunction: 'None', - gridlinesVisibilitySettings: { x: true, yLeft: true, yRight: true }, - layers: [ - { - accessors: ['col2'], - layerId: 'layer1', - seriesType: 'bar_stacked', - xAccessor: 'col1', - yConfig: [{ forAccessor: 'col2', color }], - }, - ], - legend: { isVisible: true, position: 'right' }, - preferredSeriesType: 'bar_stacked', - tickLabelsVisibilitySettings: { x: true, yLeft: true, yRight: true }, - valueLabels: 'hide', - }, + visualization: xyConfig, }, }; } diff --git a/x-pack/plugins/actions/README.md b/x-pack/plugins/actions/README.md index 9472cbf400a6a..1eb94af4dddf8 100644 --- a/x-pack/plugins/actions/README.md +++ b/x-pack/plugins/actions/README.md @@ -70,12 +70,14 @@ Table of Contents - [`params`](#params-6) - [`subActionParams (pushToService)`](#subactionparams-pushtoservice) - [`subActionParams (getFields)`](#subactionparams-getfields) + - [`subActionParams (getIncident)`](#subactionparams-getincident) + - [`subActionParams (getChoices)`](#subactionparams-getchoices) - [Jira](#jira) - [`config`](#config-7) - [`secrets`](#secrets-7) - [`params`](#params-7) - [`subActionParams (pushToService)`](#subactionparams-pushtoservice-1) - - [`subActionParams (getIncident)`](#subactionparams-getincident) + - [`subActionParams (getIncident)`](#subactionparams-getincident-1) - [`subActionParams (issueTypes)`](#subactionparams-issuetypes) - [`subActionParams (fieldsByIssueType)`](#subactionparams-fieldsbyissuetype) - [`subActionParams (issues)`](#subactionparams-issues) @@ -347,17 +349,18 @@ const result = await actionsClient.execute({ Kibana ships with a set of built-in action types: -| Type | Id | Description | -| ------------------------------- | ------------- | ------------------------------------------------------------------ | -| [Server log](#server-log) | `.server-log` | Logs messages to the Kibana log using Kibana's logger | -| [Email](#email) | `.email` | Sends an email using SMTP | -| [Slack](#slack) | `.slack` | Posts a message to a slack channel | -| [Index](#index) | `.index` | Indexes document(s) into Elasticsearch | -| [Webhook](#webhook) | `.webhook` | Send a payload to a web service using HTTP POST or PUT | -| [PagerDuty](#pagerduty) | `.pagerduty` | Trigger, resolve, or acknowlege an incident to a PagerDuty service | -| [ServiceNow](#servicenow) | `.servicenow` | Create or update an incident to a ServiceNow instance | -| [Jira](#jira) | `.jira` | Create or update an issue to a Jira instance | -| [IBM Resilient](#ibm-resilient) | `.resilient` | Create or update an incident to a IBM Resilient instance | +| Type | Id | Description | +| ------------------------------- | ----------------- | ------------------------------------------------------------------ | +| [Server log](#server-log) | `.server-log` | Logs messages to the Kibana log using Kibana's logger | +| [Email](#email) | `.email` | Sends an email using SMTP | +| [Slack](#slack) | `.slack` | Posts a message to a slack channel | +| [Index](#index) | `.index` | Indexes document(s) into Elasticsearch | +| [Webhook](#webhook) | `.webhook` | Send a payload to a web service using HTTP POST or PUT | +| [PagerDuty](#pagerduty) | `.pagerduty` | Trigger, resolve, or acknowlege an incident to a PagerDuty service | +| [ServiceNow ITSM](#servicenow) | `.servicenow` | Create or update an incident to a ServiceNow ITSM instance | +| [ServiceNow SIR](#servicenow) | `.servicenow-sir` | Create or update an incident to a ServiceNow SIR instance | +| [Jira](#jira) | `.jira` | Create or update an issue to a Jira instance | +| [IBM Resilient](#ibm-resilient) | `.resilient` | Create or update an incident to a IBM Resilient instance | --- @@ -549,9 +552,11 @@ For more details see [PagerDuty v2 event parameters](https://v2.developer.pagerd ## ServiceNow -ID: `.servicenow` +ServiceNow ITSM ID: `.servicenow` -The ServiceNow action uses the [V2 Table API](https://developer.servicenow.com/app.do#!/rest_api_doc?v=orlando&id=c_TableAPI) to create and update ServiceNow incidents. +ServiceNow SIR ID: `.servicenow-sir` + +The ServiceNow actions use the [V2 Table API](https://developer.servicenow.com/app.do#!/rest_api_doc?v=orlando&id=c_TableAPI) to create and update ServiceNow incidents. Both action types use the same `config`, `secrets`, and `params` schema. ### `config` @@ -568,10 +573,10 @@ The ServiceNow action uses the [V2 Table API](https://developer.servicenow.com/a ### `params` -| Property | Description | Type | -| --------------- | --------------------------------------------------------------------- | ------ | -| subAction | The sub action to perform. It can be `getFields`, and `pushToService` | string | -| subActionParams | The parameters of the sub action | object | +| Property | Description | Type | +| --------------- | -------------------------------------------------------------------------------------------------- | ------ | +| subAction | The sub action to perform. It can be `pushToService`, `getFields`, `getIncident`, and `getChoices` | string | +| subActionParams | The parameters of the sub action | object | #### `subActionParams (pushToService)` @@ -595,6 +600,19 @@ The following table describes the properties of the `incident` object. No parameters for `getFields` sub-action. Provide an empty object `{}`. +#### `subActionParams (getIncident)` + +| Property | Description | Type | +| ---------- | ------------------------------------- | ------ | +| externalId | The id of the incident in ServiceNow. | string | + + +#### `subActionParams (getChoices)` + +| Property | Description | Type | +| -------- | ------------------------------------------------------------ | -------- | +| fields | An array of fields. Example: `[priority, category, impact]`. | string[] | + --- ## Jira diff --git a/x-pack/plugins/actions/server/builtin_action_types/index.ts b/x-pack/plugins/actions/server/builtin_action_types/index.ts index 3a01b875ec4a0..21161ff8ad0dd 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/index.ts @@ -14,7 +14,7 @@ import { getActionType as getPagerDutyActionType } from './pagerduty'; import { getActionType as getServerLogActionType } from './server_log'; import { getActionType as getSlackActionType } from './slack'; import { getActionType as getWebhookActionType } from './webhook'; -import { getActionType as getServiceNowActionType } from './servicenow'; +import { getServiceNowITSMActionType, getServiceNowSIRActionType } from './servicenow'; import { getActionType as getJiraActionType } from './jira'; import { getActionType as getResilientActionType } from './resilient'; import { getActionType as getTeamsActionType } from './teams'; @@ -38,7 +38,8 @@ export { } from './webhook'; export { ActionParamsType as ServiceNowActionParams, - ActionTypeId as ServiceNowActionTypeId, + ServiceNowITSMActionTypeId, + ServiceNowSIRActionTypeId, } from './servicenow'; export { ActionParamsType as JiraActionParams, ActionTypeId as JiraActionTypeId } from './jira'; export { @@ -66,7 +67,8 @@ export function registerBuiltInActionTypes({ actionTypeRegistry.register(getServerLogActionType({ logger })); actionTypeRegistry.register(getSlackActionType({ logger, configurationUtilities })); actionTypeRegistry.register(getWebhookActionType({ logger, configurationUtilities })); - actionTypeRegistry.register(getServiceNowActionType({ logger, configurationUtilities })); + actionTypeRegistry.register(getServiceNowITSMActionType({ logger, configurationUtilities })); + actionTypeRegistry.register(getServiceNowSIRActionType({ logger, configurationUtilities })); actionTypeRegistry.register(getJiraActionType({ logger, configurationUtilities })); actionTypeRegistry.register(getResilientActionType({ logger, configurationUtilities })); actionTypeRegistry.register(getTeamsActionType({ logger, configurationUtilities })); diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.test.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.test.ts index 23e16b7463914..a4db25310c793 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.test.ts @@ -10,7 +10,7 @@ import { Logger } from '../../../../../../src/core/server'; import { addTimeZoneToDate, request, patch, getErrorMessage } from './axios_utils'; import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; import { actionsConfigMock } from '../../actions_config.mock'; -import { getProxyAgents } from './get_proxy_agents'; +import { getCustomAgents } from './get_custom_agents'; const logger = loggingSystemMock.create().get() as jest.Mocked; const configurationUtilities = actionsConfigMock.create(); @@ -66,7 +66,7 @@ describe('request', () => { proxyRejectUnauthorizedCertificates: true, proxyUrl: 'https://localhost:1212', }); - const { httpAgent, httpsAgent } = getProxyAgents(configurationUtilities, logger); + const { httpAgent, httpsAgent } = getCustomAgents(configurationUtilities, logger); const res = await request({ axios, diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.ts index a70a452737dc6..9a8c4e09ad553 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.ts @@ -6,7 +6,7 @@ import { AxiosInstance, Method, AxiosResponse, AxiosBasicCredentials } from 'axios'; import { Logger } from '../../../../../../src/core/server'; -import { getProxyAgents } from './get_proxy_agents'; +import { getCustomAgents } from './get_custom_agents'; import { ActionsConfigurationUtilities } from '../../actions_config'; export const request = async ({ @@ -29,7 +29,7 @@ export const request = async ({ validateStatus?: (status: number) => boolean; auth?: AxiosBasicCredentials; }): Promise => { - const { httpAgent, httpsAgent } = getProxyAgents(configurationUtilities, logger); + const { httpAgent, httpsAgent } = getCustomAgents(configurationUtilities, logger); return await axios(url, { ...rest, diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agents.test.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/get_custom_agents.test.ts similarity index 81% rename from x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agents.test.ts rename to x-pack/plugins/actions/server/builtin_action_types/lib/get_custom_agents.test.ts index da2ad9bb3990d..cc2f729a033a9 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agents.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/get_custom_agents.test.ts @@ -8,12 +8,12 @@ import { Agent as HttpsAgent } from 'https'; import HttpProxyAgent from 'http-proxy-agent'; import { HttpsProxyAgent } from 'https-proxy-agent'; import { Logger } from '../../../../../../src/core/server'; -import { getProxyAgents } from './get_proxy_agents'; +import { getCustomAgents } from './get_custom_agents'; import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; import { actionsConfigMock } from '../../actions_config.mock'; const logger = loggingSystemMock.create().get() as jest.Mocked; -describe('getProxyAgents', () => { +describe('getCustomAgents', () => { const configurationUtilities = actionsConfigMock.create(); test('get agents for valid proxy URL', () => { @@ -21,7 +21,7 @@ describe('getProxyAgents', () => { proxyUrl: 'https://someproxyhost', proxyRejectUnauthorizedCertificates: false, }); - const { httpAgent, httpsAgent } = getProxyAgents(configurationUtilities, logger); + const { httpAgent, httpsAgent } = getCustomAgents(configurationUtilities, logger); expect(httpAgent instanceof HttpProxyAgent).toBeTruthy(); expect(httpsAgent instanceof HttpsProxyAgent).toBeTruthy(); }); @@ -31,13 +31,13 @@ describe('getProxyAgents', () => { proxyUrl: ':nope: not a valid URL', proxyRejectUnauthorizedCertificates: false, }); - const { httpAgent, httpsAgent } = getProxyAgents(configurationUtilities, logger); + const { httpAgent, httpsAgent } = getCustomAgents(configurationUtilities, logger); expect(httpAgent).toBe(undefined); expect(httpsAgent instanceof HttpsAgent).toBeTruthy(); }); test('return default agents for undefined proxy options', () => { - const { httpAgent, httpsAgent } = getProxyAgents(configurationUtilities, logger); + const { httpAgent, httpsAgent } = getCustomAgents(configurationUtilities, logger); expect(httpAgent).toBe(undefined); expect(httpsAgent instanceof HttpsAgent).toBeTruthy(); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agents.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/get_custom_agents.ts similarity index 90% rename from x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agents.ts rename to x-pack/plugins/actions/server/builtin_action_types/lib/get_custom_agents.ts index a49889570f4bf..ad97dd5023f80 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agents.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/get_custom_agents.ts @@ -11,17 +11,17 @@ import { HttpsProxyAgent } from 'https-proxy-agent'; import { Logger } from '../../../../../../src/core/server'; import { ActionsConfigurationUtilities } from '../../actions_config'; -interface GetProxyAgentsResponse { +interface GetCustomAgentsResponse { httpAgent: HttpAgent | undefined; httpsAgent: HttpsAgent | undefined; } -export function getProxyAgents( +export function getCustomAgents( configurationUtilities: ActionsConfigurationUtilities, logger: Logger -): GetProxyAgentsResponse { +): GetCustomAgentsResponse { const proxySettings = configurationUtilities.getProxySettings(); - const defaultResponse = { + const defaultAgents = { httpAgent: undefined, httpsAgent: new HttpsAgent({ rejectUnauthorized: configurationUtilities.isRejectUnauthorizedCertificatesEnabled(), @@ -29,7 +29,7 @@ export function getProxyAgents( }; if (!proxySettings) { - return defaultResponse; + return defaultAgents; } logger.debug(`Creating proxy agents for proxy: ${proxySettings.proxyUrl}`); @@ -38,7 +38,7 @@ export function getProxyAgents( proxyUrl = new URL(proxySettings.proxyUrl); } catch (err) { logger.warn(`invalid proxy URL "${proxySettings.proxyUrl}" ignored`); - return defaultResponse; + return defaultAgents; } const httpAgent = new HttpProxyAgent(proxySettings.proxyUrl); diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.test.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.test.ts index 772cd16cc4d51..ef5de9fc487bc 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.test.ts @@ -5,7 +5,7 @@ */ import { Logger } from '../../../../../../src/core/server'; -import { externalServiceMock, apiParams, serviceNowCommonFields } from './mocks'; +import { externalServiceMock, apiParams, serviceNowCommonFields, serviceNowChoices } from './mocks'; import { ExternalService } from './types'; import { api } from './api'; let mockedLogger: jest.Mocked; @@ -235,4 +235,14 @@ describe('api', () => { expect(res).toEqual(serviceNowCommonFields); }); }); + + describe('getChoices', () => { + test('it returns the fields correctly', async () => { + const res = await api.getChoices({ + externalService, + params: { fields: ['priority'] }, + }); + expect(res).toEqual(serviceNowChoices); + }); + }); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.ts index 9981a8431a736..7f5747277a4e9 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/api.ts @@ -5,6 +5,8 @@ */ import { ExternalServiceApi, + GetChoicesHandlerArgs, + GetChoicesResponse, GetCommonFieldsHandlerArgs, GetCommonFieldsResponse, GetIncidentApiHandlerArgs, @@ -71,7 +73,16 @@ const getFieldsHandler = async ({ return res; }; +const getChoicesHandler = async ({ + externalService, + params, +}: GetChoicesHandlerArgs): Promise => { + const res = await externalService.getChoices(params.fields); + return res; +}; + export const api: ExternalServiceApi = { + getChoices: getChoicesHandler, getFields: getFieldsHandler, getIncident: getIncidentHandler, handshake: handshakeHandler, diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts index 107d86f111deb..fd4991e5f7e98 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts @@ -11,7 +11,8 @@ import { validate } from './validators'; import { ExternalIncidentServiceConfiguration, ExternalIncidentServiceSecretConfiguration, - ExecutorParamsSchema, + ExecutorParamsSchemaITSM, + ExecutorParamsSchemaSIR, } from './schema'; import { ActionsConfigurationUtilities } from '../../actions_config'; import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../../types'; @@ -27,18 +28,26 @@ import { PushToServiceResponse, ExecutorSubActionCommonFieldsParams, ServiceNowExecutorResultData, + ExecutorSubActionGetChoicesParams, } from './types'; -export type ActionParamsType = TypeOf; +export type ActionParamsType = + | TypeOf + | TypeOf; interface GetActionTypeParams { logger: Logger; configurationUtilities: ActionsConfigurationUtilities; } -export const ActionTypeId = '.servicenow'; +const serviceNowITSMTable = 'incident'; +const serviceNowSIRTable = 'sn_si_incident'; + +export const ServiceNowITSMActionTypeId = '.servicenow'; +export const ServiceNowSIRActionTypeId = '.servicenow-sir'; + // action type definition -export function getActionType( +export function getServiceNowITSMActionType( params: GetActionTypeParams ): ActionType< ServiceNowPublicConfigurationType, @@ -48,9 +57,9 @@ export function getActionType( > { const { logger, configurationUtilities } = params; return { - id: ActionTypeId, + id: ServiceNowITSMActionTypeId, minimumLicenseRequired: 'platinum', - name: i18n.NAME, + name: i18n.SERVICENOW_ITSM, validate: { config: schema.object(ExternalIncidentServiceConfiguration, { validate: curry(validate.config)(configurationUtilities), @@ -58,19 +67,46 @@ export function getActionType( secrets: schema.object(ExternalIncidentServiceSecretConfiguration, { validate: curry(validate.secrets)(configurationUtilities), }), - params: ExecutorParamsSchema, + params: ExecutorParamsSchemaITSM, }, - executor: curry(executor)({ logger, configurationUtilities }), + executor: curry(executor)({ logger, configurationUtilities, table: serviceNowITSMTable }), + }; +} + +export function getServiceNowSIRActionType( + params: GetActionTypeParams +): ActionType< + ServiceNowPublicConfigurationType, + ServiceNowSecretConfigurationType, + ExecutorParams, + PushToServiceResponse | {} +> { + const { logger, configurationUtilities } = params; + return { + id: ServiceNowSIRActionTypeId, + minimumLicenseRequired: 'platinum', + name: i18n.SERVICENOW_SIR, + validate: { + config: schema.object(ExternalIncidentServiceConfiguration, { + validate: curry(validate.config)(configurationUtilities), + }), + secrets: schema.object(ExternalIncidentServiceSecretConfiguration, { + validate: curry(validate.secrets)(configurationUtilities), + }), + params: ExecutorParamsSchemaSIR, + }, + executor: curry(executor)({ logger, configurationUtilities, table: serviceNowSIRTable }), }; } // action executor -const supportedSubActions: string[] = ['getFields', 'pushToService']; +const supportedSubActions: string[] = ['getFields', 'pushToService', 'getChoices', 'getIncident']; async function executor( { logger, configurationUtilities, - }: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities }, + table, + }: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities; table: string }, execOptions: ActionTypeExecutorOptions< ServiceNowPublicConfigurationType, ServiceNowSecretConfigurationType, @@ -82,6 +118,7 @@ async function executor( let data: ServiceNowExecutorResultData | null = null; const externalService = createExternalService( + table, { config, secrets, @@ -122,5 +159,13 @@ async function executor( }); } + if (subAction === 'getChoices') { + const getChoicesParams = subActionParams as ExecutorSubActionGetChoicesParams; + data = await api.getChoices({ + externalService, + params: getChoicesParams, + }); + } + return { status: 'ok', data: data ?? {}, actionId }; } diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/mocks.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/mocks.ts index 9d9b1e164e7dd..f958cdb73ebfc 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/mocks.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/mocks.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ExternalService, PushToServiceApiParams, ExecutorSubActionPushParams } from './types'; +import { ExternalService, ExecutorSubActionPushParams } from './types'; export const serviceNowCommonFields = [ { @@ -33,8 +33,43 @@ export const serviceNowCommonFields = [ element: 'sys_updated_by', }, ]; + +export const serviceNowChoices = [ + { + dependent_value: '', + label: '1 - Critical', + value: '1', + element: 'priority', + }, + { + dependent_value: '', + label: '2 - High', + value: '2', + element: 'priority', + }, + { + dependent_value: '', + label: '3 - Moderate', + value: '3', + element: 'priority', + }, + { + dependent_value: '', + label: '4 - Low', + value: '4', + element: 'priority', + }, + { + dependent_value: '', + label: '5 - Planning', + value: '5', + element: 'priority', + }, +]; + const createMock = (): jest.Mocked => { const service = { + getChoices: jest.fn().mockImplementation(() => Promise.resolve(serviceNowChoices)), getFields: jest.fn().mockImplementation(() => Promise.resolve(serviceNowCommonFields)), getIncident: jest.fn().mockImplementation(() => Promise.resolve({ @@ -89,8 +124,6 @@ const executorParams: ExecutorSubActionPushParams = { ], }; -const apiParams: PushToServiceApiParams = { - ...executorParams, -}; +const apiParams = executorParams; export { externalServiceMock, executorParams, apiParams }; diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/schema.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/schema.ts index 1c05fa93f2362..5c7de935223a8 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/schema.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/schema.ts @@ -28,25 +28,48 @@ export const ExecutorSubActionSchema = schema.oneOf([ schema.literal('getIncident'), schema.literal('pushToService'), schema.literal('handshake'), + schema.literal('getChoices'), ]); -export const ExecutorSubActionPushParamsSchema = schema.object({ +const CommentsSchema = schema.nullable( + schema.arrayOf( + schema.object({ + comment: schema.string(), + commentId: schema.string(), + }) + ) +); + +const CommonAttributes = { + short_description: schema.string(), + description: schema.nullable(schema.string()), + externalId: schema.nullable(schema.string()), +}; + +// Schema for ServiceNow Incident Management (ITSM) +export const ExecutorSubActionPushParamsSchemaITSM = schema.object({ incident: schema.object({ - short_description: schema.string(), - description: schema.nullable(schema.string()), - externalId: schema.nullable(schema.string()), + ...CommonAttributes, severity: schema.nullable(schema.string()), urgency: schema.nullable(schema.string()), impact: schema.nullable(schema.string()), }), - comments: schema.nullable( - schema.arrayOf( - schema.object({ - comment: schema.string(), - commentId: schema.string(), - }) - ) - ), + comments: CommentsSchema, +}); + +// Schema for ServiceNow Security Incident Response (SIR) +export const ExecutorSubActionPushParamsSchemaSIR = schema.object({ + incident: schema.object({ + ...CommonAttributes, + category: schema.nullable(schema.string()), + dest_ip: schema.nullable(schema.string()), + malware_hash: schema.nullable(schema.string()), + malware_url: schema.nullable(schema.string()), + priority: schema.nullable(schema.string()), + source_ip: schema.nullable(schema.string()), + subcategory: schema.nullable(schema.string()), + }), + comments: CommentsSchema, }); export const ExecutorSubActionGetIncidentParamsSchema = schema.object({ @@ -56,8 +79,36 @@ export const ExecutorSubActionGetIncidentParamsSchema = schema.object({ // Reserved for future implementation export const ExecutorSubActionHandshakeParamsSchema = schema.object({}); export const ExecutorSubActionCommonFieldsParamsSchema = schema.object({}); +export const ExecutorSubActionGetChoicesParamsSchema = schema.object({ + fields: schema.arrayOf(schema.string()), +}); + +// Executor parameters for ServiceNow Incident Management (ITSM) +export const ExecutorParamsSchemaITSM = schema.oneOf([ + schema.object({ + subAction: schema.literal('getFields'), + subActionParams: ExecutorSubActionCommonFieldsParamsSchema, + }), + schema.object({ + subAction: schema.literal('getIncident'), + subActionParams: ExecutorSubActionGetIncidentParamsSchema, + }), + schema.object({ + subAction: schema.literal('handshake'), + subActionParams: ExecutorSubActionHandshakeParamsSchema, + }), + schema.object({ + subAction: schema.literal('pushToService'), + subActionParams: ExecutorSubActionPushParamsSchemaITSM, + }), + schema.object({ + subAction: schema.literal('getChoices'), + subActionParams: ExecutorSubActionGetChoicesParamsSchema, + }), +]); -export const ExecutorParamsSchema = schema.oneOf([ +// Executor parameters for ServiceNow Security Incident Response (SIR) +export const ExecutorParamsSchemaSIR = schema.oneOf([ schema.object({ subAction: schema.literal('getFields'), subActionParams: ExecutorSubActionCommonFieldsParamsSchema, @@ -72,6 +123,10 @@ export const ExecutorParamsSchema = schema.oneOf([ }), schema.object({ subAction: schema.literal('pushToService'), - subActionParams: ExecutorSubActionPushParamsSchema, + subActionParams: ExecutorSubActionPushParamsSchemaSIR, + }), + schema.object({ + subAction: schema.literal('getChoices'), + subActionParams: ExecutorSubActionGetChoicesParamsSchema, }), ]); diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts index 4ef0e7da166e6..18f3a2f3ff379 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts @@ -12,7 +12,7 @@ import { ExternalService } from './types'; import { Logger } from '../../../../../../src/core/server'; import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; import { actionsConfigMock } from '../../actions_config.mock'; -import { serviceNowCommonFields } from './mocks'; +import { serviceNowCommonFields, serviceNowChoices } from './mocks'; const logger = loggingSystemMock.create().get() as jest.Mocked; jest.mock('axios'); @@ -29,12 +29,14 @@ axios.create = jest.fn(() => axios); const requestMock = utils.request as jest.Mock; const patchMock = utils.patch as jest.Mock; const configurationUtilities = actionsConfigMock.create(); +const table = 'incident'; describe('ServiceNow service', () => { let service: ExternalService; - beforeAll(() => { + beforeEach(() => { service = createExternalService( + table, { // The trailing slash at the end of the url is intended. // All API calls need to have the trailing slash removed. @@ -54,6 +56,7 @@ describe('ServiceNow service', () => { test('throws without url', () => { expect(() => createExternalService( + table, { config: { apiUrl: null }, secrets: { username: 'admin', password: 'admin' }, @@ -67,6 +70,7 @@ describe('ServiceNow service', () => { test('throws without username', () => { expect(() => createExternalService( + table, { config: { apiUrl: 'test.com' }, secrets: { username: '', password: 'admin' }, @@ -80,6 +84,7 @@ describe('ServiceNow service', () => { test('throws without password', () => { expect(() => createExternalService( + table, { config: { apiUrl: 'test.com' }, secrets: { username: '', password: undefined }, @@ -114,6 +119,30 @@ describe('ServiceNow service', () => { }); }); + test('it should call request with correct arguments when table changes', async () => { + service = createExternalService( + 'sn_si_incident', + { + config: { apiUrl: 'https://dev102283.service-now.com/' }, + secrets: { username: 'admin', password: 'admin' }, + }, + logger, + configurationUtilities + ); + + requestMock.mockImplementation(() => ({ + data: { result: { sys_id: '1', number: 'INC01' } }, + })); + + await service.getIncident('1'); + expect(requestMock).toHaveBeenCalledWith({ + axios, + logger, + configurationUtilities, + url: 'https://dev102283.service-now.com/api/now/v2/table/sn_si_incident/1', + }); + }); + test('it should throw an error', async () => { requestMock.mockImplementation(() => { throw new Error('An error has occurred'); @@ -122,6 +151,17 @@ describe('ServiceNow service', () => { 'Unable to get incident with id 1. Error: An error has occurred' ); }); + + test('it should throw an error when instance is not alive', async () => { + requestMock.mockImplementation(() => ({ + status: 200, + data: {}, + request: { connection: { servername: 'Developer instance' } }, + })); + await expect(service.getIncident('1')).rejects.toThrow( + 'There is an issue with your Service Now Instance. Please check Developer instance.' + ); + }); }); describe('createIncident', () => { @@ -161,6 +201,39 @@ describe('ServiceNow service', () => { }); }); + test('it should call request with correct arguments when table changes', async () => { + service = createExternalService( + 'sn_si_incident', + { + config: { apiUrl: 'https://dev102283.service-now.com/' }, + secrets: { username: 'admin', password: 'admin' }, + }, + logger, + configurationUtilities + ); + + requestMock.mockImplementation(() => ({ + data: { result: { sys_id: '1', number: 'INC01', sys_created_on: '2020-03-10 12:24:20' } }, + })); + + const res = await service.createIncident({ + incident: { short_description: 'title', description: 'desc' }, + }); + + expect(requestMock).toHaveBeenCalledWith({ + axios, + logger, + configurationUtilities, + url: 'https://dev102283.service-now.com/api/now/v2/table/sn_si_incident', + method: 'post', + data: { short_description: 'title', description: 'desc' }, + }); + + expect(res.url).toEqual( + 'https://dev102283.service-now.com/nav_to.do?uri=sn_si_incident.do?sys_id=1' + ); + }); + test('it should throw an error', async () => { requestMock.mockImplementation(() => { throw new Error('An error has occurred'); @@ -174,6 +247,17 @@ describe('ServiceNow service', () => { '[Action][ServiceNow]: Unable to create incident. Error: An error has occurred' ); }); + + test('it should throw an error when instance is not alive', async () => { + requestMock.mockImplementation(() => ({ + status: 200, + data: {}, + request: { connection: { servername: 'Developer instance' } }, + })); + await expect(service.getIncident('1')).rejects.toThrow( + 'There is an issue with your Service Now Instance. Please check Developer instance.' + ); + }); }); describe('updateIncident', () => { @@ -214,6 +298,39 @@ describe('ServiceNow service', () => { }); }); + test('it should call request with correct arguments when table changes', async () => { + service = createExternalService( + 'sn_si_incident', + { + config: { apiUrl: 'https://dev102283.service-now.com/' }, + secrets: { username: 'admin', password: 'admin' }, + }, + logger, + configurationUtilities + ); + + patchMock.mockImplementation(() => ({ + data: { result: { sys_id: '1', number: 'INC01', sys_updated_on: '2020-03-10 12:24:20' } }, + })); + + const res = await service.updateIncident({ + incidentId: '1', + incident: { short_description: 'title', description: 'desc' }, + }); + + expect(patchMock).toHaveBeenCalledWith({ + axios, + logger, + configurationUtilities, + url: 'https://dev102283.service-now.com/api/now/v2/table/sn_si_incident/1', + data: { short_description: 'title', description: 'desc' }, + }); + + expect(res.url).toEqual( + 'https://dev102283.service-now.com/nav_to.do?uri=sn_si_incident.do?sys_id=1' + ); + }); + test('it should throw an error', async () => { patchMock.mockImplementation(() => { throw new Error('An error has occurred'); @@ -228,6 +345,7 @@ describe('ServiceNow service', () => { '[Action][ServiceNow]: Unable to update incident with id 1. Error: An error has occurred' ); }); + test('it creates the comment correctly', async () => { patchMock.mockImplementation(() => ({ data: { result: { sys_id: '11', number: 'INC011', sys_updated_on: '2020-03-10 12:24:20' } }, @@ -245,6 +363,17 @@ describe('ServiceNow service', () => { url: 'https://dev102283.service-now.com/nav_to.do?uri=incident.do?sys_id=11', }); }); + + test('it should throw an error when instance is not alive', async () => { + requestMock.mockImplementation(() => ({ + status: 200, + data: {}, + request: { connection: { servername: 'Developer instance' } }, + })); + await expect(service.getIncident('1')).rejects.toThrow( + 'There is an issue with your Service Now Instance. Please check Developer instance.' + ); + }); }); describe('getFields', () => { @@ -259,9 +388,10 @@ describe('ServiceNow service', () => { logger, configurationUtilities, url: - 'https://dev102283.service-now.com/api/now/v2/table/sys_dictionary?sysparm_query=name=task^internal_type=string&active=true&array=false&read_only=false&sysparm_fields=max_length,element,column_label,mandatory', + 'https://dev102283.service-now.com/api/now/v2/table/sys_dictionary?sysparm_query=name=task^ORname=incident^internal_type=string&active=true&array=false&read_only=false&sysparm_fields=max_length,element,column_label,mandatory', }); }); + test('it returns common fields correctly', async () => { requestMock.mockImplementation(() => ({ data: { result: serviceNowCommonFields }, @@ -270,6 +400,31 @@ describe('ServiceNow service', () => { expect(res).toEqual(serviceNowCommonFields); }); + test('it should call request with correct arguments when table changes', async () => { + service = createExternalService( + 'sn_si_incident', + { + config: { apiUrl: 'https://dev102283.service-now.com/' }, + secrets: { username: 'admin', password: 'admin' }, + }, + logger, + configurationUtilities + ); + + requestMock.mockImplementation(() => ({ + data: { result: serviceNowCommonFields }, + })); + await service.getFields(); + + expect(requestMock).toHaveBeenCalledWith({ + axios, + logger, + configurationUtilities, + url: + 'https://dev102283.service-now.com/api/now/v2/table/sys_dictionary?sysparm_query=name=task^ORname=sn_si_incident^internal_type=string&active=true&array=false&read_only=false&sysparm_fields=max_length,element,column_label,mandatory', + }); + }); + test('it should throw an error', async () => { requestMock.mockImplementation(() => { throw new Error('An error has occurred'); @@ -278,5 +433,87 @@ describe('ServiceNow service', () => { '[Action][ServiceNow]: Unable to get fields. Error: An error has occurred' ); }); + + test('it should throw an error when instance is not alive', async () => { + requestMock.mockImplementation(() => ({ + status: 200, + data: {}, + request: { connection: { servername: 'Developer instance' } }, + })); + await expect(service.getIncident('1')).rejects.toThrow( + 'There is an issue with your Service Now Instance. Please check Developer instance.' + ); + }); + }); + + describe('getChoices', () => { + test('it should call request with correct arguments', async () => { + requestMock.mockImplementation(() => ({ + data: { result: serviceNowChoices }, + })); + await service.getChoices(['priority', 'category']); + + expect(requestMock).toHaveBeenCalledWith({ + axios, + logger, + configurationUtilities, + url: + 'https://dev102283.service-now.com/api/now/v2/table/sys_choice?sysparm_query=name=task^ORname=incident^element=priority^ORelement=category&sysparm_fields=label,value,dependent_value,element', + }); + }); + + test('it returns common fields correctly', async () => { + requestMock.mockImplementation(() => ({ + data: { result: serviceNowChoices }, + })); + const res = await service.getChoices(['priority']); + expect(res).toEqual(serviceNowChoices); + }); + + test('it should call request with correct arguments when table changes', async () => { + service = createExternalService( + 'sn_si_incident', + { + config: { apiUrl: 'https://dev102283.service-now.com/' }, + secrets: { username: 'admin', password: 'admin' }, + }, + logger, + configurationUtilities + ); + + requestMock.mockImplementation(() => ({ + data: { result: serviceNowChoices }, + })); + + await service.getChoices(['priority', 'category']); + + expect(requestMock).toHaveBeenCalledWith({ + axios, + logger, + configurationUtilities, + url: + 'https://dev102283.service-now.com/api/now/v2/table/sys_choice?sysparm_query=name=task^ORname=sn_si_incident^element=priority^ORelement=category&sysparm_fields=label,value,dependent_value,element', + }); + }); + + test('it should throw an error', async () => { + requestMock.mockImplementation(() => { + throw new Error('An error has occurred'); + }); + await expect(service.getChoices(['priority'])).rejects.toThrow( + '[Action][ServiceNow]: Unable to get choices. Error: An error has occurred' + ); + }); + + test('it should throw an error when instance is not alive', async () => { + requestMock.mockImplementation(() => ({ + status: 200, + data: {}, + request: { connection: { servername: 'Developer instance' } }, + })); + await expect(service.getIncident('1')).rejects.toThrow( + 'There is an issue with your Service Now Instance. Please check Developer instance.' + ); + }); }); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts index 108fe06bcbcaa..7c7723c98a070 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts @@ -15,13 +15,10 @@ import { request, getErrorMessage, addTimeZoneToDate, patch } from '../lib/axios import { ActionsConfigurationUtilities } from '../../actions_config'; const API_VERSION = 'v2'; -const INCIDENT_URL = `api/now/${API_VERSION}/table/incident`; const SYS_DICTIONARY = `api/now/${API_VERSION}/table/sys_dictionary`; -// Based on: https://docs.servicenow.com/bundle/orlando-platform-user-interface/page/use/navigation/reference/r_NavigatingByURLExamples.html -const VIEW_INCIDENT_URL = `nav_to.do?uri=incident.do?sys_id=`; - export const createExternalService = ( + table: string, { config, secrets }: ExternalServiceCredentials, logger: Logger, configurationUtilities: ActionsConfigurationUtilities @@ -30,24 +27,36 @@ export const createExternalService = ( const { username, password } = secrets as ServiceNowSecretConfigurationType; if (!url || !username || !password) { - throw Error(`[Action]${i18n.NAME}: Wrong configuration.`); + throw Error(`[Action]${i18n.SERVICENOW}: Wrong configuration.`); } const urlWithoutTrailingSlash = url.endsWith('/') ? url.slice(0, -1) : url; - const incidentUrl = `${urlWithoutTrailingSlash}/${INCIDENT_URL}`; - const fieldsUrl = `${urlWithoutTrailingSlash}/${SYS_DICTIONARY}?sysparm_query=name=task^internal_type=string&active=true&array=false&read_only=false&sysparm_fields=max_length,element,column_label,mandatory`; + const incidentUrl = `${urlWithoutTrailingSlash}/api/now/${API_VERSION}/table/${table}`; + const fieldsUrl = `${urlWithoutTrailingSlash}/${SYS_DICTIONARY}?sysparm_query=name=task^ORname=${table}^internal_type=string&active=true&array=false&read_only=false&sysparm_fields=max_length,element,column_label,mandatory`; + const choicesUrl = `${urlWithoutTrailingSlash}/api/now/${API_VERSION}/table/sys_choice`; const axiosInstance = axios.create({ auth: { username, password }, }); const getIncidentViewURL = (id: string) => { - return `${urlWithoutTrailingSlash}/${VIEW_INCIDENT_URL}${id}`; + // Based on: https://docs.servicenow.com/bundle/orlando-platform-user-interface/page/use/navigation/reference/r_NavigatingByURLExamples.html + return `${urlWithoutTrailingSlash}/nav_to.do?uri=${table}.do?sys_id=${id}`; + }; + + const getChoicesURL = (fields: string[]) => { + const elements = fields + .slice(1) + .reduce((acc, field) => `${acc}^ORelement=${field}`, `element=${fields[0]}`); + + return `${choicesUrl}?sysparm_query=name=task^ORname=${table}^${elements}&sysparm_fields=label,value,dependent_value,element`; }; const checkInstance = (res: AxiosResponse) => { if (res.status === 200 && res.data.result == null) { throw new Error( - `There is an issue with your Service Now Instance. Please check ${res.request.connection.servername}` + `There is an issue with your Service Now Instance. Please check ${ + res.request?.connection?.servername ?? '' + }.` ); } }; @@ -64,7 +73,10 @@ export const createExternalService = ( return { ...res.data.result }; } catch (error) { throw new Error( - getErrorMessage(i18n.NAME, `Unable to get incident with id ${id}. Error: ${error.message}`) + getErrorMessage( + i18n.SERVICENOW, + `Unable to get incident with id ${id}. Error: ${error.message}` + ) ); } }; @@ -82,7 +94,10 @@ export const createExternalService = ( return res.data.result.length > 0 ? { ...res.data.result } : undefined; } catch (error) { throw new Error( - getErrorMessage(i18n.NAME, `Unable to find incidents by query. Error: ${error.message}`) + getErrorMessage( + i18n.SERVICENOW, + `Unable to find incidents by query. Error: ${error.message}` + ) ); } }; @@ -106,7 +121,7 @@ export const createExternalService = ( }; } catch (error) { throw new Error( - getErrorMessage(i18n.NAME, `Unable to create incident. Error: ${error.message}`) + getErrorMessage(i18n.SERVICENOW, `Unable to create incident. Error: ${error.message}`) ); } }; @@ -130,7 +145,7 @@ export const createExternalService = ( } catch (error) { throw new Error( getErrorMessage( - i18n.NAME, + i18n.SERVICENOW, `Unable to update incident with id ${incidentId}. Error: ${error.message}` ) ); @@ -148,7 +163,26 @@ export const createExternalService = ( checkInstance(res); return res.data.result.length > 0 ? res.data.result : []; } catch (error) { - throw new Error(getErrorMessage(i18n.NAME, `Unable to get fields. Error: ${error.message}`)); + throw new Error( + getErrorMessage(i18n.SERVICENOW, `Unable to get fields. Error: ${error.message}`) + ); + } + }; + + const getChoices = async (fields: string[]) => { + try { + const res = await request({ + axios: axiosInstance, + url: getChoicesURL(fields), + logger, + configurationUtilities, + }); + checkInstance(res); + return res.data.result; + } catch (error) { + throw new Error( + getErrorMessage(i18n.SERVICENOW, `Unable to get choices. Error: ${error.message}`) + ); } }; @@ -158,5 +192,6 @@ export const createExternalService = ( getFields, getIncident, updateIncident, + getChoices, }; }; diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/translations.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/translations.ts index 287fe8cacda79..84fe538e0a63a 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/translations.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/translations.ts @@ -6,10 +6,18 @@ import { i18n } from '@kbn/i18n'; -export const NAME = i18n.translate('xpack.actions.builtin.servicenowTitle', { +export const SERVICENOW = i18n.translate('xpack.actions.builtin.serviceNowTitle', { defaultMessage: 'ServiceNow', }); +export const SERVICENOW_ITSM = i18n.translate('xpack.actions.builtin.serviceNowITSMTitle', { + defaultMessage: 'ServiceNow ITSM', +}); + +export const SERVICENOW_SIR = i18n.translate('xpack.actions.builtin.serviceNowSIRTitle', { + defaultMessage: 'ServiceNow SIR', +}); + export const ALLOWED_HOSTS_ERROR = (message: string) => i18n.translate('xpack.actions.builtin.configuration.apiAllowedHostsError', { defaultMessage: 'error configuring connector action: {message}', diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts index 9868f5d1bea06..c74d1fbffd759 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/types.ts @@ -8,13 +8,16 @@ import { TypeOf } from '@kbn/config-schema'; import { - ExecutorParamsSchema, + ExecutorParamsSchemaITSM, ExecutorSubActionCommonFieldsParamsSchema, ExecutorSubActionGetIncidentParamsSchema, ExecutorSubActionHandshakeParamsSchema, - ExecutorSubActionPushParamsSchema, + ExecutorSubActionPushParamsSchemaITSM, ExternalIncidentServiceConfigurationSchema, ExternalIncidentServiceSecretConfigurationSchema, + ExecutorParamsSchemaSIR, + ExecutorSubActionPushParamsSchemaSIR, + ExecutorSubActionGetChoicesParamsSchema, } from './schema'; import { ActionsConfigurationUtilities } from '../../actions_config'; import { Logger } from '../../../../../../src/core/server'; @@ -30,14 +33,29 @@ export type ExecutorSubActionCommonFieldsParams = TypeOf< typeof ExecutorSubActionCommonFieldsParamsSchema >; -export type ServiceNowExecutorResultData = PushToServiceResponse | GetCommonFieldsResponse; +export type ExecutorSubActionGetChoicesParams = TypeOf< + typeof ExecutorSubActionGetChoicesParamsSchema +>; + +export type ServiceNowExecutorResultData = + | PushToServiceResponse + | GetCommonFieldsResponse + | GetChoicesResponse; export interface CreateCommentRequest { [key: string]: string; } -export type ExecutorParams = TypeOf; -export type ExecutorSubActionPushParams = TypeOf; +export type ExecutorParams = + | TypeOf + | TypeOf; + +export type ExecutorSubActionPushParamsITSM = TypeOf; +export type ExecutorSubActionPushParamsSIR = TypeOf; + +export type ExecutorSubActionPushParams = + | ExecutorSubActionPushParamsITSM + | ExecutorSubActionPushParamsSIR; export interface ExternalServiceCredentials { config: Record; @@ -62,14 +80,17 @@ export interface PushToServiceResponse extends ExternalServiceIncidentResponse { export type ExternalServiceParams = Record; export interface ExternalService { - getFields: () => Promise; + getChoices: (fields: string[]) => Promise; getIncident: (id: string) => Promise; + getFields: () => Promise; createIncident: (params: ExternalServiceParams) => Promise; updateIncident: (params: ExternalServiceParams) => Promise; findIncidents: (params?: Record) => Promise; } export type PushToServiceApiParams = ExecutorSubActionPushParams; +export type PushToServiceApiParamsITSM = ExecutorSubActionPushParamsITSM; +export type PushToServiceApiParamsSIR = ExecutorSubActionPushParamsSIR; export interface ExternalServiceApiHandlerArgs { externalService: ExternalService; @@ -83,7 +104,17 @@ export type ExecutorSubActionHandshakeParams = TypeOf< typeof ExecutorSubActionHandshakeParamsSchema >; -export type Incident = Omit; +export type ServiceNowITSMIncident = Omit< + TypeOf['incident'], + 'externalId' +>; + +export type ServiceNowSIRIncident = Omit< + TypeOf['incident'], + 'externalId' +>; + +export type Incident = ServiceNowITSMIncident | ServiceNowSIRIncident; export interface PushToServiceApiHandlerArgs extends ExternalServiceApiHandlerArgs { params: PushToServiceApiParams; @@ -104,13 +135,29 @@ export interface ExternalServiceFields { max_length: string; element: string; } + +export interface ExternalServiceChoices { + value: string; + label: string; + dependent_value: string; + element: string; +} + export type GetCommonFieldsResponse = ExternalServiceFields[]; +export type GetChoicesResponse = ExternalServiceChoices[]; + export interface GetCommonFieldsHandlerArgs { externalService: ExternalService; params: ExecutorSubActionCommonFieldsParams; } +export interface GetChoicesHandlerArgs { + externalService: ExternalService; + params: ExecutorSubActionGetChoicesParams; +} + export interface ExternalServiceApi { + getChoices: (args: GetChoicesHandlerArgs) => Promise; getFields: (args: GetCommonFieldsHandlerArgs) => Promise; handshake: (args: HandshakeApiHandlerArgs) => Promise; pushToService: (args: PushToServiceApiHandlerArgs) => Promise; diff --git a/x-pack/plugins/actions/server/builtin_action_types/slack.ts b/x-pack/plugins/actions/server/builtin_action_types/slack.ts index 5d2c5a24b3edd..9f0a4c44b3c54 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/slack.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/slack.ts @@ -22,7 +22,7 @@ import { ExecutorType, } from '../types'; import { ActionsConfigurationUtilities } from '../actions_config'; -import { getProxyAgents } from './lib/get_proxy_agents'; +import { getCustomAgents } from './lib/get_custom_agents'; export type SlackActionType = ActionType<{}, ActionTypeSecretsType, ActionParamsType, unknown>; export type SlackActionTypeExecutorOptions = ActionTypeExecutorOptions< @@ -130,10 +130,10 @@ async function slackExecutor( const { message } = params; const proxySettings = configurationUtilities.getProxySettings(); - const proxyAgents = getProxyAgents(configurationUtilities, logger); - const httpProxyAgent = webhookUrl.toLowerCase().startsWith('https') - ? proxyAgents.httpsAgent - : proxyAgents.httpAgent; + const customAgents = getCustomAgents(configurationUtilities, logger); + const agent = webhookUrl.toLowerCase().startsWith('https') + ? customAgents.httpsAgent + : customAgents.httpAgent; if (proxySettings) { logger.debug(`IncomingWebhook was called with proxyUrl ${proxySettings.proxyUrl}`); @@ -143,7 +143,7 @@ async function slackExecutor( // https://slack.dev/node-slack-sdk/webhook // node-slack-sdk use Axios inside :) const webhook = new IncomingWebhook(webhookUrl, { - agent: httpProxyAgent, + agent, }); result = await webhook.send(message); } catch (err) { diff --git a/x-pack/plugins/actions/server/index.ts b/x-pack/plugins/actions/server/index.ts index 4e59dfd099811..b573bcfc10914 100644 --- a/x-pack/plugins/actions/server/index.ts +++ b/x-pack/plugins/actions/server/index.ts @@ -35,7 +35,8 @@ export type { SlackActionParams, WebhookActionTypeId, WebhookActionParams, - ServiceNowActionTypeId, + ServiceNowITSMActionTypeId, + ServiceNowSIRActionTypeId, ServiceNowActionParams, JiraActionTypeId, JiraActionParams, diff --git a/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx index 8a1d73c818944..a221f4bfb05a9 100644 --- a/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx +++ b/x-pack/plugins/apm/public/application/action_menu/anomaly_detection_setup_link.tsx @@ -57,7 +57,9 @@ export function AnomalyDetectionSetupLink() { export function MissingJobsAlert({ environment }: { environment?: string }) { const { data = DEFAULT_DATA, status } = useFetcher( (callApmApi) => - callApmApi({ endpoint: `GET /api/apm/settings/anomaly-detection/jobs` }), + callApmApi({ + endpoint: `GET /api/apm/settings/anomaly-detection/jobs`, + }), [], { preservePreviousData: false, showToastOnError: false } ); diff --git a/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx index d7375d14e17cf..3b68eccbd9dc4 100644 --- a/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/error_count_alert_trigger/index.tsx @@ -13,7 +13,6 @@ import { asInteger } from '../../../../common/utils/formatters'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; import { useFetcher } from '../../../hooks/use_fetcher'; -import { callApmApi } from '../../../services/rest/createCallApmApi'; import { ChartPreview } from '../chart_preview'; import { EnvironmentField, IsAboveField, ServiceField } from '../fields'; import { getAbsoluteTimeRange } from '../helper'; @@ -46,20 +45,23 @@ export function ErrorCountAlertTrigger(props: Props) { const { threshold, windowSize, windowUnit, environment } = alertParams; - const { data } = useFetcher(() => { - if (windowSize && windowUnit) { - return callApmApi({ - endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_count', - params: { - query: { - ...getAbsoluteTimeRange(windowSize, windowUnit), - environment, - serviceName, + const { data } = useFetcher( + (callApmApi) => { + if (windowSize && windowUnit) { + return callApmApi({ + endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_count', + params: { + query: { + ...getAbsoluteTimeRange(windowSize, windowUnit), + environment, + serviceName, + }, }, - }, - }); - } - }, [windowSize, windowUnit, environment, serviceName]); + }); + } + }, + [windowSize, windowUnit, environment, serviceName] + ); const defaults = { threshold: 25, diff --git a/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx index 7c0a74f2e1b60..4d28cdaec3782 100644 --- a/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/transaction_duration_alert_trigger/index.tsx @@ -8,7 +8,6 @@ import { i18n } from '@kbn/i18n'; import { map } from 'lodash'; import React from 'react'; import { useParams } from 'react-router-dom'; -import { useFetcher } from '../../../../../observability/public'; import { ForLastExpression } from '../../../../../triggers_actions_ui/public'; import { ENVIRONMENT_ALL } from '../../../../common/environment_filter_values'; import { getDurationFormatter } from '../../../../common/utils/formatters'; @@ -16,7 +15,7 @@ import { TimeSeries } from '../../../../typings/timeseries'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; -import { callApmApi } from '../../../services/rest/createCallApmApi'; +import { useFetcher } from '../../../hooks/use_fetcher'; import { getMaxY, getResponseTimeTickFormatter, @@ -88,29 +87,32 @@ export function TransactionDurationAlertTrigger(props: Props) { windowUnit, } = alertParams; - const { data } = useFetcher(() => { - if (windowSize && windowUnit) { - return callApmApi({ - endpoint: 'GET /api/apm/alerts/chart_preview/transaction_duration', - params: { - query: { - ...getAbsoluteTimeRange(windowSize, windowUnit), - aggregationType, - environment, - serviceName, - transactionType: alertParams.transactionType, + const { data } = useFetcher( + (callApmApi) => { + if (windowSize && windowUnit) { + return callApmApi({ + endpoint: 'GET /api/apm/alerts/chart_preview/transaction_duration', + params: { + query: { + ...getAbsoluteTimeRange(windowSize, windowUnit), + aggregationType, + environment, + serviceName, + transactionType: alertParams.transactionType, + }, }, - }, - }); - } - }, [ - aggregationType, - environment, - serviceName, - alertParams.transactionType, - windowSize, - windowUnit, - ]); + }); + } + }, + [ + aggregationType, + environment, + serviceName, + alertParams.transactionType, + windowSize, + windowUnit, + ] + ); const maxY = getMaxY([ { data: data ?? [] } as TimeSeries<{ x: number; y: number | null }>, diff --git a/x-pack/plugins/apm/public/components/alerting/transaction_error_rate_alert_trigger/index.tsx b/x-pack/plugins/apm/public/components/alerting/transaction_error_rate_alert_trigger/index.tsx index e06f39ec10220..58adbb7b172c8 100644 --- a/x-pack/plugins/apm/public/components/alerting/transaction_error_rate_alert_trigger/index.tsx +++ b/x-pack/plugins/apm/public/components/alerting/transaction_error_rate_alert_trigger/index.tsx @@ -12,7 +12,6 @@ import { useApmServiceContext } from '../../../context/apm_service/use_apm_servi import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { useEnvironmentsFetcher } from '../../../hooks/use_environments_fetcher'; import { useFetcher } from '../../../hooks/use_fetcher'; -import { callApmApi } from '../../../services/rest/createCallApmApi'; import { ChartPreview } from '../chart_preview'; import { EnvironmentField, @@ -54,27 +53,30 @@ export function TransactionErrorRateAlertTrigger(props: Props) { const thresholdAsPercent = (threshold ?? 0) / 100; - const { data } = useFetcher(() => { - if (windowSize && windowUnit) { - return callApmApi({ - endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_rate', - params: { - query: { - ...getAbsoluteTimeRange(windowSize, windowUnit), - environment, - serviceName, - transactionType: alertParams.transactionType, + const { data } = useFetcher( + (callApmApi) => { + if (windowSize && windowUnit) { + return callApmApi({ + endpoint: 'GET /api/apm/alerts/chart_preview/transaction_error_rate', + params: { + query: { + ...getAbsoluteTimeRange(windowSize, windowUnit), + environment, + serviceName, + transactionType: alertParams.transactionType, + }, }, - }, - }); - } - }, [ - alertParams.transactionType, - environment, - serviceName, - windowSize, - windowUnit, - ]); + }); + } + }, + [ + alertParams.transactionType, + environment, + serviceName, + windowSize, + windowUnit, + ] + ); if (serviceName && !transactionTypes.length) { return null; diff --git a/x-pack/plugins/apm/public/components/app/Correlations/ErrorCorrelations.tsx b/x-pack/plugins/apm/public/components/app/Correlations/ErrorCorrelations.tsx index 25973b9bda388..891a2081804cc 100644 --- a/x-pack/plugins/apm/public/components/app/Correlations/ErrorCorrelations.tsx +++ b/x-pack/plugins/apm/public/components/app/Correlations/ErrorCorrelations.tsx @@ -25,10 +25,7 @@ import { } from '@elastic/eui'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; -import { - APIReturnType, - callApmApi, -} from '../../../services/rest/createCallApmApi'; +import { APIReturnType } from '../../../services/rest/createCallApmApi'; import { px } from '../../../style/variables'; import { SignificantTermsTable } from './SignificantTermsTable'; import { ChartContainer } from '../../shared/charts/chart_container'; @@ -65,32 +62,35 @@ export function ErrorCorrelations() { const { urlParams, uiFilters } = useUrlParams(); const { transactionName, transactionType, start, end } = urlParams; - const { data, status } = useFetcher(() => { - if (start && end) { - return callApmApi({ - endpoint: 'GET /api/apm/correlations/failed_transactions', - params: { - query: { - serviceName, - transactionName, - transactionType, - start, - end, - uiFilters: JSON.stringify(uiFilters), - fieldNames: fieldNames.map((field) => field.label).join(','), + const { data, status } = useFetcher( + (callApmApi) => { + if (start && end) { + return callApmApi({ + endpoint: 'GET /api/apm/correlations/failed_transactions', + params: { + query: { + serviceName, + transactionName, + transactionType, + start, + end, + uiFilters: JSON.stringify(uiFilters), + fieldNames: fieldNames.map((field) => field.label).join(','), + }, }, - }, - }); - } - }, [ - serviceName, - start, - end, - transactionName, - transactionType, - uiFilters, - fieldNames, - ]); + }); + } + }, + [ + serviceName, + start, + end, + transactionName, + transactionType, + uiFilters, + fieldNames, + ] + ); return ( <> diff --git a/x-pack/plugins/apm/public/components/app/Correlations/LatencyCorrelations.tsx b/x-pack/plugins/apm/public/components/app/Correlations/LatencyCorrelations.tsx index 438303110fbc4..493c6e04ffbb1 100644 --- a/x-pack/plugins/apm/public/components/app/Correlations/LatencyCorrelations.tsx +++ b/x-pack/plugins/apm/public/components/app/Correlations/LatencyCorrelations.tsx @@ -26,10 +26,7 @@ import { import { getDurationFormatter } from '../../../../common/utils/formatters'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; -import { - APIReturnType, - callApmApi, -} from '../../../services/rest/createCallApmApi'; +import { APIReturnType } from '../../../services/rest/createCallApmApi'; import { SignificantTermsTable } from './SignificantTermsTable'; import { ChartContainer } from '../../shared/charts/chart_container'; @@ -65,34 +62,37 @@ export function LatencyCorrelations() { const { urlParams, uiFilters } = useUrlParams(); const { transactionName, transactionType, start, end } = urlParams; - const { data, status } = useFetcher(() => { - if (start && end) { - return callApmApi({ - endpoint: 'GET /api/apm/correlations/slow_transactions', - params: { - query: { - serviceName, - transactionName, - transactionType, - start, - end, - uiFilters: JSON.stringify(uiFilters), - durationPercentile, - fieldNames: fieldNames.map((field) => field.label).join(','), + const { data, status } = useFetcher( + (callApmApi) => { + if (start && end) { + return callApmApi({ + endpoint: 'GET /api/apm/correlations/slow_transactions', + params: { + query: { + serviceName, + transactionName, + transactionType, + start, + end, + uiFilters: JSON.stringify(uiFilters), + durationPercentile, + fieldNames: fieldNames.map((field) => field.label).join(','), + }, }, - }, - }); - } - }, [ - serviceName, - start, - end, - transactionName, - transactionType, - uiFilters, - durationPercentile, - fieldNames, - ]); + }); + } + }, + [ + serviceName, + start, + end, + transactionName, + transactionType, + uiFilters, + durationPercentile, + fieldNames, + ] + ); return ( <> diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx index 95ebd5d4036de..9501474e3be65 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupDetails/index.tsx @@ -23,7 +23,6 @@ import { useTrackPageview } from '../../../../../observability/public'; import { NOT_AVAILABLE_LABEL } from '../../../../common/i18n'; import { useFetcher } from '../../../hooks/use_fetcher'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; -import { callApmApi } from '../../../services/rest/createCallApmApi'; import { fontFamilyCode, fontSizes, px, units } from '../../../style/variables'; import { ApmHeader } from '../../shared/ApmHeader'; import { SearchBar } from '../../shared/search_bar'; @@ -70,24 +69,27 @@ export function ErrorGroupDetails({ location, match }: ErrorGroupDetailsProps) { const { urlParams, uiFilters } = useUrlParams(); const { start, end } = urlParams; - const { data: errorGroupData } = useFetcher(() => { - if (start && end) { - return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/errors/{groupId}', - params: { - path: { - serviceName, - groupId, + const { data: errorGroupData } = useFetcher( + (callApmApi) => { + if (start && end) { + return callApmApi({ + endpoint: 'GET /api/apm/services/{serviceName}/errors/{groupId}', + params: { + path: { + serviceName, + groupId, + }, + query: { + start, + end, + uiFilters: JSON.stringify(uiFilters), + }, }, - query: { - start, - end, - uiFilters: JSON.stringify(uiFilters), - }, - }, - }); - } - }, [serviceName, start, end, groupId, uiFilters]); + }); + } + }, + [serviceName, start, end, groupId, uiFilters] + ); const { errorDistributionData } = useErrorGroupDistributionFetcher({ serviceName, diff --git a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx index 71cb8e0e01602..af8c667df6ca2 100644 --- a/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ErrorGroupOverview/index.tsx @@ -18,7 +18,6 @@ import { useTrackPageview } from '../../../../../observability/public'; import { Projection } from '../../../../common/projections'; import { useFetcher } from '../../../hooks/use_fetcher'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; -import { callApmApi } from '../../../services/rest/createCallApmApi'; import { LocalUIFilters } from '../../shared/LocalUIFilters'; import { SearchBar } from '../../shared/search_bar'; import { ErrorDistribution } from '../ErrorGroupDetails/Distribution'; @@ -37,27 +36,30 @@ function ErrorGroupOverview({ serviceName }: ErrorGroupOverviewProps) { groupId: undefined, }); - const { data: errorGroupListData } = useFetcher(() => { - const normalizedSortDirection = sortDirection === 'asc' ? 'asc' : 'desc'; + const { data: errorGroupListData } = useFetcher( + (callApmApi) => { + const normalizedSortDirection = sortDirection === 'asc' ? 'asc' : 'desc'; - if (start && end) { - return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/errors', - params: { - path: { - serviceName, + if (start && end) { + return callApmApi({ + endpoint: 'GET /api/apm/services/{serviceName}/errors', + params: { + path: { + serviceName, + }, + query: { + start, + end, + sortField, + sortDirection: normalizedSortDirection, + uiFilters: JSON.stringify(uiFilters), + }, }, - query: { - start, - end, - sortField, - sortDirection: normalizedSortDirection, - uiFilters: JSON.stringify(uiFilters), - }, - }, - }); - } - }, [serviceName, start, end, sortField, sortDirection, uiFilters]); + }); + } + }, + [serviceName, start, end, sortField, sortDirection, uiFilters] + ); useTrackPageview({ app: 'apm', diff --git a/x-pack/plugins/apm/public/components/app/RumDashboard/ux_overview_fetchers.ts b/x-pack/plugins/apm/public/components/app/RumDashboard/ux_overview_fetchers.ts index 7ce9d3f25354c..0468fbabcdb41 100644 --- a/x-pack/plugins/apm/public/components/app/RumDashboard/ux_overview_fetchers.ts +++ b/x-pack/plugins/apm/public/components/app/RumDashboard/ux_overview_fetchers.ts @@ -21,6 +21,7 @@ export const fetchUxOverviewDate = async ({ }: FetchDataParams): Promise => { const data = await callApmApi({ endpoint: 'GET /api/apm/rum-client/web-core-vitals', + signal: null, params: { query: { start: new Date(absoluteTime.start).toISOString(), @@ -41,6 +42,7 @@ export async function hasRumData({ }: HasDataParams): Promise { return await callApmApi({ endpoint: 'GET /api/apm/observability_overview/has_rum_data', + signal: null, params: { query: { start: new Date(absoluteTime.start).toISOString(), diff --git a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx index 6f8d058903183..463c9f36fb2fe 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx +++ b/x-pack/plugins/apm/public/components/app/ServiceMap/index.tsx @@ -17,7 +17,6 @@ import { FETCH_STATUS, useFetcher } from '../../../hooks/use_fetcher'; import { useLicenseContext } from '../../../context/license/use_license_context'; import { useTheme } from '../../../hooks/use_theme'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; -import { callApmApi } from '../../../services/rest/createCallApmApi'; import { DatePicker } from '../../shared/DatePicker'; import { LicensePrompt } from '../../shared/LicensePrompt'; import { Controls } from './Controls'; @@ -86,28 +85,31 @@ export function ServiceMap({ const license = useLicenseContext(); const { urlParams } = useUrlParams(); - const { data = { elements: [] }, status, error } = useFetcher(() => { - // When we don't have a license or a valid license, don't make the request. - if (!license || !isActivePlatinumLicense(license)) { - return; - } - - const { start, end, environment } = urlParams; - if (start && end) { - return callApmApi({ - isCachable: false, - endpoint: 'GET /api/apm/service-map', - params: { - query: { - start, - end, - environment, - serviceName, + const { data = { elements: [] }, status, error } = useFetcher( + (callApmApi) => { + // When we don't have a license or a valid license, don't make the request. + if (!license || !isActivePlatinumLicense(license)) { + return; + } + + const { start, end, environment } = urlParams; + if (start && end) { + return callApmApi({ + isCachable: false, + endpoint: 'GET /api/apm/service-map', + params: { + query: { + start, + end, + environment, + serviceName, + }, }, - }, - }); - } - }, [license, serviceName, urlParams]); + }); + } + }, + [license, serviceName, urlParams] + ); const { ref, height } = useRefDimensions(); diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/saveConfig.ts b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/saveConfig.ts index e15a57ff7539e..a9c334f414db2 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/saveConfig.ts +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/AgentConfigurationCreateEdit/SettingsPage/saveConfig.ts @@ -26,6 +26,7 @@ export async function saveConfig({ try { await callApmApi({ endpoint: 'PUT /api/apm/settings/agent-configuration', + signal: null, params: { query: { overwrite: isEditMode }, body: { diff --git a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx index 958aafa8159df..09251efe8b977 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/AgentConfigurations/List/ConfirmDeleteModal.tsx @@ -73,6 +73,7 @@ async function deleteConfig( try { await callApmApi({ endpoint: 'DELETE /api/apm/settings/agent-configuration', + signal: null, params: { body: { service: { diff --git a/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx b/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx index 8c10b96c51ce2..29fca54547e7a 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/ApmIndices/index.tsx @@ -74,6 +74,7 @@ async function saveApmIndices({ }) { await callApmApi({ endpoint: 'POST /api/apm/settings/apm-indices/save', + signal: null, params: { body: apmIndices, }, diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/DeleteButton.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/DeleteButton.tsx index ffcb85384642a..9257d5d78b6b3 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/DeleteButton.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/DeleteButton.tsx @@ -48,6 +48,7 @@ async function deleteConfig( try { await callApmApi({ endpoint: 'DELETE /api/apm/settings/custom_links/{id}', + signal: null, params: { path: { id: customLinkId }, }, diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/LinkPreview.tsx b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/LinkPreview.tsx index 25fd8f7ad3caf..626aca6218ea3 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/LinkPreview.tsx +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/LinkPreview.tsx @@ -31,6 +31,7 @@ interface Props { const fetchTransaction = debounce( async (filters: Filter[], callback: (transaction: Transaction) => void) => { const transaction = await callApmApi({ + signal: null, endpoint: 'GET /api/apm/settings/custom_links/transaction', params: { query: convertFiltersToQuery(filters) }, }); diff --git a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/saveCustomLink.ts b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/saveCustomLink.ts index cb1eaf6bca3f0..2d172d652ad81 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/saveCustomLink.ts +++ b/x-pack/plugins/apm/public/components/app/Settings/CustomizeUI/CustomLink/CreateEditCustomLinkFlyout/saveCustomLink.ts @@ -35,6 +35,7 @@ export async function saveCustomLink({ if (id) { await callApmApi({ endpoint: 'PUT /api/apm/settings/custom_links/{id}', + signal: null, params: { path: { id }, body: customLink, @@ -43,6 +44,7 @@ export async function saveCustomLink({ } else { await callApmApi({ endpoint: 'POST /api/apm/settings/custom_links', + signal: null, params: { body: customLink, }, diff --git a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/create_jobs.ts b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/create_jobs.ts index 7106a4c48ef70..dc73bf12ff4b8 100644 --- a/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/create_jobs.ts +++ b/x-pack/plugins/apm/public/components/app/Settings/anomaly_detection/create_jobs.ts @@ -28,6 +28,7 @@ export async function createJobs({ try { await callApmApi({ endpoint: 'POST /api/apm/settings/anomaly-detection/jobs', + signal: null, params: { body: { environments }, }, diff --git a/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx b/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx index c07e00ef387c9..a33be140d9a36 100644 --- a/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx +++ b/x-pack/plugins/apm/public/components/app/TraceLink/trace_link.test.tsx @@ -60,12 +60,13 @@ describe('TraceLink', () => { describe('when no transaction is found', () => { it('renders a trace page', () => { jest.spyOn(urlParamsHooks, 'useUrlParams').mockReturnValue({ + rangeId: 0, + refreshTimeRange: jest.fn(), + uiFilters: {}, urlParams: { rangeFrom: 'now-24h', rangeTo: 'now', }, - refreshTimeRange: jest.fn(), - uiFilters: {}, }); jest.spyOn(hooks, 'useFetcher').mockReturnValue({ data: { transaction: undefined }, @@ -87,12 +88,13 @@ describe('TraceLink', () => { describe('transaction page', () => { beforeAll(() => { jest.spyOn(urlParamsHooks, 'useUrlParams').mockReturnValue({ + rangeId: 0, + refreshTimeRange: jest.fn(), + uiFilters: {}, urlParams: { rangeFrom: 'now-24h', rangeTo: 'now', }, - refreshTimeRange: jest.fn(), - uiFilters: {}, }); }); diff --git a/x-pack/plugins/apm/public/components/app/service_inventory/use_anomaly_detection_jobs_fetcher.ts b/x-pack/plugins/apm/public/components/app/service_inventory/use_anomaly_detection_jobs_fetcher.ts index 901841ac4d593..eacb09bde70ac 100644 --- a/x-pack/plugins/apm/public/components/app/service_inventory/use_anomaly_detection_jobs_fetcher.ts +++ b/x-pack/plugins/apm/public/components/app/service_inventory/use_anomaly_detection_jobs_fetcher.ts @@ -8,7 +8,9 @@ import { useFetcher } from '../../../hooks/use_fetcher'; export function useAnomalyDetectionJobsFetcher() { const { data, status } = useFetcher( (callApmApi) => - callApmApi({ endpoint: `GET /api/apm/settings/anomaly-detection/jobs` }), + callApmApi({ + endpoint: `GET /api/apm/settings/anomaly-detection/jobs`, + }), [], { showToastOnError: false } ); diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx index 1bd7310e3251d..e93b29025426d 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx @@ -26,7 +26,6 @@ import { import { ServiceDependencyItem } from '../../../../../server/lib/services/get_service_dependencies'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; -import { callApmApi } from '../../../../services/rest/createCallApmApi'; import { px, unit } from '../../../../style/variables'; import { AgentIcon } from '../../../shared/AgentIcon'; import { SparkPlot } from '../../../shared/charts/spark_plot'; @@ -167,26 +166,29 @@ export function ServiceOverviewDependenciesTable({ serviceName }: Props) { }, ]; - const { data = [], status } = useFetcher(() => { - if (!start || !end) { - return; - } + const { data = [], status } = useFetcher( + (callApmApi) => { + if (!start || !end) { + return; + } - return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/dependencies', - params: { - path: { - serviceName, + return callApmApi({ + endpoint: 'GET /api/apm/services/{serviceName}/dependencies', + params: { + path: { + serviceName, + }, + query: { + start, + end, + environment: environment || ENVIRONMENT_ALL.value, + numBuckets: 20, + }, }, - query: { - start, - end, - environment: environment || ENVIRONMENT_ALL.value, - numBuckets: 20, - }, - }, - }); - }, [start, end, serviceName, environment]); + }); + }, + [start, end, serviceName, environment] + ); // need top-level sortable fields for the managed table const items = data.map((item) => ({ diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx index d14ef648c22d3..c523728025674 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_errors_table/index.tsx @@ -16,7 +16,6 @@ import { asInteger } from '../../../../../common/utils/formatters'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; -import { callApmApi } from '../../../../services/rest/createCallApmApi'; import { px, unit } from '../../../../style/variables'; import { SparkPlot } from '../../../shared/charts/spark_plot'; import { ErrorDetailLink } from '../../../shared/Links/apm/ErrorDetailLink'; @@ -140,50 +139,53 @@ export function ServiceOverviewErrorsTable({ serviceName }: Props) { }, }, status, - } = useFetcher(() => { - if (!start || !end || !transactionType) { - return; - } + } = useFetcher( + (callApmApi) => { + if (!start || !end || !transactionType) { + return; + } - return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/error_groups', - params: { - path: { serviceName }, - query: { - start, - end, - uiFilters: JSON.stringify(uiFilters), - size: PAGE_SIZE, - numBuckets: 20, - pageIndex: tableOptions.pageIndex, - sortField: tableOptions.sort.field, - sortDirection: tableOptions.sort.direction, - transactionType, - }, - }, - }).then((response) => { - return { - items: response.error_groups, - totalItemCount: response.total_error_groups, - tableOptions: { - pageIndex: tableOptions.pageIndex, - sort: { - field: tableOptions.sort.field, - direction: tableOptions.sort.direction, + return callApmApi({ + endpoint: 'GET /api/apm/services/{serviceName}/error_groups', + params: { + path: { serviceName }, + query: { + start, + end, + uiFilters: JSON.stringify(uiFilters), + size: PAGE_SIZE, + numBuckets: 20, + pageIndex: tableOptions.pageIndex, + sortField: tableOptions.sort.field, + sortDirection: tableOptions.sort.direction, + transactionType, }, }, - }; - }); - }, [ - start, - end, - serviceName, - uiFilters, - tableOptions.pageIndex, - tableOptions.sort.field, - tableOptions.sort.direction, - transactionType, - ]); + }).then((response) => { + return { + items: response.error_groups, + totalItemCount: response.total_error_groups, + tableOptions: { + pageIndex: tableOptions.pageIndex, + sort: { + field: tableOptions.sort.field, + direction: tableOptions.sort.direction, + }, + }, + }; + }); + }, + [ + start, + end, + serviceName, + uiFilters, + tableOptions.pageIndex, + tableOptions.sort.field, + tableOptions.sort.direction, + transactionType, + ] + ); const { items, diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx index f7c2891bb3e65..a0528b7220cc1 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_instances_chart_and_table.tsx @@ -9,7 +9,6 @@ import React from 'react'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { useFetcher } from '../../../hooks/use_fetcher'; -import { callApmApi } from '../../../services/rest/createCallApmApi'; import { InstancesLatencyDistributionChart } from '../../shared/charts/instances_latency_distribution_chart'; import { ServiceOverviewInstancesTable } from './service_overview_instances_table'; @@ -29,28 +28,31 @@ export function ServiceOverviewInstancesChartAndTable({ uiFilters, } = useUrlParams(); - const { data = [], status } = useFetcher(() => { - if (!start || !end || !transactionType) { - return; - } + const { data = [], status } = useFetcher( + (callApmApi) => { + if (!start || !end || !transactionType) { + return; + } - return callApmApi({ - endpoint: - 'GET /api/apm/services/{serviceName}/service_overview_instances', - params: { - path: { - serviceName, + return callApmApi({ + endpoint: + 'GET /api/apm/services/{serviceName}/service_overview_instances', + params: { + path: { + serviceName, + }, + query: { + start, + end, + transactionType, + uiFilters: JSON.stringify(uiFilters), + numBuckets: 20, + }, }, - query: { - start, - end, - transactionType, - uiFilters: JSON.stringify(uiFilters), - numBuckets: 20, - }, - }, - }); - }, [start, end, serviceName, transactionType, uiFilters]); + }); + }, + [start, end, serviceName, transactionType, uiFilters] + ); return ( <> diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx index b79e011bde488..fae00822bb966 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_throughput_chart.tsx @@ -13,7 +13,6 @@ import { useFetcher } from '../../../hooks/use_fetcher'; import { useTheme } from '../../../hooks/use_theme'; import { useUrlParams } from '../../../context/url_params_context/use_url_params'; import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context'; -import { callApmApi } from '../../../services/rest/createCallApmApi'; import { TimeseriesChart } from '../../shared/charts/timeseries_chart'; export function ServiceOverviewThroughputChart({ @@ -27,24 +26,27 @@ export function ServiceOverviewThroughputChart({ const { transactionType } = useApmServiceContext(); const { start, end } = urlParams; - const { data, status } = useFetcher(() => { - if (serviceName && transactionType && start && end) { - return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/throughput', - params: { - path: { - serviceName, + const { data, status } = useFetcher( + (callApmApi) => { + if (serviceName && transactionType && start && end) { + return callApmApi({ + endpoint: 'GET /api/apm/services/{serviceName}/throughput', + params: { + path: { + serviceName, + }, + query: { + start, + end, + transactionType, + uiFilters: JSON.stringify(uiFilters), + }, }, - query: { - start, - end, - transactionType, - uiFilters: JSON.stringify(uiFilters), - }, - }, - }); - } - }, [serviceName, start, end, uiFilters, transactionType]); + }); + } + }, + [serviceName, start, end, uiFilters, transactionType] + ); return ( diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx index c77e80d0176de..069c4466d28a0 100644 --- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx +++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_transactions_table/index.tsx @@ -23,10 +23,7 @@ import { import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; import { FETCH_STATUS, useFetcher } from '../../../../hooks/use_fetcher'; -import { - APIReturnType, - callApmApi, -} from '../../../../services/rest/createCallApmApi'; +import { APIReturnType } from '../../../../services/rest/createCallApmApi'; import { px, unit } from '../../../../style/variables'; import { SparkPlot } from '../../../shared/charts/spark_plot'; import { ImpactBar } from '../../../shared/ImpactBar'; @@ -110,53 +107,56 @@ export function ServiceOverviewTransactionsTable(props: Props) { }, }, status, - } = useFetcher(() => { - if (!start || !end || !latencyAggregationType || !transactionType) { - return; - } + } = useFetcher( + (callApmApi) => { + if (!start || !end || !latencyAggregationType || !transactionType) { + return; + } - return callApmApi({ - endpoint: - 'GET /api/apm/services/{serviceName}/transactions/groups/overview', - params: { - path: { serviceName }, - query: { - start, - end, - uiFilters: JSON.stringify(uiFilters), - size: PAGE_SIZE, - numBuckets: 20, - pageIndex: tableOptions.pageIndex, - sortField: tableOptions.sort.field, - sortDirection: tableOptions.sort.direction, - transactionType, - latencyAggregationType: latencyAggregationType as LatencyAggregationType, - }, - }, - }).then((response) => { - return { - items: response.transactionGroups, - totalItemCount: response.totalTransactionGroups, - tableOptions: { - pageIndex: tableOptions.pageIndex, - sort: { - field: tableOptions.sort.field, - direction: tableOptions.sort.direction, + return callApmApi({ + endpoint: + 'GET /api/apm/services/{serviceName}/transactions/groups/overview', + params: { + path: { serviceName }, + query: { + start, + end, + uiFilters: JSON.stringify(uiFilters), + size: PAGE_SIZE, + numBuckets: 20, + pageIndex: tableOptions.pageIndex, + sortField: tableOptions.sort.field, + sortDirection: tableOptions.sort.direction, + transactionType, + latencyAggregationType: latencyAggregationType as LatencyAggregationType, }, }, - }; - }); - }, [ - serviceName, - start, - end, - uiFilters, - tableOptions.pageIndex, - tableOptions.sort.field, - tableOptions.sort.direction, - transactionType, - latencyAggregationType, - ]); + }).then((response) => { + return { + items: response.transactionGroups, + totalItemCount: response.totalTransactionGroups, + tableOptions: { + pageIndex: tableOptions.pageIndex, + sort: { + field: tableOptions.sort.field, + direction: tableOptions.sort.direction, + }, + }, + }; + }); + }, + [ + serviceName, + start, + end, + uiFilters, + tableOptions.pageIndex, + tableOptions.sort.field, + tableOptions.sort.direction, + transactionType, + latencyAggregationType, + ] + ); const { items, diff --git a/x-pack/plugins/apm/public/components/shared/DatePicker/date_picker.test.tsx b/x-pack/plugins/apm/public/components/shared/DatePicker/date_picker.test.tsx index 222c27cc7ed6d..1bf3cee32f286 100644 --- a/x-pack/plugins/apm/public/components/shared/DatePicker/date_picker.test.tsx +++ b/x-pack/plugins/apm/public/components/shared/DatePicker/date_picker.test.tsx @@ -31,8 +31,9 @@ function MockUrlParamsProvider({ return ( { const hostName = transaction.host?.hostname; - const podId = transaction.kubernetes?.pod.uid; + const podId = transaction.kubernetes?.pod?.uid; const containerId = transaction.container?.id; const time = Math.round(transaction.timestamp.us / 1000); diff --git a/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx b/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx index d712fa27c75ac..a16edfee5fb3d 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/charts/transaction_error_rate_chart/index.tsx @@ -12,7 +12,6 @@ import { asPercent } from '../../../../../common/utils/formatters'; import { useFetcher } from '../../../../hooks/use_fetcher'; import { useTheme } from '../../../../hooks/use_theme'; import { useUrlParams } from '../../../../context/url_params_context/use_url_params'; -import { callApmApi } from '../../../../services/rest/createCallApmApi'; import { TimeseriesChart } from '../timeseries_chart'; import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context'; @@ -35,26 +34,29 @@ export function TransactionErrorRateChart({ const { transactionType } = useApmServiceContext(); const { start, end, transactionName } = urlParams; - const { data, status } = useFetcher(() => { - if (transactionType && serviceName && start && end) { - return callApmApi({ - endpoint: - 'GET /api/apm/services/{serviceName}/transactions/charts/error_rate', - params: { - path: { - serviceName, + const { data, status } = useFetcher( + (callApmApi) => { + if (transactionType && serviceName && start && end) { + return callApmApi({ + endpoint: + 'GET /api/apm/services/{serviceName}/transactions/charts/error_rate', + params: { + path: { + serviceName, + }, + query: { + start, + end, + transactionType, + transactionName, + uiFilters: JSON.stringify(uiFilters), + }, }, - query: { - start, - end, - transactionType, - transactionName, - uiFilters: JSON.stringify(uiFilters), - }, - }, - }); - } - }, [serviceName, start, end, uiFilters, transactionType, transactionName]); + }); + } + }, + [serviceName, start, end, uiFilters, transactionType, transactionName] + ); const errorRates = data?.transactionErrorRate || []; diff --git a/x-pack/plugins/apm/public/context/annotations/annotations_context.tsx b/x-pack/plugins/apm/public/context/annotations/annotations_context.tsx index 77285f976d850..9d1b6d70f9738 100644 --- a/x-pack/plugins/apm/public/context/annotations/annotations_context.tsx +++ b/x-pack/plugins/apm/public/context/annotations/annotations_context.tsx @@ -9,7 +9,6 @@ import { useParams } from 'react-router-dom'; import { Annotation } from '../../../common/annotations'; import { useFetcher } from '../../hooks/use_fetcher'; import { useUrlParams } from '../url_params_context/use_url_params'; -import { callApmApi } from '../../services/rest/createCallApmApi'; export const AnnotationsContext = createContext({ annotations: [] } as { annotations: Annotation[]; @@ -27,23 +26,26 @@ export function AnnotationsContextProvider({ const { start, end } = urlParams; const { environment } = uiFilters; - const { data = INITIAL_STATE } = useFetcher(() => { - if (start && end && serviceName) { - return callApmApi({ - endpoint: 'GET /api/apm/services/{serviceName}/annotation/search', - params: { - path: { - serviceName, + const { data = INITIAL_STATE } = useFetcher( + (callApmApi) => { + if (start && end && serviceName) { + return callApmApi({ + endpoint: 'GET /api/apm/services/{serviceName}/annotation/search', + params: { + path: { + serviceName, + }, + query: { + start, + end, + environment, + }, }, - query: { - start, - end, - environment, - }, - }, - }); - } - }, [start, end, environment, serviceName]); + }); + } + }, + [start, end, environment, serviceName] + ); return ; } diff --git a/x-pack/plugins/apm/public/context/url_params_context/helpers.test.ts b/x-pack/plugins/apm/public/context/url_params_context/helpers.test.ts index 587cb172eeab7..5f3c8842ad549 100644 --- a/x-pack/plugins/apm/public/context/url_params_context/helpers.test.ts +++ b/x-pack/plugins/apm/public/context/url_params_context/helpers.test.ts @@ -9,29 +9,53 @@ import moment from 'moment-timezone'; import * as helpers from './helpers'; describe('url_params_context helpers', () => { - describe('getParsedDate', () => { - describe('given undefined', () => { - it('returns undefined', () => { - expect(helpers.getParsedDate(undefined)).toBeUndefined(); + describe('getDateRange', () => { + describe('with non-rounded dates', () => { + describe('one minute', () => { + it('rounds the values', () => { + expect( + helpers.getDateRange({ + state: {}, + rangeFrom: '2021-01-28T05:47:52.134Z', + rangeTo: '2021-01-28T05:48:55.304Z', + }) + ).toEqual({ + start: '2021-01-28T05:47:50.000Z', + end: '2021-01-28T05:49:00.000Z', + }); + }); }); - }); - - describe('given a parsable date', () => { - it('returns the parsed date', () => { - expect(helpers.getParsedDate('1970-01-01T00:00:00.000Z')).toEqual( - '1970-01-01T00:00:00.000Z' - ); + describe('one day', () => { + it('rounds the values', () => { + expect( + helpers.getDateRange({ + state: {}, + rangeFrom: '2021-01-27T05:46:07.377Z', + rangeTo: '2021-01-28T05:46:13.367Z', + }) + ).toEqual({ + start: '2021-01-27T03:00:00.000Z', + end: '2021-01-28T06:00:00.000Z', + }); + }); }); - }); - describe('given a non-parsable date', () => { - it('returns null', () => { - expect(helpers.getParsedDate('nope')).toEqual(null); + describe('one year', () => { + it('rounds the values', () => { + expect( + helpers.getDateRange({ + state: {}, + rangeFrom: '2020-01-28T05:52:36.290Z', + rangeTo: '2021-01-28T05:52:39.741Z', + }) + ).toEqual({ + start: '2020-01-01T00:00:00.000Z', + end: '2021-02-01T00:00:00.000Z', + }); + }); }); }); - }); - describe('getDateRange', () => { describe('when rangeFrom and rangeTo are not changed', () => { it('returns the previous state', () => { expect( @@ -52,6 +76,45 @@ describe('url_params_context helpers', () => { }); }); + describe('when rangeFrom or rangeTo are falsy', () => { + it('returns the previous state', () => { + // Disable console warning about not receiving a valid date for rangeFrom + jest.spyOn(console, 'warn').mockImplementationOnce(() => {}); + + expect( + helpers.getDateRange({ + state: { + start: '1972-01-01T00:00:00.000Z', + end: '1973-01-01T00:00:00.000Z', + }, + rangeFrom: '', + rangeTo: 'now', + }) + ).toEqual({ + start: '1972-01-01T00:00:00.000Z', + end: '1973-01-01T00:00:00.000Z', + }); + }); + }); + + describe('when the start or end are invalid', () => { + it('returns the previous state', () => { + expect( + helpers.getDateRange({ + state: { + start: '1972-01-01T00:00:00.000Z', + end: '1973-01-01T00:00:00.000Z', + }, + rangeFrom: 'nope', + rangeTo: 'now', + }) + ).toEqual({ + start: '1972-01-01T00:00:00.000Z', + end: '1973-01-01T00:00:00.000Z', + }); + }); + }); + describe('when rangeFrom or rangeTo have changed', () => { it('returns new state', () => { jest.spyOn(datemath, 'parse').mockReturnValue(moment(0).utc()); diff --git a/x-pack/plugins/apm/public/context/url_params_context/helpers.ts b/x-pack/plugins/apm/public/context/url_params_context/helpers.ts index bff2ef5deb86c..0be11d440aecb 100644 --- a/x-pack/plugins/apm/public/context/url_params_context/helpers.ts +++ b/x-pack/plugins/apm/public/context/url_params_context/helpers.ts @@ -4,15 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { compact, pickBy } from 'lodash'; import datemath from '@elastic/datemath'; +import { scaleUtc } from 'd3-scale'; +import { compact, pickBy } from 'lodash'; import { IUrlParams } from './types'; -export function getParsedDate(rawDate?: string, opts = {}) { +function getParsedDate(rawDate?: string, options = {}) { if (rawDate) { - const parsed = datemath.parse(rawDate, opts); - if (parsed) { - return parsed.toISOString(); + const parsed = datemath.parse(rawDate, options); + if (parsed && parsed.isValid()) { + return parsed.toDate(); } } } @@ -26,13 +27,27 @@ export function getDateRange({ rangeFrom?: string; rangeTo?: string; }) { + // If the previous state had the same range, just return that instead of calculating a new range. if (state.rangeFrom === rangeFrom && state.rangeTo === rangeTo) { return { start: state.start, end: state.end }; } + const start = getParsedDate(rangeFrom); + const end = getParsedDate(rangeTo, { roundUp: true }); + + // `getParsedDate` will return undefined for invalid or empty dates. We return + // the previous state if either date is undefined. + if (!start || !end) { + return { start: state.start, end: state.end }; + } + + // Calculate ticks for the time ranges to produce nicely rounded values. + const ticks = scaleUtc().domain([start, end]).nice().ticks(); + + // Return the first and last tick values. return { - start: getParsedDate(rangeFrom), - end: getParsedDate(rangeTo, { roundUp: true }), + start: ticks[0].toISOString(), + end: ticks[ticks.length - 1].toISOString(), }; } diff --git a/x-pack/plugins/apm/public/context/url_params_context/mock_url_params_context_provider.tsx b/x-pack/plugins/apm/public/context/url_params_context/mock_url_params_context_provider.tsx index b593cbd57a9a9..1e546599ee8a3 100644 --- a/x-pack/plugins/apm/public/context/url_params_context/mock_url_params_context_provider.tsx +++ b/x-pack/plugins/apm/public/context/url_params_context/mock_url_params_context_provider.tsx @@ -30,8 +30,9 @@ export function MockUrlParamsContextProvider({ return ( ) { } describe('UrlParamsContext', () => { - beforeEach(() => { + beforeAll(() => { moment.tz.setDefault('Etc/GMT'); }); - afterEach(() => { + afterAll(() => { moment.tz.setDefault(''); }); @@ -50,8 +49,11 @@ describe('UrlParamsContext', () => { const wrapper = mountParams(location); const params = getDataFromOutput(wrapper); - expect(params.start).toEqual('2010-03-15T12:00:00.000Z'); - expect(params.end).toEqual('2010-04-10T12:00:00.000Z'); + + expect([params.start, params.end]).toEqual([ + '2010-03-15T00:00:00.000Z', + '2010-04-11T00:00:00.000Z', + ]); }); it('should update param values if location has changed', () => { @@ -66,8 +68,11 @@ describe('UrlParamsContext', () => { // force an update wrapper.setProps({ abc: 123 }); const params = getDataFromOutput(wrapper); - expect(params.start).toEqual('2009-03-15T12:00:00.000Z'); - expect(params.end).toEqual('2009-04-10T12:00:00.000Z'); + + expect([params.start, params.end]).toEqual([ + '2009-03-15T00:00:00.000Z', + '2009-04-11T00:00:00.000Z', + ]); }); it('should parse relative time ranges on mount', () => { @@ -76,13 +81,20 @@ describe('UrlParamsContext', () => { search: '?rangeFrom=now-1d%2Fd&rangeTo=now-1d%2Fd&transactionId=UPDATED', } as Location; + const nowSpy = jest.spyOn(Date, 'now').mockReturnValue(0); + const wrapper = mountParams(location); // force an update wrapper.setProps({ abc: 123 }); const params = getDataFromOutput(wrapper); - expect(params.start).toEqual(getParsedDate('now-1d/d')); - expect(params.end).toEqual(getParsedDate('now-1d/d', { roundUp: true })); + + expect([params.start, params.end]).toEqual([ + '1969-12-31T00:00:00.000Z', + '1970-01-01T00:00:00.000Z', + ]); + + nowSpy.mockRestore(); }); it('should refresh the time range with new values', async () => { @@ -130,8 +142,11 @@ describe('UrlParamsContext', () => { expect(calls.length).toBe(2); const params = getDataFromOutput(wrapper); - expect(params.start).toEqual('2005-09-20T12:00:00.000Z'); - expect(params.end).toEqual('2005-10-21T12:00:00.000Z'); + + expect([params.start, params.end]).toEqual([ + '2005-09-19T00:00:00.000Z', + '2005-10-23T00:00:00.000Z', + ]); }); it('should refresh the time range with new values if time range is relative', async () => { @@ -177,7 +192,10 @@ describe('UrlParamsContext', () => { await waitFor(() => {}); const params = getDataFromOutput(wrapper); - expect(params.start).toEqual('2000-06-14T00:00:00.000Z'); - expect(params.end).toEqual('2000-06-14T23:59:59.999Z'); + + expect([params.start, params.end]).toEqual([ + '2000-06-14T00:00:00.000Z', + '2000-06-15T00:00:00.000Z', + ]); }); }); diff --git a/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx index 0a3f8459ff002..f66a45db261a7 100644 --- a/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx +++ b/x-pack/plugins/apm/public/context/url_params_context/url_params_context.tsx @@ -4,28 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ +import { mapValues } from 'lodash'; import React, { createContext, - useMemo, useCallback, + useMemo, useRef, useState, } from 'react'; import { withRouter } from 'react-router-dom'; -import { uniqueId, mapValues } from 'lodash'; -import { IUrlParams } from './types'; -import { getParsedDate } from './helpers'; -import { resolveUrlParams } from './resolve_url_params'; -import { UIFilters } from '../../../typings/ui_filters'; -import { - localUIFilterNames, - - // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from '../../../server/lib/ui_filters/local_ui_filters/config'; +import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values'; +import { LocalUIFilterName } from '../../../common/ui_filter'; import { pickKeys } from '../../../common/utils/pick_keys'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { localUIFilterNames } from '../../../server/lib/ui_filters/local_ui_filters/config'; +import { UIFilters } from '../../../typings/ui_filters'; import { useDeepObjectIdentity } from '../../hooks/useDeepObjectIdentity'; -import { LocalUIFilterName } from '../../../common/ui_filter'; -import { ENVIRONMENT_ALL } from '../../../common/environment_filter_values'; +import { getDateRange } from './helpers'; +import { resolveUrlParams } from './resolve_url_params'; +import { IUrlParams } from './types'; interface TimeRange { rangeFrom: string; @@ -49,9 +46,10 @@ function useUiFilters(params: IUrlParams): UIFilters { const defaultRefresh = (_time: TimeRange) => {}; const UrlParamsContext = createContext({ - urlParams: {} as IUrlParams, + rangeId: 0, refreshTimeRange: defaultRefresh, uiFilters: {} as UIFilters, + urlParams: {} as IUrlParams, }); const UrlParamsProvider: React.ComponentClass<{}> = withRouter( @@ -60,7 +58,8 @@ const UrlParamsProvider: React.ComponentClass<{}> = withRouter( const { start, end, rangeFrom, rangeTo } = refUrlParams.current; - const [, forceUpdate] = useState(''); + // Counter to force an update in useFetcher when the refresh button is clicked. + const [rangeId, setRangeId] = useState(0); const urlParams = useMemo( () => @@ -75,28 +74,25 @@ const UrlParamsProvider: React.ComponentClass<{}> = withRouter( refUrlParams.current = urlParams; - const refreshTimeRange = useCallback( - (timeRange: TimeRange) => { - refUrlParams.current = { - ...refUrlParams.current, - start: getParsedDate(timeRange.rangeFrom), - end: getParsedDate(timeRange.rangeTo, { roundUp: true }), - }; - - forceUpdate(uniqueId()); - }, - [forceUpdate] - ); + const refreshTimeRange = useCallback((timeRange: TimeRange) => { + refUrlParams.current = { + ...refUrlParams.current, + ...getDateRange({ state: {}, ...timeRange }), + }; + + setRangeId((prevRangeId) => prevRangeId + 1); + }, []); const uiFilters = useUiFilters(urlParams); const contextValue = useMemo(() => { return { - urlParams, + rangeId, refreshTimeRange, + urlParams, uiFilters, }; - }, [urlParams, refreshTimeRange, uiFilters]); + }, [rangeId, refreshTimeRange, uiFilters, urlParams]); return ( diff --git a/x-pack/plugins/apm/public/hooks/useLocalUIFilters.ts b/x-pack/plugins/apm/public/hooks/useLocalUIFilters.ts index dabdf41c63f04..fbdee617864df 100644 --- a/x-pack/plugins/apm/public/hooks/useLocalUIFilters.ts +++ b/x-pack/plugins/apm/public/hooks/useLocalUIFilters.ts @@ -16,7 +16,6 @@ import { } from '../../server/lib/ui_filters/local_ui_filters/config'; import { fromQuery, toQuery } from '../components/shared/Links/url_helpers'; import { removeUndefinedProps } from '../context/url_params_context/helpers'; -import { useCallApi } from './useCallApi'; import { useFetcher } from './use_fetcher'; import { useUrlParams } from '../context/url_params_context/use_url_params'; import { LocalUIFilterName } from '../../common/ui_filter'; @@ -43,7 +42,6 @@ export function useLocalUIFilters({ }) { const history = useHistory(); const { uiFilters, urlParams } = useUrlParams(); - const callApi = useCallApi(); const values = pickKeys(uiFilters, ...filterNames); @@ -69,30 +67,34 @@ export function useLocalUIFilters({ }); }; - const { data = getInitialData(filterNames), status } = useFetcher(() => { - if (shouldFetch) { - return callApi({ - method: 'GET', - pathname: `/api/apm/ui_filters/local_filters/${projection}`, - query: { - uiFilters: JSON.stringify(uiFilters), - start: urlParams.start, - end: urlParams.end, - filterNames: JSON.stringify(filterNames), - ...params, - }, - }); - } - }, [ - callApi, - projection, - uiFilters, - urlParams.start, - urlParams.end, - filterNames, - params, - shouldFetch, - ]); + const { data = getInitialData(filterNames), status } = useFetcher( + (callApmApi) => { + if (shouldFetch && urlParams.start && urlParams.end) { + return callApmApi({ + endpoint: `GET /api/apm/ui_filters/local_filters/${projection}` as const, + params: { + query: { + uiFilters: JSON.stringify(uiFilters), + start: urlParams.start, + end: urlParams.end, + // type expects string constants, but we have to send it as json + filterNames: JSON.stringify(filterNames) as any, + ...params, + }, + }, + }); + } + }, + [ + projection, + uiFilters, + urlParams.start, + urlParams.end, + filterNames, + params, + shouldFetch, + ] + ); const filters = data.map((filter) => ({ ...filter, diff --git a/x-pack/plugins/apm/public/hooks/use_environments_fetcher.tsx b/x-pack/plugins/apm/public/hooks/use_environments_fetcher.tsx index 1ad151b8c7e90..38a8610b82acb 100644 --- a/x-pack/plugins/apm/public/hooks/use_environments_fetcher.tsx +++ b/x-pack/plugins/apm/public/hooks/use_environments_fetcher.tsx @@ -10,7 +10,6 @@ import { ENVIRONMENT_ALL, ENVIRONMENT_NOT_DEFINED, } from '../../common/environment_filter_values'; -import { callApmApi } from '../services/rest/createCallApmApi'; function getEnvironmentOptions(environments: string[]) { const environmentOptions = environments @@ -32,20 +31,23 @@ export function useEnvironmentsFetcher({ start?: string; end?: string; }) { - const { data: environments = [], status = 'loading' } = useFetcher(() => { - if (start && end) { - return callApmApi({ - endpoint: 'GET /api/apm/ui_filters/environments', - params: { - query: { - start, - end, - serviceName, + const { data: environments = [], status = 'loading' } = useFetcher( + (callApmApi) => { + if (start && end) { + return callApmApi({ + endpoint: 'GET /api/apm/ui_filters/environments', + params: { + query: { + start, + end, + serviceName, + }, }, - }, - }); - } - }, [start, end, serviceName]); + }); + } + }, + [start, end, serviceName] + ); const environmentOptions = useMemo( () => getEnvironmentOptions(environments), diff --git a/x-pack/plugins/apm/public/hooks/use_fetcher.tsx b/x-pack/plugins/apm/public/hooks/use_fetcher.tsx index 2b58f30a9ec64..0da96691be957 100644 --- a/x-pack/plugins/apm/public/hooks/use_fetcher.tsx +++ b/x-pack/plugins/apm/public/hooks/use_fetcher.tsx @@ -8,8 +8,12 @@ import { i18n } from '@kbn/i18n'; import React, { useEffect, useMemo, useState } from 'react'; import { IHttpFetchError } from 'src/core/public'; import { toMountPoint } from '../../../../../src/plugins/kibana_react/public'; -import { APMClient, callApmApi } from '../services/rest/createCallApmApi'; +import { + callApmApi, + AutoAbortedAPMClient, +} from '../services/rest/createCallApmApi'; import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context'; +import { useUrlParams } from '../context/url_params_context/use_url_params'; export enum FETCH_STATUS { LOADING = 'loading', @@ -39,6 +43,14 @@ function getDetailsFromErrorResponse(error: IHttpFetchError) { ); } +const createAutoAbortedAPMClient = ( + signal: AbortSignal +): AutoAbortedAPMClient => { + return ((options: Parameters[0]) => { + return callApmApi({ ...options, signal }); + }) as AutoAbortedAPMClient; +}; + // fetcher functions can return undefined OR a promise. Previously we had a more simple type // but it led to issues when using object destructuring with default values type InferResponseType = Exclude extends Promise< @@ -48,7 +60,7 @@ type InferResponseType = Exclude extends Promise< : unknown; export function useFetcher( - fn: (callApmApi: APMClient) => TReturn, + fn: (callApmApi: AutoAbortedAPMClient) => TReturn, fnDeps: any[], options: { preservePreviousData?: boolean; @@ -64,12 +76,19 @@ export function useFetcher( status: FETCH_STATUS.NOT_INITIATED, }); const [counter, setCounter] = useState(0); + const { rangeId } = useUrlParams(); useEffect(() => { - let didCancel = false; + let controller: AbortController = new AbortController(); async function doFetch() { - const promise = fn(callApmApi); + controller.abort(); + + controller = new AbortController(); + + const signal = controller.signal; + + const promise = fn(createAutoAbortedAPMClient(signal)); // if `fn` doesn't return a promise it is a signal that data fetching was not initiated. // This can happen if the data fetching is conditional (based on certain inputs). // In these cases it is not desirable to invoke the global loading spinner, or change the status to success @@ -85,7 +104,11 @@ export function useFetcher( try { const data = await promise; - if (!didCancel) { + // when http fetches are aborted, the promise will be rejected + // and this code is never reached. For async operations that are + // not cancellable, we need to check whether the signal was + // aborted before updating the result. + if (!signal.aborted) { setResult({ data, status: FETCH_STATUS.SUCCESS, @@ -95,7 +118,7 @@ export function useFetcher( } catch (e) { const err = e as Error | IHttpFetchError; - if (!didCancel) { + if (!signal.aborted) { const errorDetails = 'response' in err ? getDetailsFromErrorResponse(err) : err.message; @@ -130,12 +153,13 @@ export function useFetcher( doFetch(); return () => { - didCancel = true; + controller.abort(); }; /* eslint-disable react-hooks/exhaustive-deps */ }, [ counter, preservePreviousData, + rangeId, showToastOnError, ...fnDeps, /* eslint-enable react-hooks/exhaustive-deps */ diff --git a/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts b/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts index a0ed51be685c7..ac98bbab5775b 100644 --- a/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts +++ b/x-pack/plugins/apm/public/services/rest/apm_observability_overview_fetchers.ts @@ -20,6 +20,7 @@ export const fetchObservabilityOverviewPageData = async ({ }: FetchDataParams): Promise => { const data = await callApmApi({ endpoint: 'GET /api/apm/observability_overview', + signal: null, params: { query: { start: new Date(absoluteTime.start).toISOString(), @@ -59,5 +60,6 @@ export const fetchObservabilityOverviewPageData = async ({ export async function hasData() { return await callApmApi({ endpoint: 'GET /api/apm/observability_overview/has_data', + signal: null, }); } diff --git a/x-pack/plugins/apm/public/services/rest/callApi.ts b/x-pack/plugins/apm/public/services/rest/callApi.ts index 4ee12908b7c79..d14cbc5f6d63e 100644 --- a/x-pack/plugins/apm/public/services/rest/callApi.ts +++ b/x-pack/plugins/apm/public/services/rest/callApi.ts @@ -87,5 +87,6 @@ function isCachable(fetchOptions: FetchOptions) { // order the options object to make sure that two objects with the same arguments, produce produce the // same cache key regardless of the order of properties function getCacheKey(options: FetchOptions) { - return hash(options); + const { pathname, method, body, query, headers } = options; + return hash({ pathname, method, body, query, headers }); } diff --git a/x-pack/plugins/apm/public/services/rest/createCallApmApi.ts b/x-pack/plugins/apm/public/services/rest/createCallApmApi.ts index 2760ed558865a..b77233982ffc5 100644 --- a/x-pack/plugins/apm/public/services/rest/createCallApmApi.ts +++ b/x-pack/plugins/apm/public/services/rest/createCallApmApi.ts @@ -12,11 +12,14 @@ import { APMAPI } from '../../../server/routes/create_apm_api'; import { Client } from '../../../server/routes/typings'; export type APMClient = Client; +export type AutoAbortedAPMClient = Client; + export type APMClientOptions = Omit< FetchOptions, - 'query' | 'body' | 'pathname' + 'query' | 'body' | 'pathname' | 'signal' > & { endpoint: string; + signal: AbortSignal | null; params?: { body?: any; query?: any; diff --git a/x-pack/plugins/apm/public/services/rest/index_pattern.ts b/x-pack/plugins/apm/public/services/rest/index_pattern.ts index 6ec542ab6baf3..cea3bcc0b68cc 100644 --- a/x-pack/plugins/apm/public/services/rest/index_pattern.ts +++ b/x-pack/plugins/apm/public/services/rest/index_pattern.ts @@ -9,11 +9,13 @@ import { callApmApi } from './createCallApmApi'; export const createStaticIndexPattern = async () => { return await callApmApi({ endpoint: 'POST /api/apm/index_pattern/static', + signal: null, }); }; export const getApmIndexPatternTitle = async () => { return await callApmApi({ endpoint: 'GET /api/apm/index_pattern/title', + signal: null, }); }; diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.test.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.test.ts index f58e04061254d..87cb60a543193 100644 --- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.test.ts +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.test.ts @@ -65,10 +65,12 @@ describe('createApmEventClient', () => { await new Promise((resolve) => { setTimeout(() => { + incomingRequest.on('abort', () => { + setTimeout(() => { + resolve(undefined); + }, 0); + }); incomingRequest.abort(); - setTimeout(() => { - resolve(undefined); - }, 0); }, 50); }); diff --git a/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts b/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts index f00941d6e6800..47a13185ff90f 100644 --- a/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts +++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts @@ -92,7 +92,7 @@ function getMockRequest() { url: '', events: { aborted$: { - subscribe: jest.fn(), + subscribe: jest.fn().mockReturnValue({ unsubscribe: jest.fn() }), }, }, } as unknown) as KibanaRequest; diff --git a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/index.ts b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/index.ts index 2b209f8f6a80a..8d6b9bfc1a4e6 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service_dependencies/index.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service_dependencies/index.ts @@ -196,19 +196,26 @@ export async function getServiceDependencies({ }); const latencySums = metricsByResolvedAddress - .map((metrics) => metrics.latency.value) + .map( + (metric) => (metric.latency.value ?? 0) * (metric.throughput.value ?? 0) + ) .filter(isFiniteNumber); const minLatencySum = Math.min(...latencySums); const maxLatencySum = Math.max(...latencySums); - return metricsByResolvedAddress.map((metric) => ({ - ...metric, - impact: - metric.latency.value === null - ? 0 - : ((metric.latency.value - minLatencySum) / + return metricsByResolvedAddress.map((metric) => { + const impact = + isFiniteNumber(metric.latency.value) && + isFiniteNumber(metric.throughput.value) + ? ((metric.latency.value * metric.throughput.value - minLatencySum) / (maxLatencySum - minLatencySum)) * - 100, - })); + 100 + : 0; + + return { + ...metric, + impact, + }; + }); } diff --git a/x-pack/plugins/apm/server/routes/create_api/index.ts b/x-pack/plugins/apm/server/routes/create_api/index.ts index 721badf7fc025..6f6ec4f06b6cb 100644 --- a/x-pack/plugins/apm/server/routes/create_api/index.ts +++ b/x-pack/plugins/apm/server/routes/create_api/index.ts @@ -10,6 +10,7 @@ import * as t from 'io-ts'; import { PathReporter } from 'io-ts/lib/PathReporter'; import { isLeft } from 'fp-ts/lib/Either'; import { KibanaResponseFactory, RouteRegistrar } from 'src/core/server'; +import { RequestAbortedError } from '@elastic/elasticsearch/lib/errors'; import { merge } from '../../../common/runtime_types/merge'; import { strictKeysRt } from '../../../common/runtime_types/strict_keys_rt'; import { APMConfig } from '../..'; @@ -132,6 +133,15 @@ export function createApi() { if (Boom.isBoom(error)) { return convertBoomToKibanaResponse(error, response); } + + if (error instanceof RequestAbortedError) { + return response.custom({ + statusCode: 499, + body: { + message: 'Client closed request', + }, + }); + } throw error; } } diff --git a/x-pack/plugins/apm/server/routes/typings.ts b/x-pack/plugins/apm/server/routes/typings.ts index 7d7a5c3b0dab3..4cc3c747b201f 100644 --- a/x-pack/plugins/apm/server/routes/typings.ts +++ b/x-pack/plugins/apm/server/routes/typings.ts @@ -131,15 +131,20 @@ type MaybeOptional }> = RequiredKeys< ? { params?: T['params'] } : { params: T['params'] }; -export type Client = < - TEndpoint extends keyof TRouteState & string ->( - options: Omit & { +export type Client< + TRouteState, + TOptions extends { abortable: boolean } = { abortable: true } +> = ( + options: Omit< + FetchOptions, + 'query' | 'body' | 'pathname' | 'method' | 'signal' + > & { forceCache?: boolean; endpoint: TEndpoint; } & (TRouteState[TEndpoint] extends { params: t.Any } ? MaybeOptional<{ params: t.TypeOf }> - : {}) + : {}) & + (TOptions extends { abortable: true } ? { signal: AbortSignal | null } : {}) ) => Promise< TRouteState[TEndpoint] extends { ret: any } ? TRouteState[TEndpoint]['ret'] diff --git a/x-pack/plugins/apm/typings/es_schemas/raw/fields/kubernetes.ts b/x-pack/plugins/apm/typings/es_schemas/raw/fields/kubernetes.ts index 5bec848056dd6..65b3542ff2171 100644 --- a/x-pack/plugins/apm/typings/es_schemas/raw/fields/kubernetes.ts +++ b/x-pack/plugins/apm/typings/es_schemas/raw/fields/kubernetes.ts @@ -5,5 +5,5 @@ */ export interface Kubernetes { - pod: { uid: string; [key: string]: unknown }; + pod?: { uid: string; [key: string]: unknown }; } diff --git a/x-pack/plugins/case/common/api/connectors/mappings.ts b/x-pack/plugins/case/common/api/connectors/mappings.ts index f8e9830fed7c1..b9f84d406a184 100644 --- a/x-pack/plugins/case/common/api/connectors/mappings.ts +++ b/x-pack/plugins/case/common/api/connectors/mappings.ts @@ -16,8 +16,8 @@ import { Incident as ResilientIncident, } from '../../../../actions/server/builtin_action_types/resilient/types'; import { - PushToServiceApiParams as ServiceNowPushToServiceApiParams, - Incident as ServiceNowIncident, + PushToServiceApiParamsITSM as ServiceNowITSMPushToServiceApiParams, + ServiceNowITSMIncident, } from '../../../../actions/server/builtin_action_types/servicenow/types'; import { ResilientFieldsRT } from './resilient'; import { ServiceNowFieldsRT } from './servicenow'; @@ -33,13 +33,13 @@ export interface ElasticUser { export { JiraPushToServiceApiParams, ResilientPushToServiceApiParams, - ServiceNowPushToServiceApiParams, + ServiceNowITSMPushToServiceApiParams, }; -export type Incident = JiraIncident | ResilientIncident | ServiceNowIncident; +export type Incident = JiraIncident | ResilientIncident | ServiceNowITSMIncident; export type PushToServiceApiParams = | JiraPushToServiceApiParams | ResilientPushToServiceApiParams - | ServiceNowPushToServiceApiParams; + | ServiceNowITSMPushToServiceApiParams; const ActionTypeRT = rt.union([ rt.literal('append'), diff --git a/x-pack/plugins/case/server/routes/api/cases/configure/utils.ts b/x-pack/plugins/case/server/routes/api/cases/configure/utils.ts index 89109af4cecb9..9e903b66459a9 100644 --- a/x-pack/plugins/case/server/routes/api/cases/configure/utils.ts +++ b/x-pack/plugins/case/server/routes/api/cases/configure/utils.ts @@ -19,7 +19,7 @@ import { PrepareFieldsForTransformArgs, PushToServiceApiParams, ResilientPushToServiceApiParams, - ServiceNowPushToServiceApiParams, + ServiceNowITSMPushToServiceApiParams, SimpleComment, Transformer, TransformerArgs, @@ -105,7 +105,11 @@ export const serviceFormatter = ( thirdPartyName: 'Resilient', }; case ConnectorTypes.servicenow: - const { severity, urgency, impact } = params as ServiceNowPushToServiceApiParams['incident']; + const { + severity, + urgency, + impact, + } = params as ServiceNowITSMPushToServiceApiParams['incident']; return { incident: { severity, urgency, impact }, thirdPartyName: 'ServiceNow', diff --git a/x-pack/plugins/code/tsconfig.json b/x-pack/plugins/code/tsconfig.json new file mode 100644 index 0000000000000..9c0b0ed21330f --- /dev/null +++ b/x-pack/plugins/code/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "server/**/*" + ], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + ] +} diff --git a/x-pack/plugins/data_enhanced/common/index.ts b/x-pack/plugins/data_enhanced/common/index.ts index 669c33230a34c..8c500ef21ffcf 100644 --- a/x-pack/plugins/data_enhanced/common/index.ts +++ b/x-pack/plugins/data_enhanced/common/index.ts @@ -5,6 +5,7 @@ */ export { + SEARCH_SESSION_TYPE, ENHANCED_ES_SEARCH_STRATEGY, EQL_SEARCH_STRATEGY, EqlRequestParams, diff --git a/x-pack/plugins/data_enhanced/common/search/session/types.ts b/x-pack/plugins/data_enhanced/common/search/session/types.ts index 9eefdf43aa245..6d07f4b731fae 100644 --- a/x-pack/plugins/data_enhanced/common/search/session/types.ts +++ b/x-pack/plugins/data_enhanced/common/search/session/types.ts @@ -6,19 +6,25 @@ import { SearchSessionStatus } from './'; +export const SEARCH_SESSION_TYPE = 'search-session'; export interface SearchSessionSavedObjectAttributes { + sessionId: string; /** * User-facing session name to be displayed in session management */ - name: string; + name?: string; /** * App that created the session. e.g 'discover' */ - appId: string; + appId?: string; /** * Creation time of the session */ created: string; + /** + * Last touch time of the session + */ + touched: string; /** * Expiration time of the session. Expiration itself is managed by Elasticsearch. */ @@ -30,22 +36,28 @@ export interface SearchSessionSavedObjectAttributes { /** * urlGeneratorId */ - urlGeneratorId: string; + urlGeneratorId?: string; /** * The application state that was used to create the session. * Should be used, for example, to re-load an expired search session. */ - initialState: Record; + initialState?: Record; /** * Application state that should be used to restore the session. * For example, relative dates are conveted to absolute ones. */ - restoreState: Record; + restoreState?: Record; /** * Mapping of search request hashes to their corresponsing info (async search id, etc.) */ idMapping: Record; + + /** + * This value is true if the session was actively stored by the user. If it is false, the session may be purged by the system. + */ + persisted: boolean; } + export interface SearchSessionRequestInfo { /** * ID of the async search request diff --git a/x-pack/plugins/data_enhanced/config.ts b/x-pack/plugins/data_enhanced/config.ts index 981c398019832..3c2c2084b2e2c 100644 --- a/x-pack/plugins/data_enhanced/config.ts +++ b/x-pack/plugins/data_enhanced/config.ts @@ -9,15 +9,49 @@ import { schema, TypeOf } from '@kbn/config-schema'; export const configSchema = schema.object({ search: schema.object({ sessions: schema.object({ + /** + * Turns the feature on \ off (incl. removing indicator and management screens) + */ enabled: schema.boolean({ defaultValue: false }), + /** + * pageSize controls how many search session objects we load at once while monitoring + * session completion + */ pageSize: schema.number({ defaultValue: 10000 }), + /** + * trackingInterval controls how often we track search session objects progress + */ trackingInterval: schema.duration({ defaultValue: '10s' }), - inMemTimeout: schema.duration({ defaultValue: '1m' }), + /** + * notTouchedTimeout controls how long do we store unpersisted search session results, + * after the last search in the session has completed + */ + notTouchedTimeout: schema.duration({ defaultValue: '5m' }), + /** + * notTouchedInProgressTimeout controls how long do allow a search session to run after + * a user has navigated away without persisting + */ + notTouchedInProgressTimeout: schema.duration({ defaultValue: '1m' }), + /** + * maxUpdateRetries controls how many retries we perform while attempting to save a search session + */ maxUpdateRetries: schema.number({ defaultValue: 3 }), + /** + * defaultExpiration controls how long search sessions are valid for, until they are expired. + */ defaultExpiration: schema.duration({ defaultValue: '7d' }), management: schema.object({ + /** + * maxSessions controls how many saved search sessions we display per page on the management screen. + */ maxSessions: schema.number({ defaultValue: 10000 }), + /** + * refreshInterval controls how often we refresh the management screen. + */ refreshInterval: schema.duration({ defaultValue: '10s' }), + /** + * refreshTimeout controls how often we refresh the management screen. + */ refreshTimeout: schema.duration({ defaultValue: '1m' }), expiresSoonWarning: schema.duration({ defaultValue: '1d' }), }), diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts index c6a3d088b3cda..25c06d1d2e278 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/api.ts @@ -10,12 +10,11 @@ import moment from 'moment'; import { from, race, timer } from 'rxjs'; import { mapTo, tap } from 'rxjs/operators'; import type { SharePluginStart } from 'src/plugins/share/public'; -import { SessionsConfigSchema } from '../'; -import type { ISessionsClient } from '../../../../../../../src/plugins/data/public'; -import type { SearchSessionSavedObjectAttributes } from '../../../../common'; +import { ISessionsClient } from '../../../../../../../src/plugins/data/public'; import { SearchSessionStatus } from '../../../../common/search'; import { ACTION } from '../components/actions'; -import { UISession } from '../types'; +import { PersistedSearchSessionSavedObjectAttributes, UISession } from '../types'; +import { SessionsConfigSchema } from '..'; type UrlGeneratorsStart = SharePluginStart['urlGenerators']; @@ -48,7 +47,7 @@ async function getUrlFromState( // Helper: factory for a function to map server objects to UI objects const mapToUISession = (urls: UrlGeneratorsStart, config: SessionsConfigSchema) => async ( - savedObject: SavedObject + savedObject: SavedObject ): Promise => { const { name, @@ -110,6 +109,8 @@ export class SearchSessionsMgmtAPI { perPage: mgmtConfig.maxSessions, sortField: 'created', sortOrder: 'asc', + searchFields: ['persisted'], + search: 'true', }) ); const timeout$ = timer(refreshTimeout.asMilliseconds()).pipe( @@ -129,7 +130,7 @@ export class SearchSessionsMgmtAPI { const result = await race(fetch$, timeout$).toPromise(); if (result && result.saved_objects) { const savedObjects = result.saved_objects as Array< - SavedObject + SavedObject >; return await Promise.all(savedObjects.map(mapToUISession(this.deps.urls, this.config))); } diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/types.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/types.ts index 78b91f7ca8ac2..3b0159a1e8faa 100644 --- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/types.ts +++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/types.ts @@ -4,11 +4,23 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SearchSessionStatus } from '../../../common'; +import { SearchSessionSavedObjectAttributes, SearchSessionStatus } from '../../../common'; import { ACTION } from './components/actions'; export const DATE_STRING_FORMAT = 'D MMM, YYYY, HH:mm:ss'; +/** + * Some properties are optional for a non-persisted Search Session. + * This interface makes them mandatory, because management only shows persisted search sessions. + */ +export type PersistedSearchSessionSavedObjectAttributes = SearchSessionSavedObjectAttributes & + Required< + Pick< + SearchSessionSavedObjectAttributes, + 'name' | 'appId' | 'urlGeneratorId' | 'initialState' | 'restoreState' + > + >; + export interface UISession { id: string; name: string; diff --git a/x-pack/plugins/data_enhanced/server/plugin.ts b/x-pack/plugins/data_enhanced/server/plugin.ts index cff0ee3efd738..834f1669e2d7e 100644 --- a/x-pack/plugins/data_enhanced/server/plugin.ts +++ b/x-pack/plugins/data_enhanced/server/plugin.ts @@ -5,6 +5,7 @@ */ import { CoreSetup, CoreStart, Logger, Plugin, PluginInitializerContext } from 'kibana/server'; +import { Observable } from 'rxjs'; import { TaskManagerSetupContract, TaskManagerStartContract } from '../../task_manager/server'; import { PluginSetup as DataPluginSetup, @@ -22,6 +23,7 @@ import { } from './search'; import { getUiSettings } from './ui_settings'; import type { DataEnhancedRequestHandlerContext } from './type'; +import { ConfigSchema } from '../config'; interface SetupDependencies { data: DataPluginSetup; @@ -37,9 +39,11 @@ export class EnhancedDataServerPlugin implements Plugin { private readonly logger: Logger; private sessionService!: SearchSessionService; + private config$: Observable; - constructor(private initializerContext: PluginInitializerContext) { + constructor(private initializerContext: PluginInitializerContext) { this.logger = initializerContext.logger.get('data_enhanced'); + this.config$ = this.initializerContext.config.create(); } public setup(core: CoreSetup, deps: SetupDependencies) { @@ -51,6 +55,7 @@ export class EnhancedDataServerPlugin deps.data.search.registerSearchStrategy( ENHANCED_ES_SEARCH_STRATEGY, enhancedEsSearchStrategyProvider( + this.config$, this.initializerContext.config.legacy.globalConfig$, this.logger, usage diff --git a/x-pack/plugins/data_enhanced/server/routes/mocks.ts b/x-pack/plugins/data_enhanced/server/routes/mocks.ts deleted file mode 100644 index 4bad563bf393b..0000000000000 --- a/x-pack/plugins/data_enhanced/server/routes/mocks.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 type { DataRequestHandlerContext } from '../../../../../src/plugins/data/server'; -import { coreMock } from '../../../../../src/core/server/mocks'; - -export function createSearchRequestHandlerContext() { - return ({ - core: coreMock.createRequestHandlerContext(), - search: { - search: jest.fn(), - cancel: jest.fn(), - session: { - search: jest.fn(), - save: jest.fn(), - get: jest.fn(), - find: jest.fn(), - delete: jest.fn(), - update: jest.fn(), - }, - }, - } as unknown) as jest.Mocked; -} diff --git a/x-pack/plugins/data_enhanced/server/routes/session.test.ts b/x-pack/plugins/data_enhanced/server/routes/session.test.ts index c4433b562e97a..6af7618e43d9b 100644 --- a/x-pack/plugins/data_enhanced/server/routes/session.test.ts +++ b/x-pack/plugins/data_enhanced/server/routes/session.test.ts @@ -12,7 +12,7 @@ import type { PluginStart as DataPluginStart, DataRequestHandlerContext, } from '../../../../../src/plugins/data/server'; -import { createSearchRequestHandlerContext } from './mocks'; +import { dataPluginMock } from '../../../../../src/plugins/data/server/mocks'; import { registerSessionRoutes } from './session'; describe('registerSessionRoutes', () => { @@ -23,11 +23,11 @@ describe('registerSessionRoutes', () => { beforeEach(() => { mockCoreSetup = coreMock.createSetup(); mockLogger = coreMock.createPluginInitializerContext().logger.get(); - mockContext = createSearchRequestHandlerContext(); + mockContext = dataPluginMock.createRequestHandlerContext(); registerSessionRoutes(mockCoreSetup.http.createRouter(), mockLogger); }); - it('save calls session.save with sessionId and attributes', async () => { + it('save calls saveSession with sessionId and attributes', async () => { const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; const name = 'my saved background search session'; const body = { sessionId, name }; @@ -40,10 +40,10 @@ describe('registerSessionRoutes', () => { saveHandler(mockContext, mockRequest, mockResponse); - expect(mockContext.search!.session.save).toHaveBeenCalledWith(sessionId, { name }); + expect(mockContext.search!.saveSession).toHaveBeenCalledWith(sessionId, { name }); }); - it('get calls session.get with sessionId', async () => { + it('get calls getSession with sessionId', async () => { const id = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; const params = { id }; @@ -55,10 +55,10 @@ describe('registerSessionRoutes', () => { getHandler(mockContext, mockRequest, mockResponse); - expect(mockContext.search!.session.get).toHaveBeenCalledWith(id); + expect(mockContext.search!.getSession).toHaveBeenCalledWith(id); }); - it('find calls session.find with options', async () => { + it('find calls findSession with options', async () => { const page = 1; const perPage = 5; const sortField = 'my_field'; @@ -74,10 +74,10 @@ describe('registerSessionRoutes', () => { findHandler(mockContext, mockRequest, mockResponse); - expect(mockContext.search!.session.find).toHaveBeenCalledWith(body); + expect(mockContext.search!.findSessions).toHaveBeenCalledWith(body); }); - it('update calls session.update with id and attributes', async () => { + it('update calls updateSession with id and attributes', async () => { const id = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; const name = 'my saved background search session'; const expires = new Date().toISOString(); @@ -92,10 +92,10 @@ describe('registerSessionRoutes', () => { updateHandler(mockContext, mockRequest, mockResponse); - expect(mockContext.search!.session.update).toHaveBeenCalledWith(id, body); + expect(mockContext.search!.updateSession).toHaveBeenCalledWith(id, body); }); - it('delete calls session.delete with id', async () => { + it('delete calls cancelSession with id', async () => { const id = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; const params = { id }; @@ -107,6 +107,23 @@ describe('registerSessionRoutes', () => { deleteHandler(mockContext, mockRequest, mockResponse); - expect(mockContext.search!.session.delete).toHaveBeenCalledWith(id); + expect(mockContext.search!.cancelSession).toHaveBeenCalledWith(id); + }); + + it('extend calls extendSession with id', async () => { + const id = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; + const expires = new Date().toISOString(); + const params = { id }; + const body = { expires }; + + const mockRequest = httpServerMock.createKibanaRequest({ params, body }); + const mockResponse = httpServerMock.createResponseFactory(); + + const mockRouter = mockCoreSetup.http.createRouter.mock.results[0].value; + const [, , [, extendHandler]] = mockRouter.post.mock.calls; + + extendHandler(mockContext, mockRequest, mockResponse); + + expect(mockContext.search.extendSession).toHaveBeenCalledWith(id, new Date(expires)); }); }); diff --git a/x-pack/plugins/data_enhanced/server/routes/session.ts b/x-pack/plugins/data_enhanced/server/routes/session.ts index cbf683bd18fd2..4855021a54f89 100644 --- a/x-pack/plugins/data_enhanced/server/routes/session.ts +++ b/x-pack/plugins/data_enhanced/server/routes/session.ts @@ -37,7 +37,7 @@ export function registerSessionRoutes(router: DataEnhancedPluginRouter, logger: } = request.body; try { - const response = await context.search!.session.save(sessionId, { + const response = await context.search!.saveSession(sessionId, { name, appId, expires, @@ -68,7 +68,7 @@ export function registerSessionRoutes(router: DataEnhancedPluginRouter, logger: async (context, request, res) => { const { id } = request.params; try { - const response = await context.search!.session.get(id); + const response = await context.search!.getSession(id); return res.ok({ body: response, @@ -91,18 +91,22 @@ export function registerSessionRoutes(router: DataEnhancedPluginRouter, logger: sortField: schema.maybe(schema.string()), sortOrder: schema.maybe(schema.string()), filter: schema.maybe(schema.string()), + searchFields: schema.maybe(schema.arrayOf(schema.string())), + search: schema.maybe(schema.string()), }), }, }, async (context, request, res) => { - const { page, perPage, sortField, sortOrder, filter } = request.body; + const { page, perPage, sortField, sortOrder, filter, searchFields, search } = request.body; try { - const response = await context.search!.session.find({ + const response = await context.search!.findSessions({ page, perPage, sortField, sortOrder, filter, + searchFields, + search, }); return res.ok({ @@ -127,7 +131,7 @@ export function registerSessionRoutes(router: DataEnhancedPluginRouter, logger: async (context, request, res) => { const { id } = request.params; try { - await context.search!.session.delete(id); + await context.search!.cancelSession(id); return res.ok(); } catch (e) { @@ -155,7 +159,7 @@ export function registerSessionRoutes(router: DataEnhancedPluginRouter, logger: const { id } = request.params; const { name, expires } = request.body; try { - const response = await context.search!.session.update(id, { name, expires }); + const response = await context.search!.updateSession(id, { name, expires }); return res.ok({ body: response, @@ -166,4 +170,33 @@ export function registerSessionRoutes(router: DataEnhancedPluginRouter, logger: } } ); + + router.post( + { + path: '/internal/session/{id}/_extend', + validate: { + params: schema.object({ + id: schema.string(), + }), + body: schema.object({ + expires: schema.string(), + }), + }, + }, + async (context, request, res) => { + const { id } = request.params; + const { expires } = request.body; + try { + const response = await context.search!.extendSession(id, new Date(expires)); + + return res.ok({ + body: response, + }); + } catch (e) { + const err = e.output?.payload || e; + logger.error(err); + return reportServerError(res, err); + } + } + ); } diff --git a/x-pack/plugins/data_enhanced/server/saved_objects/search_session.ts b/x-pack/plugins/data_enhanced/server/saved_objects/search_session.ts index 4e75ffaeec69a..16472199de4d9 100644 --- a/x-pack/plugins/data_enhanced/server/saved_objects/search_session.ts +++ b/x-pack/plugins/data_enhanced/server/saved_objects/search_session.ts @@ -5,8 +5,7 @@ */ import { SavedObjectsType } from 'kibana/server'; - -export const SEARCH_SESSION_TYPE = 'search-session'; +import { SEARCH_SESSION_TYPE } from '../../common'; export const searchSessionMapping: SavedObjectsType = { name: SEARCH_SESSION_TYPE, @@ -14,6 +13,9 @@ export const searchSessionMapping: SavedObjectsType = { hidden: true, mappings: { properties: { + persisted: { + type: 'boolean', + }, sessionId: { type: 'keyword', }, @@ -26,6 +28,9 @@ export const searchSessionMapping: SavedObjectsType = { expires: { type: 'date', }, + touched: { + type: 'date', + }, status: { type: 'keyword', }, diff --git a/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.test.ts b/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.test.ts index f2d7725954a26..1670b1116eedb 100644 --- a/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.test.ts @@ -117,7 +117,6 @@ describe('EQL search strategy', () => { expect(request).toEqual( expect.objectContaining({ wait_for_completion_timeout: '100ms', - keep_alive: '1m', }) ); }); @@ -156,7 +155,6 @@ describe('EQL search strategy', () => { expect(request).toEqual( expect.objectContaining({ wait_for_completion_timeout: '5ms', - keep_alive: '1m', keep_on_completion: false, }) ); diff --git a/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.ts index a0d4e9dcd19b9..65ce5bdf5255c 100644 --- a/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/eql_search_strategy.ts @@ -22,7 +22,8 @@ export const eqlSearchStrategyProvider = ( logger: Logger ): ISearchStrategy => { async function cancelAsyncSearch(id: string, esClient: IScopedClusterClient) { - await esClient.asCurrentUser.asyncSearch.delete({ id }); + const client = esClient.asCurrentUser.eql; + await client.delete({ id }); } return { @@ -41,11 +42,11 @@ export const eqlSearchStrategyProvider = ( uiSettingsClient ); const params = id - ? getDefaultAsyncGetParams() + ? getDefaultAsyncGetParams(options) : { ...(await getIgnoreThrottled(uiSettingsClient)), ...defaultParams, - ...getDefaultAsyncGetParams(), + ...getDefaultAsyncGetParams(options), ...request.params, }; const promise = id diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts index b2ddd0310f8f5..98238f50fa059 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.test.ts @@ -7,6 +7,7 @@ import { enhancedEsSearchStrategyProvider } from './es_search_strategy'; import { BehaviorSubject } from 'rxjs'; import { SearchStrategyDependencies } from '../../../../../src/plugins/data/server/search'; +import moment from 'moment'; import { KbnServerError } from '../../../../../src/plugins/kibana_utils/server'; import { ElasticsearchClientError, ResponseError } from '@elastic/elasticsearch/lib/errors'; import * as indexNotFoundException from '../../../../../src/plugins/data/common/search/test_data/index_not_found_exception.json'; @@ -60,7 +61,7 @@ describe('ES search strategy', () => { }, }, } as unknown) as SearchStrategyDependencies; - const mockConfig$ = new BehaviorSubject({ + const mockLegacyConfig$ = new BehaviorSubject({ elasticsearch: { shardTimeout: { asMilliseconds: () => { @@ -70,6 +71,14 @@ describe('ES search strategy', () => { }, }); + const mockConfig$ = new BehaviorSubject({ + search: { + sessions: { + defaultExpiration: moment.duration('1', 'm'), + }, + }, + }); + beforeEach(() => { mockApiCaller.mockClear(); mockGetCaller.mockClear(); @@ -78,76 +87,140 @@ describe('ES search strategy', () => { }); it('returns a strategy with `search and `cancel`', async () => { - const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); + const esSearch = await enhancedEsSearchStrategyProvider( + mockConfig$, + mockLegacyConfig$, + mockLogger + ); expect(typeof esSearch.search).toBe('function'); }); describe('search', () => { - it('makes a POST request to async search with params when no ID is provided', async () => { - mockSubmitCaller.mockResolvedValueOnce(mockAsyncResponse); + describe('no sessionId', () => { + it('makes a POST request with params when no ID provided', async () => { + mockSubmitCaller.mockResolvedValueOnce(mockAsyncResponse); - const params = { index: 'logstash-*', body: { query: {} } }; - const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); + const params = { index: 'logstash-*', body: { query: {} } }; + const esSearch = await enhancedEsSearchStrategyProvider( + mockConfig$, + mockLegacyConfig$, + mockLogger + ); - await esSearch.search({ params }, {}, mockDeps).toPromise(); + await esSearch.search({ params }, {}, mockDeps).toPromise(); - expect(mockSubmitCaller).toBeCalled(); - const request = mockSubmitCaller.mock.calls[0][0]; - expect(request.index).toEqual(params.index); - expect(request.body).toEqual(params.body); - }); + expect(mockSubmitCaller).toBeCalled(); + const request = mockSubmitCaller.mock.calls[0][0]; + expect(request.index).toEqual(params.index); + expect(request.body).toEqual(params.body); + expect(request).toHaveProperty('keep_alive', '1m'); + }); - it('makes a GET request to async search with ID when ID is provided', async () => { - mockGetCaller.mockResolvedValueOnce(mockAsyncResponse); + it('makes a GET request to async search with ID', async () => { + mockGetCaller.mockResolvedValueOnce(mockAsyncResponse); - const params = { index: 'logstash-*', body: { query: {} } }; - const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); + const params = { index: 'logstash-*', body: { query: {} } }; + const esSearch = await enhancedEsSearchStrategyProvider( + mockConfig$, + mockLegacyConfig$, + mockLogger + ); - await esSearch.search({ id: 'foo', params }, {}, mockDeps).toPromise(); + await esSearch.search({ id: 'foo', params }, {}, mockDeps).toPromise(); - expect(mockGetCaller).toBeCalled(); - const request = mockGetCaller.mock.calls[0][0]; - expect(request.id).toEqual('foo'); - expect(request).toHaveProperty('wait_for_completion_timeout'); - expect(request).toHaveProperty('keep_alive'); - }); + expect(mockGetCaller).toBeCalled(); + const request = mockGetCaller.mock.calls[0][0]; + expect(request.id).toEqual('foo'); + expect(request).toHaveProperty('wait_for_completion_timeout'); + expect(request).toHaveProperty('keep_alive', '1m'); + }); + + it('sets wait_for_completion_timeout and keep_alive in the request', async () => { + mockSubmitCaller.mockResolvedValueOnce(mockAsyncResponse); + + const params = { index: 'foo-*', body: {} }; + const esSearch = await enhancedEsSearchStrategyProvider( + mockConfig$, + mockLegacyConfig$, + mockLogger + ); + + await esSearch.search({ params }, {}, mockDeps).toPromise(); + + expect(mockSubmitCaller).toBeCalled(); + const request = mockSubmitCaller.mock.calls[0][0]; + expect(request).toHaveProperty('wait_for_completion_timeout'); + expect(request).toHaveProperty('keep_alive'); + }); - it('calls the rollup API if the index is a rollup type', async () => { - mockApiCaller.mockResolvedValueOnce(mockRollupResponse); - - const params = { index: 'foo-程', body: {} }; - const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); - - await esSearch - .search( - { - indexType: 'rollup', - params, - }, - {}, - mockDeps - ) - .toPromise(); - - expect(mockApiCaller).toBeCalled(); - const { method, path } = mockApiCaller.mock.calls[0][0]; - expect(method).toBe('POST'); - expect(path).toBe('/foo-%E7%A8%8B/_rollup_search'); + it('calls the rollup API if the index is a rollup type', async () => { + mockApiCaller.mockResolvedValueOnce(mockRollupResponse); + + const params = { index: 'foo-程', body: {} }; + const esSearch = await enhancedEsSearchStrategyProvider( + mockConfig$, + mockLegacyConfig$, + mockLogger + ); + + await esSearch + .search( + { + indexType: 'rollup', + params, + }, + {}, + mockDeps + ) + .toPromise(); + + expect(mockApiCaller).toBeCalled(); + const { method, path } = mockApiCaller.mock.calls[0][0]; + expect(method).toBe('POST'); + expect(path).toBe('/foo-%E7%A8%8B/_rollup_search'); + }); }); - it('sets wait_for_completion_timeout and keep_alive in the request', async () => { - mockSubmitCaller.mockResolvedValueOnce(mockAsyncResponse); + describe('with sessionId', () => { + it('makes a POST request with params (long keepalive)', async () => { + mockSubmitCaller.mockResolvedValueOnce(mockAsyncResponse); - const params = { index: 'foo-*', body: {} }; - const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); + const params = { index: 'logstash-*', body: { query: {} } }; + const esSearch = await enhancedEsSearchStrategyProvider( + mockConfig$, + mockLegacyConfig$, + mockLogger + ); - await esSearch.search({ params }, {}, mockDeps).toPromise(); + await esSearch.search({ params }, { sessionId: '1' }, mockDeps).toPromise(); - expect(mockSubmitCaller).toBeCalled(); - const request = mockSubmitCaller.mock.calls[0][0]; - expect(request).toHaveProperty('wait_for_completion_timeout'); - expect(request).toHaveProperty('keep_alive'); + expect(mockSubmitCaller).toBeCalled(); + const request = mockSubmitCaller.mock.calls[0][0]; + expect(request.index).toEqual(params.index); + expect(request.body).toEqual(params.body); + + expect(request).toHaveProperty('keep_alive', '60000ms'); + }); + + it('makes a GET request to async search without keepalive', async () => { + mockGetCaller.mockResolvedValueOnce(mockAsyncResponse); + + const params = { index: 'logstash-*', body: { query: {} } }; + const esSearch = await enhancedEsSearchStrategyProvider( + mockConfig$, + mockLegacyConfig$, + mockLogger + ); + + await esSearch.search({ id: 'foo', params }, { sessionId: '1' }, mockDeps).toPromise(); + + expect(mockGetCaller).toBeCalled(); + const request = mockGetCaller.mock.calls[0][0]; + expect(request.id).toEqual('foo'); + expect(request).toHaveProperty('wait_for_completion_timeout'); + expect(request).not.toHaveProperty('keep_alive'); + }); }); it('throws normalized error if ResponseError is thrown', async () => { @@ -162,7 +235,11 @@ describe('ES search strategy', () => { mockSubmitCaller.mockRejectedValue(errResponse); const params = { index: 'logstash-*', body: { query: {} } }; - const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); + const esSearch = await enhancedEsSearchStrategyProvider( + mockConfig$, + mockLegacyConfig$, + mockLogger + ); let err: KbnServerError | undefined; try { @@ -183,7 +260,11 @@ describe('ES search strategy', () => { mockSubmitCaller.mockRejectedValue(errResponse); const params = { index: 'logstash-*', body: { query: {} } }; - const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); + const esSearch = await enhancedEsSearchStrategyProvider( + mockConfig$, + mockLegacyConfig$, + mockLogger + ); let err: KbnServerError | undefined; try { @@ -204,7 +285,11 @@ describe('ES search strategy', () => { mockDeleteCaller.mockResolvedValueOnce(200); const id = 'some_id'; - const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); + const esSearch = await enhancedEsSearchStrategyProvider( + mockConfig$, + mockLegacyConfig$, + mockLogger + ); await esSearch.cancel!(id, {}, mockDeps); @@ -224,7 +309,11 @@ describe('ES search strategy', () => { mockDeleteCaller.mockRejectedValue(errResponse); const id = 'some_id'; - const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); + const esSearch = await enhancedEsSearchStrategyProvider( + mockConfig$, + mockLegacyConfig$, + mockLogger + ); let err: KbnServerError | undefined; try { @@ -247,7 +336,11 @@ describe('ES search strategy', () => { const id = 'some_other_id'; const keepAlive = '1d'; - const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); + const esSearch = await enhancedEsSearchStrategyProvider( + mockConfig$, + mockLegacyConfig$, + mockLogger + ); await esSearch.extend!(id, keepAlive, {}, mockDeps); @@ -262,7 +355,11 @@ describe('ES search strategy', () => { const id = 'some_other_id'; const keepAlive = '1d'; - const esSearch = await enhancedEsSearchStrategyProvider(mockConfig$, mockLogger); + const esSearch = await enhancedEsSearchStrategyProvider( + mockConfig$, + mockLegacyConfig$, + mockLogger + ); let err: KbnServerError | undefined; try { diff --git a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts index 694d9807b5a4d..64b1e1a57b489 100644 --- a/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts +++ b/x-pack/plugins/data_enhanced/server/search/es_search_strategy.ts @@ -23,6 +23,7 @@ import { getTotalLoaded, searchUsageObserver, shimAbortSignal, + shimHitsTotal, } from '../../../../../src/plugins/data/server'; import type { IAsyncSearchOptions } from '../../common'; import { pollSearch } from '../../common'; @@ -33,10 +34,12 @@ import { } from './request_utils'; import { toAsyncKibanaSearchResponse } from './response_utils'; import { AsyncSearchResponse } from './types'; +import { ConfigSchema } from '../../config'; import { getKbnServerError, KbnServerError } from '../../../../../src/plugins/kibana_utils/server'; export const enhancedEsSearchStrategyProvider = ( - config$: Observable, + config$: Observable, + legacyConfig$: Observable, logger: Logger, usage?: SearchUsage ): ISearchStrategy => { @@ -56,14 +59,19 @@ export const enhancedEsSearchStrategyProvider = ( const client = esClient.asCurrentUser.asyncSearch; const search = async () => { + const config = await config$.pipe(first()).toPromise(); const params = id - ? getDefaultAsyncGetParams() - : { ...(await getDefaultAsyncSubmitParams(uiSettingsClient, options)), ...request.params }; + ? getDefaultAsyncGetParams(options) + : { + ...(await getDefaultAsyncSubmitParams(uiSettingsClient, config, options)), + ...request.params, + }; const promise = id ? client.get({ ...params, id }) : client.submit(params); const { body } = await shimAbortSignal(promise, options.abortSignal); - return toAsyncKibanaSearchResponse(body); + const response = shimHitsTotal(body.response, options); + return toAsyncKibanaSearchResponse({ ...body, response }); }; const cancel = async () => { @@ -86,12 +94,12 @@ export const enhancedEsSearchStrategyProvider = ( options: ISearchOptions, { esClient, uiSettingsClient }: SearchStrategyDependencies ): Promise { - const config = await config$.pipe(first()).toPromise(); + const legacyConfig = await legacyConfig$.pipe(first()).toPromise(); const { body, index, ...params } = request.params!; const method = 'POST'; const path = encodeURI(`/${index}/_rollup_search`); const querystring = { - ...getShardTimeout(config), + ...getShardTimeout(legacyConfig), ...(await getIgnoreThrottled(uiSettingsClient)), ...(await getDefaultSearchParams(uiSettingsClient)), ...params, @@ -108,7 +116,7 @@ export const enhancedEsSearchStrategyProvider = ( const esResponse = await shimAbortSignal(promise, options?.abortSignal); const response = esResponse.body as SearchResponse; return { - rawResponse: response, + rawResponse: shimHitsTotal(response, options), ...getTotalLoaded(response), }; } catch (e) { diff --git a/x-pack/plugins/data_enhanced/server/search/request_utils.ts b/x-pack/plugins/data_enhanced/server/search/request_utils.ts index f54ab2199c905..d9ef3ab3292c3 100644 --- a/x-pack/plugins/data_enhanced/server/search/request_utils.ts +++ b/x-pack/plugins/data_enhanced/server/search/request_utils.ts @@ -11,6 +11,7 @@ import { } from '@elastic/elasticsearch/api/requestParams'; import { ISearchOptions, UI_SETTINGS } from '../../../../../src/plugins/data/common'; import { getDefaultSearchParams } from '../../../../../src/plugins/data/server'; +import { ConfigSchema } from '../../config'; /** * @internal @@ -27,6 +28,7 @@ export async function getIgnoreThrottled( */ export async function getDefaultAsyncSubmitParams( uiSettingsClient: IUiSettingsClient, + config: ConfigSchema, options: ISearchOptions ): Promise< Pick< @@ -44,21 +46,30 @@ export async function getDefaultAsyncSubmitParams( return { batched_reduce_size: 64, keep_on_completion: !!options.sessionId, // Always return an ID, even if the request completes quickly - ...getDefaultAsyncGetParams(), + ...getDefaultAsyncGetParams(options), ...(await getIgnoreThrottled(uiSettingsClient)), ...(await getDefaultSearchParams(uiSettingsClient)), + ...(options.sessionId + ? { + keep_alive: `${config.search.sessions.defaultExpiration.asMilliseconds()}ms`, + } + : {}), }; } /** @internal */ -export function getDefaultAsyncGetParams(): Pick< - AsyncSearchGet, - 'keep_alive' | 'wait_for_completion_timeout' -> { +export function getDefaultAsyncGetParams( + options: ISearchOptions +): Pick { return { - keep_alive: '1m', // Extend the TTL for this search request by one minute wait_for_completion_timeout: '100ms', // Wait up to 100ms for the response to return + ...(options.sessionId + ? undefined + : { + keep_alive: '1m', + // We still need to do polling for searches not within the context of a search session + }), }; } diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts index 4334ab3bc2903..352edc4639631 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.test.ts @@ -5,187 +5,569 @@ */ import { checkRunningSessions } from './check_running_sessions'; -import { SearchSessionStatus, SearchSessionSavedObjectAttributes } from '../../../common'; +import { + SearchSessionStatus, + SearchSessionSavedObjectAttributes, + ENHANCED_ES_SEARCH_STRATEGY, + EQL_SEARCH_STRATEGY, +} from '../../../common'; import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; import type { SavedObjectsClientContract } from 'kibana/server'; -import { SearchStatus } from './types'; +import { SearchSessionsConfig, SearchStatus } from './types'; +import moment from 'moment'; describe('getSearchStatus', () => { let mockClient: any; let savedObjectsClient: jest.Mocked; + const config: SearchSessionsConfig = { + enabled: true, + pageSize: 5, + notTouchedInProgressTimeout: moment.duration(1, 'm'), + notTouchedTimeout: moment.duration(5, 'm'), + maxUpdateRetries: 3, + defaultExpiration: moment.duration(7, 'd'), + trackingInterval: moment.duration(10, 's'), + management: {} as any, + }; const mockLogger: any = { debug: jest.fn(), warn: jest.fn(), error: jest.fn(), }; + const emptySO = { + persisted: false, + status: SearchSessionStatus.IN_PROGRESS, + created: moment().subtract(moment.duration(3, 'm')), + touched: moment().subtract(moment.duration(10, 's')), + idMapping: {}, + }; + beforeEach(() => { savedObjectsClient = savedObjectsClientMock.create(); mockClient = { asyncSearch: { status: jest.fn(), + delete: jest.fn(), + }, + eql: { + status: jest.fn(), + delete: jest.fn(), }, }; }); test('does nothing if there are no open sessions', async () => { - savedObjectsClient.bulkUpdate = jest.fn(); savedObjectsClient.find.mockResolvedValue({ saved_objects: [], total: 0, } as any); - await checkRunningSessions(savedObjectsClient, mockClient, mockLogger); + await checkRunningSessions( + { + savedObjectsClient, + client: mockClient, + logger: mockLogger, + }, + config + ); expect(savedObjectsClient.bulkUpdate).not.toBeCalled(); + expect(savedObjectsClient.delete).not.toBeCalled(); }); - test('does nothing if there are no searchIds in the saved object', async () => { - savedObjectsClient.bulkUpdate = jest.fn(); - savedObjectsClient.find.mockResolvedValue({ - saved_objects: [ + describe('pagination', () => { + test('fetches one page if not objects exist', async () => { + savedObjectsClient.find.mockResolvedValueOnce({ + saved_objects: [], + total: 0, + } as any); + + await checkRunningSessions( { - attributes: { - idMapping: {}, - }, + savedObjectsClient, + client: mockClient, + logger: mockLogger, }, - ], - total: 1, - } as any); + config + ); - await checkRunningSessions(savedObjectsClient, mockClient, mockLogger); + expect(savedObjectsClient.find).toHaveBeenCalledTimes(1); + }); - expect(savedObjectsClient.bulkUpdate).not.toBeCalled(); + test('fetches one page if less than page size object are returned', async () => { + savedObjectsClient.find.mockResolvedValueOnce({ + saved_objects: [emptySO, emptySO], + total: 5, + } as any); + + await checkRunningSessions( + { + savedObjectsClient, + client: mockClient, + logger: mockLogger, + }, + config + ); + + expect(savedObjectsClient.find).toHaveBeenCalledTimes(1); + }); + + test('fetches two pages if exactly page size objects are returned', async () => { + let i = 0; + savedObjectsClient.find.mockImplementation(() => { + return new Promise((resolve) => { + resolve({ + saved_objects: i++ === 0 ? [emptySO, emptySO, emptySO, emptySO, emptySO] : [], + total: 5, + page: i, + } as any); + }); + }); + + await checkRunningSessions( + { + savedObjectsClient, + client: mockClient, + logger: mockLogger, + }, + config + ); + + expect(savedObjectsClient.find).toHaveBeenCalledTimes(2); + + // validate that page number increases + const { page: page1 } = savedObjectsClient.find.mock.calls[0][0]; + const { page: page2 } = savedObjectsClient.find.mock.calls[1][0]; + expect(page1).toBe(1); + expect(page2).toBe(2); + }); + + test('fetches two pages if page size +1 objects are returned', async () => { + let i = 0; + savedObjectsClient.find.mockImplementation(() => { + return new Promise((resolve) => { + resolve({ + saved_objects: i++ === 0 ? [emptySO, emptySO, emptySO, emptySO, emptySO] : [emptySO], + total: 5, + page: i, + } as any); + }); + }); + + await checkRunningSessions( + { + savedObjectsClient, + client: mockClient, + logger: mockLogger, + }, + config + ); + + expect(savedObjectsClient.find).toHaveBeenCalledTimes(2); + }); }); - test('does nothing if the search is still running', async () => { - savedObjectsClient.bulkUpdate = jest.fn(); - const so = { - attributes: { - idMapping: { - 'search-hash': { - id: 'search-id', - strategy: 'cool', - status: SearchStatus.IN_PROGRESS, + describe('delete', () => { + test('doesnt delete a persisted session', async () => { + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [ + { + id: '123', + attributes: { + persisted: true, + status: SearchSessionStatus.IN_PROGRESS, + created: moment().subtract(moment.duration(30, 'm')), + touched: moment().subtract(moment.duration(10, 'm')), + idMapping: {}, + }, }, + ], + total: 1, + } as any); + await checkRunningSessions( + { + savedObjectsClient, + client: mockClient, + logger: mockLogger, }, - }, - }; - savedObjectsClient.find.mockResolvedValue({ - saved_objects: [so], - total: 1, - } as any); + config + ); - mockClient.asyncSearch.status.mockResolvedValue({ - body: { - is_partial: true, - is_running: true, - }, + expect(savedObjectsClient.bulkUpdate).not.toBeCalled(); + expect(savedObjectsClient.delete).not.toBeCalled(); }); - await checkRunningSessions(savedObjectsClient, mockClient, mockLogger); + test('doesnt delete a non persisted, recently touched session', async () => { + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [ + { + id: '123', + attributes: { + persisted: false, + status: SearchSessionStatus.IN_PROGRESS, + created: moment().subtract(moment.duration(3, 'm')), + touched: moment().subtract(moment.duration(10, 's')), + idMapping: {}, + }, + }, + ], + total: 1, + } as any); + await checkRunningSessions( + { + savedObjectsClient, + client: mockClient, + logger: mockLogger, + }, + config + ); - expect(savedObjectsClient.bulkUpdate).not.toBeCalled(); - }); + expect(savedObjectsClient.bulkUpdate).not.toBeCalled(); + expect(savedObjectsClient.delete).not.toBeCalled(); + }); - test("doesn't re-check completed or errored searches", async () => { - savedObjectsClient.bulkUpdate = jest.fn(); - const so = { - attributes: { - idMapping: { - 'search-hash': { - id: 'search-id', - strategy: 'cool', - status: SearchStatus.COMPLETE, + test('doesnt delete a non persisted, completed session, within on screen time frame', async () => { + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [ + { + id: '123', + attributes: { + persisted: false, + status: SearchSessionStatus.COMPLETE, + created: moment().subtract(moment.duration(3, 'm')), + touched: moment().subtract(moment.duration(1, 'm')), + idMapping: { + 'search-hash': { + id: 'search-id', + strategy: 'cool', + status: SearchStatus.COMPLETE, + }, + }, + }, }, - 'another-search-hash': { - id: 'search-id', - strategy: 'cool', - status: SearchStatus.ERROR, + ], + total: 1, + } as any); + await checkRunningSessions( + { + savedObjectsClient, + client: mockClient, + logger: mockLogger, + }, + config + ); + + expect(savedObjectsClient.bulkUpdate).not.toBeCalled(); + expect(savedObjectsClient.delete).not.toBeCalled(); + }); + + test('deletes a non persisted, abandoned session', async () => { + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [ + { + id: '123', + attributes: { + persisted: false, + status: SearchSessionStatus.IN_PROGRESS, + created: moment().subtract(moment.duration(3, 'm')), + touched: moment().subtract(moment.duration(2, 'm')), + idMapping: { + 'map-key': { + strategy: ENHANCED_ES_SEARCH_STRATEGY, + id: 'async-id', + }, + }, + }, }, + ], + total: 1, + } as any); + + await checkRunningSessions( + { + savedObjectsClient, + client: mockClient, + logger: mockLogger, }, - }, - }; - savedObjectsClient.find.mockResolvedValue({ - saved_objects: [so], - total: 1, - } as any); + config + ); - await checkRunningSessions(savedObjectsClient, mockClient, mockLogger); + expect(savedObjectsClient.bulkUpdate).not.toBeCalled(); + expect(savedObjectsClient.delete).toBeCalled(); - expect(mockClient.asyncSearch.status).not.toBeCalled(); - }); + expect(mockClient.asyncSearch.delete).toBeCalled(); + + const { id } = mockClient.asyncSearch.delete.mock.calls[0][0]; + expect(id).toBe('async-id'); + }); - test('updates to complete if the search is done', async () => { - savedObjectsClient.bulkUpdate = jest.fn(); - const so = { - attributes: { - idMapping: { - 'search-hash': { - id: 'search-id', - strategy: 'cool', - status: SearchStatus.IN_PROGRESS, + test('deletes a completed, not persisted session', async () => { + mockClient.asyncSearch.delete = jest.fn().mockResolvedValue(true); + + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [ + { + id: '123', + attributes: { + persisted: false, + status: SearchSessionStatus.COMPLETE, + created: moment().subtract(moment.duration(30, 'm')), + touched: moment().subtract(moment.duration(6, 'm')), + idMapping: { + 'map-key': { + strategy: ENHANCED_ES_SEARCH_STRATEGY, + id: 'async-id', + status: SearchStatus.COMPLETE, + }, + 'eql-map-key': { + strategy: EQL_SEARCH_STRATEGY, + id: 'eql-async-id', + status: SearchStatus.COMPLETE, + }, + }, + }, }, + ], + total: 1, + } as any); + + await checkRunningSessions( + { + savedObjectsClient, + client: mockClient, + logger: mockLogger, }, - }, - }; - savedObjectsClient.find.mockResolvedValue({ - saved_objects: [so], - total: 1, - } as any); + config + ); - mockClient.asyncSearch.status.mockResolvedValue({ - body: { - is_partial: false, - is_running: false, - completion_status: 200, - }, + expect(savedObjectsClient.bulkUpdate).not.toBeCalled(); + expect(savedObjectsClient.delete).toBeCalled(); + + expect(mockClient.asyncSearch.delete).toBeCalled(); + expect(mockClient.eql.delete).not.toBeCalled(); + + const { id } = mockClient.asyncSearch.delete.mock.calls[0][0]; + expect(id).toBe('async-id'); }); - await checkRunningSessions(savedObjectsClient, mockClient, mockLogger); + test('ignores errors thrown while deleting async searches', async () => { + mockClient.asyncSearch.delete = jest.fn().mockRejectedValueOnce(false); + + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [ + { + id: '123', + attributes: { + persisted: false, + status: SearchSessionStatus.COMPLETE, + created: moment().subtract(moment.duration(30, 'm')), + touched: moment().subtract(moment.duration(6, 'm')), + idMapping: { + 'map-key': { + strategy: ENHANCED_ES_SEARCH_STRATEGY, + id: 'async-id', + status: SearchStatus.COMPLETE, + }, + }, + }, + }, + ], + total: 1, + } as any); + + await checkRunningSessions( + { + savedObjectsClient, + client: mockClient, + logger: mockLogger, + }, + config + ); + + expect(savedObjectsClient.bulkUpdate).not.toBeCalled(); + expect(savedObjectsClient.delete).toBeCalled(); - expect(mockClient.asyncSearch.status).toBeCalledWith({ id: 'search-id' }); - const [updateInput] = savedObjectsClient.bulkUpdate.mock.calls[0]; - const updatedAttributes = updateInput[0].attributes as SearchSessionSavedObjectAttributes; - expect(updatedAttributes.status).toBe(SearchSessionStatus.COMPLETE); - expect(updatedAttributes.idMapping['search-hash'].status).toBe(SearchStatus.COMPLETE); - expect(updatedAttributes.idMapping['search-hash'].error).toBeUndefined(); + expect(mockClient.asyncSearch.delete).toBeCalled(); + + const { id } = mockClient.asyncSearch.delete.mock.calls[0][0]; + expect(id).toBe('async-id'); + }); }); - test('updates to error if the search is errored', async () => { - savedObjectsClient.bulkUpdate = jest.fn(); - const so = { - attributes: { - idMapping: { - 'search-hash': { - id: 'search-id', - strategy: 'cool', - status: SearchStatus.IN_PROGRESS, + describe('update', () => { + test('does nothing if the search is still running', async () => { + const so = { + id: '123', + attributes: { + persisted: false, + status: SearchSessionStatus.IN_PROGRESS, + created: moment().subtract(moment.duration(3, 'm')), + touched: moment().subtract(moment.duration(10, 's')), + idMapping: { + 'search-hash': { + id: 'search-id', + strategy: 'cool', + status: SearchStatus.IN_PROGRESS, + }, }, }, - }, - }; - savedObjectsClient.find.mockResolvedValue({ - saved_objects: [so], - total: 1, - } as any); + }; + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [so], + total: 1, + } as any); - mockClient.asyncSearch.status.mockResolvedValue({ - body: { - is_partial: false, - is_running: false, - completion_status: 500, - }, + mockClient.asyncSearch.status.mockResolvedValue({ + body: { + is_partial: true, + is_running: true, + }, + }); + + await checkRunningSessions( + { + savedObjectsClient, + client: mockClient, + logger: mockLogger, + }, + config + ); + + expect(savedObjectsClient.bulkUpdate).not.toBeCalled(); + expect(savedObjectsClient.delete).not.toBeCalled(); }); - await checkRunningSessions(savedObjectsClient, mockClient, mockLogger); - const [updateInput] = savedObjectsClient.bulkUpdate.mock.calls[0]; + test("doesn't re-check completed or errored searches", async () => { + savedObjectsClient.bulkUpdate = jest.fn(); + savedObjectsClient.delete = jest.fn(); + const so = { + id: '123', + attributes: { + status: SearchSessionStatus.ERROR, + idMapping: { + 'search-hash': { + id: 'search-id', + strategy: 'cool', + status: SearchStatus.COMPLETE, + }, + 'another-search-hash': { + id: 'search-id', + strategy: 'cool', + status: SearchStatus.ERROR, + }, + }, + }, + }; + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [so], + total: 1, + } as any); - const updatedAttributes = updateInput[0].attributes as SearchSessionSavedObjectAttributes; - expect(updatedAttributes.status).toBe(SearchSessionStatus.ERROR); - expect(updatedAttributes.idMapping['search-hash'].status).toBe(SearchStatus.ERROR); - expect(updatedAttributes.idMapping['search-hash'].error).toBe( - 'Search completed with a 500 status' - ); + await checkRunningSessions( + { + savedObjectsClient, + client: mockClient, + logger: mockLogger, + }, + config + ); + + expect(mockClient.asyncSearch.status).not.toBeCalled(); + expect(savedObjectsClient.bulkUpdate).not.toBeCalled(); + expect(savedObjectsClient.delete).not.toBeCalled(); + }); + + test('updates to complete if the search is done', async () => { + savedObjectsClient.bulkUpdate = jest.fn(); + const so = { + attributes: { + status: SearchSessionStatus.IN_PROGRESS, + touched: '123', + idMapping: { + 'search-hash': { + id: 'search-id', + strategy: 'cool', + status: SearchStatus.IN_PROGRESS, + }, + }, + }, + }; + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [so], + total: 1, + } as any); + + mockClient.asyncSearch.status.mockResolvedValue({ + body: { + is_partial: false, + is_running: false, + completion_status: 200, + }, + }); + + await checkRunningSessions( + { + savedObjectsClient, + client: mockClient, + logger: mockLogger, + }, + config + ); + + expect(mockClient.asyncSearch.status).toBeCalledWith({ id: 'search-id' }); + const [updateInput] = savedObjectsClient.bulkUpdate.mock.calls[0]; + const updatedAttributes = updateInput[0].attributes as SearchSessionSavedObjectAttributes; + expect(updatedAttributes.status).toBe(SearchSessionStatus.COMPLETE); + expect(updatedAttributes.touched).not.toBe('123'); + expect(updatedAttributes.idMapping['search-hash'].status).toBe(SearchStatus.COMPLETE); + expect(updatedAttributes.idMapping['search-hash'].error).toBeUndefined(); + + expect(savedObjectsClient.delete).not.toBeCalled(); + }); + + test('updates to error if the search is errored', async () => { + savedObjectsClient.bulkUpdate = jest.fn(); + const so = { + attributes: { + idMapping: { + 'search-hash': { + id: 'search-id', + strategy: 'cool', + status: SearchStatus.IN_PROGRESS, + }, + }, + }, + }; + savedObjectsClient.find.mockResolvedValue({ + saved_objects: [so], + total: 1, + } as any); + + mockClient.asyncSearch.status.mockResolvedValue({ + body: { + is_partial: false, + is_running: false, + completion_status: 500, + }, + }); + + await checkRunningSessions( + { + savedObjectsClient, + client: mockClient, + logger: mockLogger, + }, + config + ); + const [updateInput] = savedObjectsClient.bulkUpdate.mock.calls[0]; + + const updatedAttributes = updateInput[0].attributes as SearchSessionSavedObjectAttributes; + expect(updatedAttributes.status).toBe(SearchSessionStatus.ERROR); + expect(updatedAttributes.idMapping['search-hash'].status).toBe(SearchStatus.ERROR); + expect(updatedAttributes.idMapping['search-hash'].error).toBe( + 'Search completed with a 500 status' + ); + }); }); }); diff --git a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts index 71274e15e284d..7258b0ac124e8 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/check_running_sessions.ts @@ -10,93 +10,198 @@ import { SavedObjectsFindResult, SavedObjectsClientContract, } from 'kibana/server'; +import moment from 'moment'; +import { EMPTY, from } from 'rxjs'; +import { expand, mergeMap } from 'rxjs/operators'; +import { nodeBuilder } from '../../../../../../src/plugins/data/common'; import { SearchSessionStatus, SearchSessionSavedObjectAttributes, SearchSessionRequestInfo, + SEARCH_SESSION_TYPE, + ENHANCED_ES_SEARCH_STRATEGY, } from '../../../common'; -import { SEARCH_SESSION_TYPE } from '../../saved_objects'; import { getSearchStatus } from './get_search_status'; import { getSessionStatus } from './get_session_status'; -import { SearchStatus } from './types'; +import { SearchSessionsConfig, SearchStatus } from './types'; -export async function checkRunningSessions( - savedObjectsClient: SavedObjectsClientContract, +export interface CheckRunningSessionsDeps { + savedObjectsClient: SavedObjectsClientContract; + client: ElasticsearchClient; + logger: Logger; +} + +function isSessionStale( + session: SavedObjectsFindResult, + config: SearchSessionsConfig, + logger: Logger +) { + const curTime = moment(); + // Delete if a running session wasn't polled for in the last notTouchedInProgressTimeout OR + // if a completed \ errored \ canceled session wasn't saved for within notTouchedTimeout + return ( + (session.attributes.status === SearchSessionStatus.IN_PROGRESS && + curTime.diff(moment(session.attributes.touched), 'ms') > + config.notTouchedInProgressTimeout.asMilliseconds()) || + (session.attributes.status !== SearchSessionStatus.IN_PROGRESS && + curTime.diff(moment(session.attributes.touched), 'ms') > + config.notTouchedTimeout.asMilliseconds()) + ); +} + +async function updateSessionStatus( + session: SavedObjectsFindResult, client: ElasticsearchClient, logger: Logger +) { + let sessionUpdated = false; + + // Check statuses of all running searches + await Promise.all( + Object.keys(session.attributes.idMapping).map(async (searchKey: string) => { + const updateSearchRequest = ( + currentStatus: Pick + ) => { + sessionUpdated = true; + session.attributes.idMapping[searchKey] = { + ...session.attributes.idMapping[searchKey], + ...currentStatus, + }; + }; + + const searchInfo = session.attributes.idMapping[searchKey]; + if (searchInfo.status === SearchStatus.IN_PROGRESS) { + try { + const currentStatus = await getSearchStatus(client, searchInfo.id); + + if (currentStatus.status !== searchInfo.status) { + logger.debug(`search ${searchInfo.id} | status changed to ${currentStatus.status}`); + updateSearchRequest(currentStatus); + } + } catch (e) { + logger.error(e); + updateSearchRequest({ + status: SearchStatus.ERROR, + error: e.message || e.meta.error?.caused_by?.reason, + }); + } + } + }) + ); + + // And only then derive the session's status + const sessionStatus = getSessionStatus(session.attributes); + if (sessionStatus !== session.attributes.status) { + session.attributes.status = sessionStatus; + session.attributes.touched = new Date().toISOString(); + sessionUpdated = true; + } + + return sessionUpdated; +} + +function getSavedSearchSessionsPage$( + { savedObjectsClient, logger }: CheckRunningSessionsDeps, + config: SearchSessionsConfig, + page: number +) { + logger.debug(`Fetching saved search sessions page ${page}`); + return from( + savedObjectsClient.find({ + page, + perPage: config.pageSize, + type: SEARCH_SESSION_TYPE, + namespaces: ['*'], + filter: nodeBuilder.or([ + nodeBuilder.and([ + nodeBuilder.is( + `${SEARCH_SESSION_TYPE}.attributes.status`, + SearchSessionStatus.IN_PROGRESS.toString() + ), + nodeBuilder.is(`${SEARCH_SESSION_TYPE}.attributes.persisted`, 'true'), + ]), + nodeBuilder.is(`${SEARCH_SESSION_TYPE}.attributes.persisted`, 'false'), + ]), + }) + ); +} + +function getAllSavedSearchSessions$(deps: CheckRunningSessionsDeps, config: SearchSessionsConfig) { + return getSavedSearchSessionsPage$(deps, config, 1).pipe( + expand((result) => { + if (!result || !result.saved_objects || result.saved_objects.length < config.pageSize) + return EMPTY; + else { + return getSavedSearchSessionsPage$(deps, config, result.page + 1); + } + }) + ); +} + +export async function checkRunningSessions( + deps: CheckRunningSessionsDeps, + config: SearchSessionsConfig ): Promise { + const { logger, client, savedObjectsClient } = deps; try { - const runningSearchSessionsResponse = await savedObjectsClient.find( - { - type: SEARCH_SESSION_TYPE, - search: SearchSessionStatus.IN_PROGRESS.toString(), - searchFields: ['status'], - namespaces: ['*'], - } - ); - - if (!runningSearchSessionsResponse.total) return; - - logger.debug(`Found ${runningSearchSessionsResponse.total} running sessions`); - - const updatedSessions = new Array>(); - - let sessionUpdated = false; - - await Promise.all( - runningSearchSessionsResponse.saved_objects.map(async (session) => { - // Check statuses of all running searches - await Promise.all( - Object.keys(session.attributes.idMapping).map(async (searchKey: string) => { - const updateSearchRequest = ( - currentStatus: Pick - ) => { - sessionUpdated = true; - session.attributes.idMapping[searchKey] = { - ...session.attributes.idMapping[searchKey], - ...currentStatus, - }; - }; - - const searchInfo = session.attributes.idMapping[searchKey]; - if (searchInfo.status === SearchStatus.IN_PROGRESS) { - try { - const currentStatus = await getSearchStatus(client, searchInfo.id); - - if (currentStatus.status !== SearchStatus.IN_PROGRESS) { - updateSearchRequest(currentStatus); + await getAllSavedSearchSessions$(deps, config) + .pipe( + mergeMap(async (runningSearchSessionsResponse) => { + if (!runningSearchSessionsResponse.total) return; + + logger.debug(`Found ${runningSearchSessionsResponse.total} running sessions`); + + const updatedSessions = new Array< + SavedObjectsFindResult + >(); + + await Promise.all( + runningSearchSessionsResponse.saved_objects.map(async (session) => { + const updated = await updateSessionStatus(session, client, logger); + let deleted = false; + + if (!session.attributes.persisted) { + if (isSessionStale(session, config, logger)) { + deleted = true; + // delete saved object to free up memory + // TODO: there's a potential rare edge case of deleting an object and then receiving a new trackId for that same session! + // Maybe we want to change state to deleted and cleanup later? + logger.debug(`Deleting stale session | ${session.id}`); + await savedObjectsClient.delete(SEARCH_SESSION_TYPE, session.id); + + // Send a delete request for each async search to ES + Object.keys(session.attributes.idMapping).map(async (searchKey: string) => { + const searchInfo = session.attributes.idMapping[searchKey]; + if (searchInfo.strategy === ENHANCED_ES_SEARCH_STRATEGY) { + try { + await client.asyncSearch.delete({ id: searchInfo.id }); + } catch (e) { + logger.debug( + `Error ignored while deleting async_search ${searchInfo.id}: ${e.message}` + ); + } + } + }); } - } catch (e) { - logger.error(e); - updateSearchRequest({ - status: SearchStatus.ERROR, - error: e.message || e.meta.error?.caused_by?.reason, - }); } - } - }) - ); - - // And only then derive the session's status - const sessionStatus = getSessionStatus(session.attributes); - if (sessionStatus !== SearchSessionStatus.IN_PROGRESS) { - session.attributes.status = sessionStatus; - sessionUpdated = true; - } - if (sessionUpdated) { - updatedSessions.push(session); - } - }) - ); - - if (updatedSessions.length) { - // If there's an error, we'll try again in the next iteration, so there's no need to check the output. - const updatedResponse = await savedObjectsClient.bulkUpdate( - updatedSessions - ); - logger.debug(`Updated ${updatedResponse.saved_objects.length} background sessions`); - } + if (updated && !deleted) { + updatedSessions.push(session); + } + }) + ); + + // Do a bulk update + if (updatedSessions.length) { + // If there's an error, we'll try again in the next iteration, so there's no need to check the output. + const updatedResponse = await savedObjectsClient.bulkUpdate( + updatedSessions + ); + logger.debug(`Updated ${updatedResponse.saved_objects.length} search sessions`); + } + }) + ) + .toPromise(); } catch (err) { logger.error(err); } diff --git a/x-pack/plugins/data_enhanced/server/search/session/get_search_status.test.ts b/x-pack/plugins/data_enhanced/server/search/session/get_search_status.test.ts index e66ce613b71d9..c4eef0b3ddbb3 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/get_search_status.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/get_search_status.test.ts @@ -17,7 +17,7 @@ describe('getSearchStatus', () => { }; }); - test('returns an error status if search is partial and not running', () => { + test('returns an error status if search is partial and not running', async () => { mockClient.asyncSearch.status.mockResolvedValue({ body: { is_partial: true, @@ -25,10 +25,11 @@ describe('getSearchStatus', () => { completion_status: 200, }, }); - expect(getSearchStatus(mockClient, '123')).resolves.toBe(SearchStatus.ERROR); + const res = await getSearchStatus(mockClient, '123'); + expect(res.status).toBe(SearchStatus.ERROR); }); - test('returns an error status if completion_status is an error', () => { + test('returns an error status if completion_status is an error', async () => { mockClient.asyncSearch.status.mockResolvedValue({ body: { is_partial: false, @@ -36,10 +37,11 @@ describe('getSearchStatus', () => { completion_status: 500, }, }); - expect(getSearchStatus(mockClient, '123')).resolves.toBe(SearchStatus.ERROR); + const res = await getSearchStatus(mockClient, '123'); + expect(res.status).toBe(SearchStatus.ERROR); }); - test('returns an error status if gets an ES error', () => { + test('returns an error status if gets an ES error', async () => { mockClient.asyncSearch.status.mockResolvedValue({ error: { root_cause: { @@ -47,15 +49,17 @@ describe('getSearchStatus', () => { }, }, }); - expect(getSearchStatus(mockClient, '123')).resolves.toBe(SearchStatus.ERROR); + const res = await getSearchStatus(mockClient, '123'); + expect(res.status).toBe(SearchStatus.ERROR); }); - test('returns an error status throws', () => { + test('returns an error status throws', async () => { mockClient.asyncSearch.status.mockRejectedValue(new Error('O_o')); - expect(getSearchStatus(mockClient, '123')).resolves.toBe(SearchStatus.ERROR); + const res = await getSearchStatus(mockClient, '123'); + expect(res.status).toBe(SearchStatus.ERROR); }); - test('returns a complete status', () => { + test('returns a complete status', async () => { mockClient.asyncSearch.status.mockResolvedValue({ body: { is_partial: false, @@ -63,10 +67,11 @@ describe('getSearchStatus', () => { completion_status: 200, }, }); - expect(getSearchStatus(mockClient, '123')).resolves.toBe(SearchStatus.COMPLETE); + const res = await getSearchStatus(mockClient, '123'); + expect(res.status).toBe(SearchStatus.COMPLETE); }); - test('returns a running status otherwise', () => { + test('returns a running status otherwise', async () => { mockClient.asyncSearch.status.mockResolvedValue({ body: { is_partial: false, @@ -74,6 +79,7 @@ describe('getSearchStatus', () => { completion_status: undefined, }, }); - expect(getSearchStatus(mockClient, '123')).resolves.toBe(SearchStatus.IN_PROGRESS); + const res = await getSearchStatus(mockClient, '123'); + expect(res.status).toBe(SearchStatus.IN_PROGRESS); }); }); diff --git a/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts b/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts index e2b5fc0157b37..3e93ae4e056c7 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/get_search_status.ts @@ -16,27 +16,40 @@ export async function getSearchStatus( asyncId: string ): Promise> { // TODO: Handle strategies other than the default one - const apiResponse: ApiResponse = await client.asyncSearch.status({ - id: asyncId, - }); - const response = apiResponse.body; - if ((response.is_partial && !response.is_running) || response.completion_status >= 400) { + try { + const apiResponse: ApiResponse = await client.asyncSearch.status({ + id: asyncId, + }); + const response = apiResponse.body; + if ((response.is_partial && !response.is_running) || response.completion_status >= 400) { + return { + status: SearchStatus.ERROR, + error: i18n.translate('xpack.data.search.statusError', { + defaultMessage: `Search completed with a {errorCode} status`, + values: { errorCode: response.completion_status }, + }), + }; + } else if (!response.is_partial && !response.is_running) { + return { + status: SearchStatus.COMPLETE, + error: undefined, + }; + } else { + return { + status: SearchStatus.IN_PROGRESS, + error: undefined, + }; + } + } catch (e) { return { status: SearchStatus.ERROR, - error: i18n.translate('xpack.data.search.statusError', { - defaultMessage: `Search completed with a {errorCode} status`, - values: { errorCode: response.completion_status }, + error: i18n.translate('xpack.data.search.statusThrow', { + defaultMessage: `Search status threw an error {message} ({errorCode}) status`, + values: { + message: e.message, + errorCode: e.statusCode || 500, + }, }), }; - } else if (!response.is_partial && !response.is_running) { - return { - status: SearchStatus.COMPLETE, - error: undefined, - }; - } else { - return { - status: SearchStatus.IN_PROGRESS, - error: undefined, - }; } } diff --git a/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts b/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts index 332e69b119bb6..d9f3cdb8debe7 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/monitoring_task.ts @@ -14,10 +14,10 @@ import { } from '../../../../task_manager/server'; import { checkRunningSessions } from './check_running_sessions'; import { CoreSetup, SavedObjectsClient, Logger } from '../../../../../../src/core/server'; -import { SEARCH_SESSION_TYPE } from '../../saved_objects'; import { ConfigSchema } from '../../../config'; +import { SEARCH_SESSION_TYPE } from '../../../common'; -export const SEARCH_SESSIONS_TASK_TYPE = 'bg_monitor'; +export const SEARCH_SESSIONS_TASK_TYPE = 'search_sessions_monitor'; export const SEARCH_SESSIONS_TASK_ID = `data_enhanced_${SEARCH_SESSIONS_TASK_TYPE}`; interface SearchSessionTaskDeps { @@ -31,17 +31,20 @@ function searchSessionRunner(core: CoreSetup, { logger, config$ }: SearchSession return { async run() { const config = await config$.pipe(first()).toPromise(); + const sessionConfig = config.search.sessions; const [coreStart] = await core.getStartServices(); const internalRepo = coreStart.savedObjects.createInternalRepository([SEARCH_SESSION_TYPE]); const internalSavedObjectsClient = new SavedObjectsClient(internalRepo); await checkRunningSessions( - internalSavedObjectsClient, - coreStart.elasticsearch.client.asInternalUser, - logger + { + savedObjectsClient: internalSavedObjectsClient, + client: coreStart.elasticsearch.client.asInternalUser, + logger, + }, + sessionConfig ); return { - runAt: new Date(Date.now() + config.search.sessions.trackingInterval.asMilliseconds()), state: {}, }; }, diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts index 1107ed8155080..3c8e0e1dc7dce 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.test.ts @@ -4,22 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ -import { BehaviorSubject, of } from 'rxjs'; -import type { SavedObject, SavedObjectsClientContract } from 'kibana/server'; -import type { SearchStrategyDependencies } from '../../../../../../src/plugins/data/server'; +import { BehaviorSubject } from 'rxjs'; +import { + SavedObject, + SavedObjectsClientContract, + SavedObjectsErrorHelpers, +} from '../../../../../../src/core/server'; import { savedObjectsClientMock } from '../../../../../../src/core/server/mocks'; -import { SearchSessionStatus } from '../../../common'; -import { SEARCH_SESSION_TYPE } from '../../saved_objects'; -import { SearchSessionDependencies, SearchSessionService, SessionInfo } from './session_service'; +import { SearchSessionStatus, SEARCH_SESSION_TYPE } from '../../../common'; +import { SearchSessionService } from './session_service'; import { createRequestHash } from './utils'; import moment from 'moment'; import { coreMock } from 'src/core/server/mocks'; import { ConfigSchema } from '../../../config'; // @ts-ignore import { taskManagerMock } from '../../../../task_manager/server/mocks'; -import { SearchStatus } from './types'; -const INMEM_TRACKING_INTERVAL = 10000; const MAX_UPDATE_RETRIES = 3; const flushPromises = () => new Promise((resolve) => setImmediate(resolve)); @@ -28,67 +28,7 @@ describe('SearchSessionService', () => { let savedObjectsClient: jest.Mocked; let service: SearchSessionService; - const MOCK_SESSION_ID = 'session-id-mock'; - const MOCK_ASYNC_ID = '123456'; const MOCK_STRATEGY = 'ese'; - const MOCK_KEY_HASH = '608de49a4600dbb5b173492759792e4a'; - - const createMockInternalSavedObjectClient = ( - findSpy?: jest.SpyInstance, - bulkUpdateSpy?: jest.SpyInstance - ) => { - Object.defineProperty(service, 'internalSavedObjectsClient', { - get: () => { - const find = - findSpy || - (() => { - return { - saved_objects: [ - { - attributes: { - sessionId: MOCK_SESSION_ID, - idMapping: { - 'another-key': { - id: 'another-async-id', - strategy: 'another-strategy', - }, - }, - }, - id: MOCK_SESSION_ID, - version: '1', - }, - ], - }; - }); - - const bulkUpdate = - bulkUpdateSpy || - (() => { - return { - saved_objects: [], - }; - }); - return { - find, - bulkUpdate, - }; - }, - }); - }; - - const createMockIdMapping = ( - mapValues: any[], - insertTime?: moment.Moment, - retryCount?: number - ): Map => { - const fakeMap = new Map(); - fakeMap.set(MOCK_SESSION_ID, { - ids: new Map(mapValues), - insertTime: insertTime || moment(), - retryCount: retryCount || 0, - }); - return fakeMap; - }; const sessionId = 'd7170a35-7e2c-48d6-8dec-9a056721b489'; const mockSavedObject: SavedObject = { @@ -110,8 +50,9 @@ describe('SearchSessionService', () => { sessions: { enabled: true, pageSize: 10000, - inMemTimeout: moment.duration(1, 'm'), - maxUpdateRetries: 3, + notTouchedInProgressTimeout: moment.duration(1, 'm'), + notTouchedTimeout: moment.duration(2, 'm'), + maxUpdateRetries: MAX_UPDATE_RETRIES, defaultExpiration: moment.duration(7, 'd'), trackingInterval: moment.duration(10, 's'), management: {} as any, @@ -126,7 +67,6 @@ describe('SearchSessionService', () => { service = new SearchSessionService(mockLogger, config$); const coreStart = coreMock.createStart(); const mockTaskManager = taskManagerMock.createStart(); - jest.useFakeTimers(); await flushPromises(); await service.start(coreStart, { taskManager: mockTaskManager, @@ -135,25 +75,12 @@ describe('SearchSessionService', () => { afterEach(() => { service.stop(); - jest.useRealTimers(); - }); - - it('search throws if `name` is not provided', () => { - expect(() => service.save(sessionId, {}, { savedObjectsClient })).rejects.toMatchInlineSnapshot( - `[Error: Name is required]` - ); - }); - - it('save throws if `name` is not provided', () => { - expect(() => service.save(sessionId, {}, { savedObjectsClient })).rejects.toMatchInlineSnapshot( - `[Error: Name is required]` - ); }); it('get calls saved objects client', async () => { savedObjectsClient.get.mockResolvedValue(mockSavedObject); - const response = await service.get(sessionId, { savedObjectsClient }); + const response = await service.get({ savedObjectsClient }, sessionId); expect(response).toBe(mockSavedObject); expect(savedObjectsClient.get).toHaveBeenCalledWith(SEARCH_SESSION_TYPE, sessionId); @@ -173,7 +100,7 @@ describe('SearchSessionService', () => { savedObjectsClient.find.mockResolvedValue(mockResponse); const options = { page: 0, perPage: 5 }; - const response = await service.find(options, { savedObjectsClient }); + const response = await service.find({ savedObjectsClient }, options); expect(response).toBe(mockResponse); expect(savedObjectsClient.find).toHaveBeenCalledWith({ @@ -182,7 +109,7 @@ describe('SearchSessionService', () => { }); }); - it('update calls saved objects client', async () => { + it('update calls saved objects client with added touch time', async () => { const mockUpdateSavedObject = { ...mockSavedObject, attributes: {}, @@ -190,185 +117,205 @@ describe('SearchSessionService', () => { savedObjectsClient.update.mockResolvedValue(mockUpdateSavedObject); const attributes = { name: 'new_name' }; - const response = await service.update(sessionId, attributes, { savedObjectsClient }); + const response = await service.update({ savedObjectsClient }, sessionId, attributes); expect(response).toBe(mockUpdateSavedObject); - expect(savedObjectsClient.update).toHaveBeenCalledWith( - SEARCH_SESSION_TYPE, - sessionId, - attributes - ); - }); - - it('delete calls saved objects client', async () => { - savedObjectsClient.delete.mockResolvedValue({}); - const response = await service.delete(sessionId, { savedObjectsClient }); + const [type, id, callAttributes] = savedObjectsClient.update.mock.calls[0]; - expect(response).toEqual({}); - expect(savedObjectsClient.delete).toHaveBeenCalledWith(SEARCH_SESSION_TYPE, sessionId); + expect(type).toBe(SEARCH_SESSION_TYPE); + expect(id).toBe(sessionId); + expect(callAttributes).toHaveProperty('name', attributes.name); + expect(callAttributes).toHaveProperty('touched'); }); - describe('search', () => { - const mockSearch = jest.fn().mockReturnValue(of({})); - const mockStrategy = { search: mockSearch }; - const mockSearchDeps = {} as SearchStrategyDependencies; - const mockDeps = {} as SearchSessionDependencies; + it('cancel updates object status', async () => { + await service.cancel({ savedObjectsClient }, sessionId); + const [type, id, callAttributes] = savedObjectsClient.update.mock.calls[0]; - beforeEach(() => { - mockSearch.mockClear(); - }); + expect(type).toBe(SEARCH_SESSION_TYPE); + expect(id).toBe(sessionId); + expect(callAttributes).toHaveProperty('status', SearchSessionStatus.CANCELLED); + expect(callAttributes).toHaveProperty('touched'); + }); - it('searches using the original request if not restoring', async () => { + describe('trackId', () => { + it('updates the saved object if search session already exists', async () => { const searchRequest = { params: {} }; - const options = { sessionId, isStored: false, isRestore: false }; - - await service - .search(mockStrategy, searchRequest, options, mockSearchDeps, mockDeps) - .toPromise(); + const requestHash = createRequestHash(searchRequest.params); + const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; - expect(mockSearch).toBeCalledWith(searchRequest, options, mockSearchDeps); - }); + const mockUpdateSavedObject = { + ...mockSavedObject, + attributes: {}, + }; + savedObjectsClient.update.mockResolvedValue(mockUpdateSavedObject); - it('searches using the original request if `id` is provided', async () => { - const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; - const searchRequest = { id: searchId, params: {} }; - const options = { sessionId, isStored: true, isRestore: true }; + await service.trackId({ savedObjectsClient }, searchRequest, searchId, { + sessionId, + strategy: MOCK_STRATEGY, + }); - await service - .search(mockStrategy, searchRequest, options, mockSearchDeps, mockDeps) - .toPromise(); + expect(savedObjectsClient.update).toHaveBeenCalled(); + expect(savedObjectsClient.create).not.toHaveBeenCalled(); - expect(mockSearch).toBeCalledWith(searchRequest, options, mockSearchDeps); + const [type, id, callAttributes] = savedObjectsClient.update.mock.calls[0]; + expect(type).toBe(SEARCH_SESSION_TYPE); + expect(id).toBe(sessionId); + expect(callAttributes).toHaveProperty('idMapping', { + [requestHash]: { + id: searchId, + status: SearchSessionStatus.IN_PROGRESS, + strategy: MOCK_STRATEGY, + }, + }); + expect(callAttributes).toHaveProperty('touched'); }); - it('searches by looking up an `id` if restoring and `id` is not provided', async () => { + it('retries updating the saved object if there was a ES conflict 409', async () => { const searchRequest = { params: {} }; - const options = { sessionId, isStored: true, isRestore: true }; - const spyGetId = jest.spyOn(service, 'getId').mockResolvedValueOnce('my_id'); + const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; + + const mockUpdateSavedObject = { + ...mockSavedObject, + attributes: {}, + }; - await service - .search(mockStrategy, searchRequest, options, mockSearchDeps, mockDeps) - .toPromise(); + let counter = 0; + + savedObjectsClient.update.mockImplementation(() => { + return new Promise((resolve, reject) => { + if (counter === 0) { + counter++; + reject(SavedObjectsErrorHelpers.createConflictError(SEARCH_SESSION_TYPE, searchId)); + } else { + resolve(mockUpdateSavedObject); + } + }); + }); - expect(mockSearch).toBeCalledWith({ ...searchRequest, id: 'my_id' }, options, mockSearchDeps); + await service.trackId({ savedObjectsClient }, searchRequest, searchId, { + sessionId, + strategy: MOCK_STRATEGY, + }); - spyGetId.mockRestore(); + expect(savedObjectsClient.update).toHaveBeenCalledTimes(2); + expect(savedObjectsClient.create).not.toHaveBeenCalled(); }); - it('calls `trackId` once if the response contains an `id` and not restoring', async () => { + it('retries updating the saved object if theres a ES conflict 409, but stops after MAX_RETRIES times', async () => { const searchRequest = { params: {} }; - const options = { sessionId, isStored: false, isRestore: false }; - const spyTrackId = jest.spyOn(service, 'trackId').mockResolvedValue(); - mockSearch.mockReturnValueOnce(of({ id: 'my_id' }, { id: 'my_id' })); + const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; - await service - .search(mockStrategy, searchRequest, options, mockSearchDeps, mockDeps) - .toPromise(); + savedObjectsClient.update.mockImplementation(() => { + return new Promise((resolve, reject) => { + reject(SavedObjectsErrorHelpers.createConflictError(SEARCH_SESSION_TYPE, searchId)); + }); + }); - expect(spyTrackId).toBeCalledTimes(1); - expect(spyTrackId).toBeCalledWith(searchRequest, 'my_id', options, {}); + await service.trackId({ savedObjectsClient }, searchRequest, searchId, { + sessionId, + strategy: MOCK_STRATEGY, + }); - spyTrackId.mockRestore(); + // Track ID doesn't throw errors even in cases of failure! + expect(savedObjectsClient.update).toHaveBeenCalledTimes(MAX_UPDATE_RETRIES); + expect(savedObjectsClient.create).not.toHaveBeenCalled(); }); - it('does not call `trackId` if restoring', async () => { + it('creates the saved object in non persisted state, if search session doesnt exists', async () => { const searchRequest = { params: {} }; - const options = { sessionId, isStored: true, isRestore: true }; - const spyGetId = jest.spyOn(service, 'getId').mockResolvedValueOnce('my_id'); - const spyTrackId = jest.spyOn(service, 'trackId').mockResolvedValue(); - mockSearch.mockReturnValueOnce(of({ id: 'my_id' })); + const requestHash = createRequestHash(searchRequest.params); + const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; + + const mockCreatedSavedObject = { + ...mockSavedObject, + attributes: {}, + }; - await service - .search(mockStrategy, searchRequest, options, mockSearchDeps, mockDeps) - .toPromise(); + savedObjectsClient.update.mockRejectedValue( + SavedObjectsErrorHelpers.createGenericNotFoundError(sessionId) + ); + savedObjectsClient.create.mockResolvedValue(mockCreatedSavedObject); + + await service.trackId({ savedObjectsClient }, searchRequest, searchId, { + sessionId, + strategy: MOCK_STRATEGY, + }); - expect(spyTrackId).not.toBeCalled(); + expect(savedObjectsClient.update).toHaveBeenCalled(); + expect(savedObjectsClient.create).toHaveBeenCalled(); - spyGetId.mockRestore(); - spyTrackId.mockRestore(); + const [type, callAttributes, options] = savedObjectsClient.create.mock.calls[0]; + expect(type).toBe(SEARCH_SESSION_TYPE); + expect(options).toStrictEqual({ id: sessionId }); + expect(callAttributes).toHaveProperty('idMapping', { + [requestHash]: { + id: searchId, + status: SearchSessionStatus.IN_PROGRESS, + strategy: MOCK_STRATEGY, + }, + }); + expect(callAttributes).toHaveProperty('expires'); + expect(callAttributes).toHaveProperty('created'); + expect(callAttributes).toHaveProperty('touched'); + expect(callAttributes).toHaveProperty('sessionId', sessionId); + expect(callAttributes).toHaveProperty('persisted', false); }); - }); - describe('trackId', () => { - it('stores hash in memory when `isStored` is `false` for when `save` is called', async () => { + it('retries updating if update returned 404 and then update returned conflict 409 (first create race condition)', async () => { const searchRequest = { params: {} }; - const requestHash = createRequestHash(searchRequest.params); const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; - const isStored = false; - const name = 'my saved background search session'; - const appId = 'my_app_id'; - const urlGeneratorId = 'my_url_generator_id'; - const created = new Date().toISOString(); - const expires = new Date().toISOString(); - - const mockIdMapping = createMockIdMapping([]); - const setSpy = jest.fn(); - mockIdMapping.set = setSpy; - Object.defineProperty(service, 'sessionSearchMap', { - get: () => mockIdMapping, - }); - await service.trackId( - searchRequest, - searchId, - { sessionId, isStored, strategy: MOCK_STRATEGY }, - { savedObjectsClient } - ); + const mockUpdateSavedObject = { + ...mockSavedObject, + attributes: {}, + }; - expect(savedObjectsClient.update).not.toHaveBeenCalled(); + let counter = 0; + + savedObjectsClient.update.mockImplementation(() => { + return new Promise((resolve, reject) => { + if (counter === 0) { + counter++; + reject(SavedObjectsErrorHelpers.createGenericNotFoundError(sessionId)); + } else { + resolve(mockUpdateSavedObject); + } + }); + }); - await service.save( - sessionId, - { name, created, expires, appId, urlGeneratorId }, - { savedObjectsClient } + savedObjectsClient.create.mockRejectedValue( + SavedObjectsErrorHelpers.createConflictError(SEARCH_SESSION_TYPE, searchId) ); - expect(savedObjectsClient.create).toHaveBeenCalledWith( - SEARCH_SESSION_TYPE, - { - name, - created, - expires, - initialState: {}, - restoreState: {}, - status: SearchSessionStatus.IN_PROGRESS, - idMapping: {}, - appId, - urlGeneratorId, - sessionId, - }, - { id: sessionId } - ); + await service.trackId({ savedObjectsClient }, searchRequest, searchId, { + sessionId, + strategy: MOCK_STRATEGY, + }); - const [setSessionId, setParams] = setSpy.mock.calls[0]; - expect(setParams.ids.get(requestHash).id).toBe(searchId); - expect(setParams.ids.get(requestHash).strategy).toBe(MOCK_STRATEGY); - expect(setSessionId).toBe(sessionId); + expect(savedObjectsClient.update).toHaveBeenCalledTimes(2); + expect(savedObjectsClient.create).toHaveBeenCalledTimes(1); }); - it('updates saved object when `isStored` is `true`', async () => { + it('retries everything at most MAX_RETRIES times', async () => { const searchRequest = { params: {} }; - const requestHash = createRequestHash(searchRequest.params); const searchId = 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0'; - const isStored = true; - await service.trackId( - searchRequest, - searchId, - { sessionId, isStored, strategy: MOCK_STRATEGY }, - { savedObjectsClient } + savedObjectsClient.update.mockRejectedValue( + SavedObjectsErrorHelpers.createGenericNotFoundError(sessionId) + ); + savedObjectsClient.create.mockRejectedValue( + SavedObjectsErrorHelpers.createConflictError(SEARCH_SESSION_TYPE, searchId) ); - expect(savedObjectsClient.update).toHaveBeenCalledWith(SEARCH_SESSION_TYPE, sessionId, { - idMapping: { - [requestHash]: { - id: searchId, - strategy: MOCK_STRATEGY, - status: SearchStatus.IN_PROGRESS, - }, - }, + await service.trackId({ savedObjectsClient }, searchRequest, searchId, { + sessionId, + strategy: MOCK_STRATEGY, }); + + expect(savedObjectsClient.update).toHaveBeenCalledTimes(MAX_UPDATE_RETRIES); + expect(savedObjectsClient.create).toHaveBeenCalledTimes(MAX_UPDATE_RETRIES); }); }); @@ -377,7 +324,7 @@ describe('SearchSessionService', () => { const searchRequest = { params: {} }; expect(() => - service.getId(searchRequest, {}, { savedObjectsClient }) + service.getId({ savedObjectsClient }, searchRequest, {}) ).rejects.toMatchInlineSnapshot(`[Error: Session ID is required]`); }); @@ -385,7 +332,7 @@ describe('SearchSessionService', () => { const searchRequest = { params: {} }; expect(() => - service.getId(searchRequest, { sessionId, isStored: false }, { savedObjectsClient }) + service.getId({ savedObjectsClient }, searchRequest, { sessionId, isStored: false }) ).rejects.toMatchInlineSnapshot( `[Error: Cannot get search ID from a session that is not stored]` ); @@ -395,11 +342,11 @@ describe('SearchSessionService', () => { const searchRequest = { params: {} }; expect(() => - service.getId( - searchRequest, - { sessionId, isStored: true, isRestore: false }, - { savedObjectsClient } - ) + service.getId({ savedObjectsClient }, searchRequest, { + sessionId, + isStored: true, + isRestore: false, + }) ).rejects.toMatchInlineSnapshot( `[Error: Get search ID is only supported when restoring a session]` ); @@ -427,204 +374,128 @@ describe('SearchSessionService', () => { }; savedObjectsClient.get.mockResolvedValue(mockSession); - const id = await service.getId( - searchRequest, - { sessionId, isStored: true, isRestore: true }, - { savedObjectsClient } - ); + const id = await service.getId({ savedObjectsClient }, searchRequest, { + sessionId, + isStored: true, + isRestore: true, + }); expect(id).toBe(searchId); }); }); - describe('Monitor', () => { - it('schedules the next iteration', async () => { - const findSpy = jest.fn().mockResolvedValue({ saved_objects: [] }); - createMockInternalSavedObjectClient(findSpy); - - const mockIdMapping = createMockIdMapping( - [[MOCK_KEY_HASH, { id: MOCK_ASYNC_ID, strategy: MOCK_STRATEGY }]], - moment() - ); - - Object.defineProperty(service, 'sessionSearchMap', { - get: () => mockIdMapping, - }); - - jest.advanceTimersByTime(INMEM_TRACKING_INTERVAL); - expect(findSpy).toHaveBeenCalledTimes(1); - await flushPromises(); - - jest.advanceTimersByTime(INMEM_TRACKING_INTERVAL); - expect(findSpy).toHaveBeenCalledTimes(2); - }); - - it('should delete expired IDs', async () => { - const findSpy = jest.fn().mockResolvedValueOnce({ saved_objects: [] }); - createMockInternalSavedObjectClient(findSpy); - - const mockIdMapping = createMockIdMapping( - [[MOCK_KEY_HASH, { id: MOCK_ASYNC_ID, strategy: MOCK_STRATEGY }]], - moment().subtract(2, 'm') + describe('getSearchIdMapping', () => { + it('retrieves the search IDs and strategies from the saved object', async () => { + const mockSession = { + id: 'd7170a35-7e2c-48d6-8dec-9a056721b489', + type: SEARCH_SESSION_TYPE, + attributes: { + name: 'my_name', + appId: 'my_app_id', + urlGeneratorId: 'my_url_generator_id', + idMapping: { + foo: { + id: 'FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0', + strategy: MOCK_STRATEGY, + }, + }, + }, + references: [], + }; + savedObjectsClient.get.mockResolvedValue(mockSession); + const searchIdMapping = await service.getSearchIdMapping( + { savedObjectsClient }, + mockSession.id ); - - const deleteSpy = jest.spyOn(mockIdMapping, 'delete'); - Object.defineProperty(service, 'sessionSearchMap', { - get: () => mockIdMapping, - }); - - // Get setInterval to fire - jest.advanceTimersByTime(INMEM_TRACKING_INTERVAL); - - expect(findSpy).not.toHaveBeenCalled(); - expect(deleteSpy).toHaveBeenCalledTimes(1); + expect(searchIdMapping).toMatchInlineSnapshot(` + Map { + "FnpFYlBpeXdCUTMyZXhCLTc1TWFKX0EbdDFDTzJzTE1Sck9PVTBIcW1iU05CZzo4MDA0" => "ese", + } + `); }); + }); - it('should delete IDs that passed max retries', async () => { - const findSpy = jest.fn().mockResolvedValueOnce({ saved_objects: [] }); - createMockInternalSavedObjectClient(findSpy); - - const mockIdMapping = createMockIdMapping( - [[MOCK_KEY_HASH, { id: MOCK_ASYNC_ID, strategy: MOCK_STRATEGY }]], - moment(), - MAX_UPDATE_RETRIES + describe('save', () => { + it('save throws if `name` is not provided', () => { + expect(service.save({ savedObjectsClient }, sessionId, {})).rejects.toMatchInlineSnapshot( + `[Error: Name is required]` ); - - const deleteSpy = jest.spyOn(mockIdMapping, 'delete'); - Object.defineProperty(service, 'sessionSearchMap', { - get: () => mockIdMapping, - }); - - // Get setInterval to fire - jest.advanceTimersByTime(INMEM_TRACKING_INTERVAL); - - expect(findSpy).not.toHaveBeenCalled(); - expect(deleteSpy).toHaveBeenCalledTimes(1); }); - it('should not fetch when no IDs are mapped', async () => { - const findSpy = jest.fn().mockResolvedValueOnce({ saved_objects: [] }); - createMockInternalSavedObjectClient(findSpy); - - jest.advanceTimersByTime(INMEM_TRACKING_INTERVAL); - expect(findSpy).not.toHaveBeenCalled(); + it('save throws if `appId` is not provided', () => { + expect( + service.save({ savedObjectsClient }, sessionId, { name: 'banana' }) + ).rejects.toMatchInlineSnapshot(`[Error: AppId is required]`); }); - it('should try to fetch saved objects if some ids are mapped', async () => { - const mockIdMapping = createMockIdMapping([[MOCK_KEY_HASH, MOCK_ASYNC_ID]]); - Object.defineProperty(service, 'sessionSearchMap', { - get: () => mockIdMapping, - }); - - const findSpy = jest.fn().mockResolvedValueOnce({ saved_objects: [] }); - const bulkUpdateSpy = jest.fn().mockResolvedValueOnce({ saved_objects: [] }); - createMockInternalSavedObjectClient(findSpy, bulkUpdateSpy); - - jest.advanceTimersByTime(INMEM_TRACKING_INTERVAL); - expect(findSpy).toHaveBeenCalledTimes(1); - expect(bulkUpdateSpy).not.toHaveBeenCalled(); + it('save throws if `generator id` is not provided', () => { + expect( + service.save({ savedObjectsClient }, sessionId, { name: 'banana', appId: 'nanana' }) + ).rejects.toMatchInlineSnapshot(`[Error: UrlGeneratorId is required]`); }); - it('should update saved objects if they are found, and delete session on success', async () => { - const mockIdMapping = createMockIdMapping([[MOCK_KEY_HASH, MOCK_ASYNC_ID]], undefined, 1); - const mockMapDeleteSpy = jest.fn(); - const mockSessionDeleteSpy = jest.fn(); - mockIdMapping.delete = mockMapDeleteSpy; - mockIdMapping.get(MOCK_SESSION_ID)!.ids.delete = mockSessionDeleteSpy; - Object.defineProperty(service, 'sessionSearchMap', { - get: () => mockIdMapping, - }); + it('saving updates an existing saved object and persists it', async () => { + const mockUpdateSavedObject = { + ...mockSavedObject, + attributes: {}, + }; + savedObjectsClient.update.mockResolvedValue(mockUpdateSavedObject); - const findSpy = jest.fn().mockResolvedValueOnce({ - saved_objects: [ - { - id: MOCK_SESSION_ID, - attributes: { - idMapping: { - b: 'c', - }, - }, - }, - ], - }); - const bulkUpdateSpy = jest.fn().mockResolvedValueOnce({ - saved_objects: [ - { - id: MOCK_SESSION_ID, - attributes: { - idMapping: { - b: 'c', - [MOCK_KEY_HASH]: { - id: MOCK_ASYNC_ID, - strategy: MOCK_STRATEGY, - }, - }, - }, - }, - ], + await service.save({ savedObjectsClient }, sessionId, { + name: 'banana', + appId: 'nanana', + urlGeneratorId: 'panama', }); - createMockInternalSavedObjectClient(findSpy, bulkUpdateSpy); - jest.advanceTimersByTime(INMEM_TRACKING_INTERVAL); - - // Release timers to call check after test actions are done. - jest.useRealTimers(); - await new Promise((r) => setTimeout(r, 15)); - - expect(findSpy).toHaveBeenCalledTimes(1); - expect(bulkUpdateSpy).toHaveBeenCalledTimes(1); - expect(mockSessionDeleteSpy).toHaveBeenCalledTimes(2); - expect(mockSessionDeleteSpy).toBeCalledWith('b'); - expect(mockSessionDeleteSpy).toBeCalledWith(MOCK_KEY_HASH); - expect(mockMapDeleteSpy).toBeCalledTimes(1); + expect(savedObjectsClient.update).toHaveBeenCalled(); + expect(savedObjectsClient.create).not.toHaveBeenCalled(); + + const [type, id, callAttributes] = savedObjectsClient.update.mock.calls[0]; + expect(type).toBe(SEARCH_SESSION_TYPE); + expect(id).toBe(sessionId); + expect(callAttributes).not.toHaveProperty('idMapping'); + expect(callAttributes).toHaveProperty('touched'); + expect(callAttributes).toHaveProperty('persisted', true); + expect(callAttributes).toHaveProperty('name', 'banana'); + expect(callAttributes).toHaveProperty('appId', 'nanana'); + expect(callAttributes).toHaveProperty('urlGeneratorId', 'panama'); + expect(callAttributes).toHaveProperty('initialState', {}); + expect(callAttributes).toHaveProperty('restoreState', {}); }); - it('should update saved objects if they are found, and increase retryCount on error', async () => { - const mockIdMapping = createMockIdMapping([[MOCK_KEY_HASH, MOCK_ASYNC_ID]]); - const mockMapDeleteSpy = jest.fn(); - const mockSessionDeleteSpy = jest.fn(); - mockIdMapping.delete = mockMapDeleteSpy; - mockIdMapping.get(MOCK_SESSION_ID)!.ids.delete = mockSessionDeleteSpy; - Object.defineProperty(service, 'sessionSearchMap', { - get: () => mockIdMapping, - }); - - const findSpy = jest.fn().mockResolvedValueOnce({ - saved_objects: [ - { - id: MOCK_SESSION_ID, - attributes: { - idMapping: { - b: { - id: 'c', - strategy: MOCK_STRATEGY, - }, - }, - }, - }, - ], - }); - const bulkUpdateSpy = jest.fn().mockResolvedValueOnce({ - saved_objects: [ - { - id: MOCK_SESSION_ID, - error: 'not ok', - }, - ], - }); - createMockInternalSavedObjectClient(findSpy, bulkUpdateSpy); + it('saving creates a new persisted saved object, if it did not exist', async () => { + const mockCreatedSavedObject = { + ...mockSavedObject, + attributes: {}, + }; - jest.advanceTimersByTime(INMEM_TRACKING_INTERVAL); + savedObjectsClient.update.mockRejectedValue( + SavedObjectsErrorHelpers.createGenericNotFoundError(sessionId) + ); + savedObjectsClient.create.mockResolvedValue(mockCreatedSavedObject); - // Release timers to call check after test actions are done. - jest.useRealTimers(); - await new Promise((r) => setTimeout(r, 15)); + await service.save({ savedObjectsClient }, sessionId, { + name: 'banana', + appId: 'nanana', + urlGeneratorId: 'panama', + }); - expect(findSpy).toHaveBeenCalledTimes(1); - expect(bulkUpdateSpy).toHaveBeenCalledTimes(1); - expect(mockSessionDeleteSpy).not.toHaveBeenCalled(); - expect(mockMapDeleteSpy).not.toHaveBeenCalled(); - expect(mockIdMapping.get(MOCK_SESSION_ID)!.retryCount).toBe(1); + expect(savedObjectsClient.update).toHaveBeenCalledTimes(1); + expect(savedObjectsClient.create).toHaveBeenCalledTimes(1); + + const [type, callAttributes, options] = savedObjectsClient.create.mock.calls[0]; + expect(type).toBe(SEARCH_SESSION_TYPE); + expect(options?.id).toBe(sessionId); + expect(callAttributes).toHaveProperty('idMapping', {}); + expect(callAttributes).toHaveProperty('touched'); + expect(callAttributes).toHaveProperty('expires'); + expect(callAttributes).toHaveProperty('created'); + expect(callAttributes).toHaveProperty('persisted', true); + expect(callAttributes).toHaveProperty('name', 'banana'); + expect(callAttributes).toHaveProperty('appId', 'nanana'); + expect(callAttributes).toHaveProperty('urlGeneratorId', 'panama'); + expect(callAttributes).toHaveProperty('initialState', {}); + expect(callAttributes).toHaveProperty('restoreState', {}); }); }); }); diff --git a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts index 794baa21e2f4c..8496a0513caeb 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/session_service.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/session_service.ts @@ -4,58 +4,38 @@ * you may not use this file except in compliance with the Elastic License. */ -import moment, { Moment } from 'moment'; -import { from, Observable } from 'rxjs'; -import { first, switchMap } from 'rxjs/operators'; +import { Observable } from 'rxjs'; +import { first } from 'rxjs/operators'; import { + CoreSetup, CoreStart, KibanaRequest, - SavedObjectsClient, SavedObjectsClientContract, Logger, SavedObject, - CoreSetup, - SavedObjectsBulkUpdateObject, SavedObjectsFindOptions, + SavedObjectsErrorHelpers, } from '../../../../../../src/core/server'; -import { - IKibanaSearchRequest, - IKibanaSearchResponse, - ISearchOptions, - KueryNode, - nodeBuilder, - tapFirst, -} from '../../../../../../src/plugins/data/common'; -import { - ISearchStrategy, - ISessionService, - SearchStrategyDependencies, -} from '../../../../../../src/plugins/data/server'; +import { IKibanaSearchRequest, ISearchOptions } from '../../../../../../src/plugins/data/common'; +import { ISearchSessionService } from '../../../../../../src/plugins/data/server'; import { TaskManagerSetupContract, TaskManagerStartContract, } from '../../../../task_manager/server'; import { - SearchSessionSavedObjectAttributes, SearchSessionRequestInfo, + SearchSessionSavedObjectAttributes, SearchSessionStatus, + SEARCH_SESSION_TYPE, } from '../../../common'; -import { SEARCH_SESSION_TYPE } from '../../saved_objects'; import { createRequestHash } from './utils'; import { ConfigSchema } from '../../../config'; import { registerSearchSessionsTask, scheduleSearchSessionsTasks } from './monitoring_task'; -import { SearchStatus } from './types'; +import { SearchSessionsConfig, SearchStatus } from './types'; export interface SearchSessionDependencies { savedObjectsClient: SavedObjectsClientContract; } - -export interface SessionInfo { - insertTime: Moment; - retryCount: number; - ids: Map; -} - interface SetupDependencies { taskManager: TaskManagerSetupContract; } @@ -64,16 +44,11 @@ interface StartDependencies { taskManager: TaskManagerStartContract; } -type SearchSessionsConfig = ConfigSchema['search']['sessions']; - -export class SearchSessionService implements ISessionService { - /** - * Map of sessionId to { [requestHash]: searchId } - * @private - */ - private sessionSearchMap = new Map(); - private internalSavedObjectsClient!: SavedObjectsClientContract; - private monitorTimer!: NodeJS.Timeout; +function sleep(ms: number) { + return new Promise((r) => setTimeout(r, ms)); +} +export class SearchSessionService + implements ISearchSessionService { private config!: SearchSessionsConfig; constructor( @@ -95,208 +70,113 @@ export class SearchSessionService implements ISessionService { return this.setupMonitoring(core, deps); } - public stop() { - this.sessionSearchMap.clear(); - clearTimeout(this.monitorTimer); - } + public stop() {} private setupMonitoring = async (core: CoreStart, deps: StartDependencies) => { if (this.config.enabled) { scheduleSearchSessionsTasks(deps.taskManager, this.logger, this.config.trackingInterval); - this.logger.debug(`setupMonitoring | Enabling monitoring`); - const internalRepo = core.savedObjects.createInternalRepository([SEARCH_SESSION_TYPE]); - this.internalSavedObjectsClient = new SavedObjectsClient(internalRepo); - this.monitorMappedIds(); } }; - /** - * Compiles a KQL Query to fetch sessions by ID. - * Done as a performance optimization workaround. - */ - private sessionIdsAsFilters(sessionIds: string[]): KueryNode { - return nodeBuilder.or( - sessionIds.map((id) => { - return nodeBuilder.is(`${SEARCH_SESSION_TYPE}.attributes.sessionId`, id); - }) - ); - } - - /** - * Gets all {@link SessionSavedObjectAttributes | Background Searches} that - * currently being tracked by the service. - * - * @remarks - * Uses `internalSavedObjectsClient` as this is called asynchronously, not within the - * context of a user's session. - */ - private async getAllMappedSavedObjects() { - const filter = this.sessionIdsAsFilters(Array.from(this.sessionSearchMap.keys())); - const res = await this.internalSavedObjectsClient.find({ - perPage: this.config.pageSize, // If there are more sessions in memory, they will be synced when some items are cleared out. - type: SEARCH_SESSION_TYPE, - filter, - namespaces: ['*'], - }); - this.logger.debug(`getAllMappedSavedObjects | Got ${res.saved_objects.length} items`); - return res.saved_objects; - } - - private clearSessions = async () => { - const curTime = moment(); - - this.sessionSearchMap.forEach((sessionInfo, sessionId) => { - if ( - moment.duration(curTime.diff(sessionInfo.insertTime)).asMilliseconds() > - this.config.inMemTimeout.asMilliseconds() - ) { - this.logger.debug(`clearSessions | Deleting expired session ${sessionId}`); - this.sessionSearchMap.delete(sessionId); - } else if (sessionInfo.retryCount >= this.config.maxUpdateRetries) { - this.logger.warn(`clearSessions | Deleting failed session ${sessionId}`); - this.sessionSearchMap.delete(sessionId); - } - }); - }; - - private async monitorMappedIds() { - this.monitorTimer = setTimeout(async () => { - try { - this.clearSessions(); - - if (!this.sessionSearchMap.size) return; - this.logger.debug(`monitorMappedIds | Map contains ${this.sessionSearchMap.size} items`); - - const savedSessions = await this.getAllMappedSavedObjects(); - const updatedSessions = await this.updateAllSavedObjects(savedSessions); + private updateOrCreate = async ( + deps: SearchSessionDependencies, + sessionId: string, + attributes: Partial, + retry: number = 1 + ): Promise | undefined> => { + const retryOnConflict = async (e: any) => { + this.logger.debug(`Conflict error | ${sessionId}`); + // Randomize sleep to spread updates out in case of conflicts + await sleep(100 + Math.random() * 50); + return await this.updateOrCreate(deps, sessionId, attributes, retry + 1); + }; - updatedSessions.forEach((updatedSavedObject) => { - const sessionInfo = this.sessionSearchMap.get(updatedSavedObject.id)!; - if (updatedSavedObject.error) { - this.logger.warn( - `monitorMappedIds | update error ${JSON.stringify(updatedSavedObject.error) || ''}` - ); - // Retry next time - sessionInfo.retryCount++; - } else if (updatedSavedObject.attributes.idMapping) { - // Delete the ids that we just saved, avoiding a potential new ids being lost. - Object.keys(updatedSavedObject.attributes.idMapping).forEach((key) => { - sessionInfo.ids.delete(key); - }); - // If the session object is empty, delete it as well - if (!sessionInfo.ids.entries.length) { - this.sessionSearchMap.delete(updatedSavedObject.id); - } else { - sessionInfo.retryCount = 0; - } + this.logger.debug(`updateOrCreate | ${sessionId} | ${retry}`); + try { + return (await this.update( + deps, + sessionId, + attributes + )) as SavedObject; + } catch (e) { + if (SavedObjectsErrorHelpers.isNotFoundError(e)) { + try { + this.logger.debug(`Object not found | ${sessionId}`); + return await this.create(deps, sessionId, attributes); + } catch (createError) { + if ( + SavedObjectsErrorHelpers.isConflictError(createError) && + retry < this.config.maxUpdateRetries + ) { + return await retryOnConflict(createError); + } else { + this.logger.error(createError); } - }); - } catch (e) { - this.logger.error(`monitorMappedIds | Error while updating sessions. ${e}`); - } finally { - this.monitorMappedIds(); + } + } else if ( + SavedObjectsErrorHelpers.isConflictError(e) && + retry < this.config.maxUpdateRetries + ) { + return await retryOnConflict(e); + } else { + this.logger.error(e); } - }, this.config.trackingInterval.asMilliseconds()); - } - - private async updateAllSavedObjects( - activeMappingObjects: Array> - ) { - if (!activeMappingObjects.length) return []; - - this.logger.debug(`updateAllSavedObjects | Updating ${activeMappingObjects.length} items`); - const updatedSessions: Array< - SavedObjectsBulkUpdateObject - > = activeMappingObjects - .filter((so) => !so.error) - .map((sessionSavedObject) => { - const sessionInfo = this.sessionSearchMap.get(sessionSavedObject.id); - const idMapping = sessionInfo ? Object.fromEntries(sessionInfo.ids.entries()) : {}; - sessionSavedObject.attributes.idMapping = { - ...sessionSavedObject.attributes.idMapping, - ...idMapping, - }; - return { - ...sessionSavedObject, - namespace: sessionSavedObject.namespaces?.[0], - }; - }); - - const updateResults = await this.internalSavedObjectsClient.bulkUpdate( - updatedSessions - ); - return updateResults.saved_objects; - } - - public search( - strategy: ISearchStrategy, - searchRequest: Request, - options: ISearchOptions, - searchDeps: SearchStrategyDependencies, - deps: SearchSessionDependencies - ): Observable { - // If this is a restored background search session, look up the ID using the provided sessionId - const getSearchRequest = async () => - !options.isRestore || searchRequest.id - ? searchRequest - : { - ...searchRequest, - id: await this.getId(searchRequest, options, deps), - }; + } - return from(getSearchRequest()).pipe( - switchMap((request) => strategy.search(request, options, searchDeps)), - tapFirst((response) => { - if (searchRequest.id || !options.sessionId || !response.id || options.isRestore) return; - this.trackId(searchRequest, response.id, options, deps); - }) - ); - } + return undefined; + }; - // TODO: Generate the `userId` from the realm type/realm name/username public save = async ( + deps: SearchSessionDependencies, sessionId: string, { name, appId, - created = new Date().toISOString(), - expires = new Date(Date.now() + this.config.defaultExpiration.asMilliseconds()).toISOString(), - status = SearchSessionStatus.IN_PROGRESS, urlGeneratorId, initialState = {}, restoreState = {}, - }: Partial, - { savedObjectsClient }: SearchSessionDependencies + }: Partial ) => { if (!name) throw new Error('Name is required'); if (!appId) throw new Error('AppId is required'); if (!urlGeneratorId) throw new Error('UrlGeneratorId is required'); - this.logger.debug(`save | ${sessionId}`); - - const attributes = { + return this.updateOrCreate(deps, sessionId, { name, - created, - expires, - status, + appId, + urlGeneratorId, initialState, restoreState, - idMapping: {}, - urlGeneratorId, - appId, - sessionId, - }; - const session = await savedObjectsClient.create( + persisted: true, + }); + }; + + private create = ( + { savedObjectsClient }: SearchSessionDependencies, + sessionId: string, + attributes: Partial + ) => { + this.logger.debug(`create | ${sessionId}`); + return savedObjectsClient.create( SEARCH_SESSION_TYPE, - attributes, + { + sessionId, + status: SearchSessionStatus.IN_PROGRESS, + expires: new Date( + Date.now() + this.config.defaultExpiration.asMilliseconds() + ).toISOString(), + created: new Date().toISOString(), + touched: new Date().toISOString(), + idMapping: {}, + persisted: false, + ...attributes, + }, { id: sessionId } ); - - return session; }; // TODO: Throw an error if this session doesn't belong to this user - public get = (sessionId: string, { savedObjectsClient }: SearchSessionDependencies) => { + public get = ({ savedObjectsClient }: SearchSessionDependencies, sessionId: string) => { this.logger.debug(`get | ${sessionId}`); return savedObjectsClient.get( SEARCH_SESSION_TYPE, @@ -306,8 +186,8 @@ export class SearchSessionService implements ISessionService { // TODO: Throw an error if this session doesn't belong to this user public find = ( - options: Omit, - { savedObjectsClient }: SearchSessionDependencies + { savedObjectsClient }: SearchSessionDependencies, + options: Omit ) => { return savedObjectsClient.find({ ...options, @@ -317,70 +197,80 @@ export class SearchSessionService implements ISessionService { // TODO: Throw an error if this session doesn't belong to this user public update = ( + { savedObjectsClient }: SearchSessionDependencies, sessionId: string, - attributes: Partial, - { savedObjectsClient }: SearchSessionDependencies + attributes: Partial ) => { this.logger.debug(`update | ${sessionId}`); return savedObjectsClient.update( SEARCH_SESSION_TYPE, sessionId, - attributes + { + ...attributes, + touched: new Date().toISOString(), + } ); }; + public extend(deps: SearchSessionDependencies, sessionId: string, expires: Date) { + this.logger.debug(`extend | ${sessionId}`); + + return this.update(deps, sessionId, { expires: expires.toISOString() }); + } + // TODO: Throw an error if this session doesn't belong to this user - public delete = (sessionId: string, { savedObjectsClient }: SearchSessionDependencies) => { - return savedObjectsClient.delete(SEARCH_SESSION_TYPE, sessionId); + public cancel = (deps: SearchSessionDependencies, sessionId: string) => { + return this.update(deps, sessionId, { + status: SearchSessionStatus.CANCELLED, + }); }; /** - * Tracks the given search request/search ID in the saved session (if it exists). Otherwise, just - * store it in memory until a saved session exists. + * Tracks the given search request/search ID in the saved session. * @internal */ public trackId = async ( + deps: SearchSessionDependencies, searchRequest: IKibanaSearchRequest, searchId: string, - { sessionId, isStored, strategy }: ISearchOptions, - deps: SearchSessionDependencies + { sessionId, strategy }: ISearchOptions ) => { if (!sessionId || !searchId) return; this.logger.debug(`trackId | ${sessionId} | ${searchId}`); - const requestHash = createRequestHash(searchRequest.params); - const searchInfo = { - id: searchId, - strategy: strategy!, - status: SearchStatus.IN_PROGRESS, - }; - // If there is already a saved object for this session, update it to include this request/ID. - // Otherwise, just update the in-memory mapping for this session for when the session is saved. - if (isStored) { - const attributes = { - idMapping: { [requestHash]: searchInfo }, - }; - await this.update(sessionId, attributes, deps); - } else { - const map = this.sessionSearchMap.get(sessionId) ?? { - insertTime: moment(), - retryCount: 0, - ids: new Map(), + let idMapping: Record = {}; + + if (searchRequest.params) { + const requestHash = createRequestHash(searchRequest.params); + const searchInfo = { + id: searchId, + strategy: strategy!, + status: SearchStatus.IN_PROGRESS, }; - map.ids.set(requestHash, searchInfo); - this.sessionSearchMap.set(sessionId, map); + idMapping = { [requestHash]: searchInfo }; } + + await this.updateOrCreate(deps, sessionId, { idMapping }); }; + public async getSearchIdMapping(deps: SearchSessionDependencies, sessionId: string) { + const searchSession = await this.get(deps, sessionId); + const searchIdMapping = new Map(); + Object.values(searchSession.attributes.idMapping).forEach((requestInfo) => { + searchIdMapping.set(requestInfo.id, requestInfo.strategy); + }); + return searchIdMapping; + } + /** * Look up an existing search ID that matches the given request in the given session so that the * request can continue rather than restart. * @internal */ public getId = async ( + deps: SearchSessionDependencies, searchRequest: IKibanaSearchRequest, - { sessionId, isStored, isRestore }: ISearchOptions, - deps: SearchSessionDependencies + { sessionId, isStored, isRestore }: ISearchOptions ) => { if (!sessionId) { throw new Error('Session ID is required'); @@ -390,11 +280,13 @@ export class SearchSessionService implements ISessionService { throw new Error('Get search ID is only supported when restoring a session'); } - const session = await this.get(sessionId, deps); + const session = await this.get(deps, sessionId); const requestHash = createRequestHash(searchRequest.params); if (!session.attributes.idMapping.hasOwnProperty(requestHash)) { + this.logger.error(`getId | ${sessionId} | ${requestHash} not found`); throw new Error('No search ID in this session matching the given search request'); } + this.logger.debug(`getId | ${sessionId} | ${requestHash}`); return session.attributes.idMapping[requestHash].id; }; @@ -406,17 +298,15 @@ export class SearchSessionService implements ISessionService { }); const deps = { savedObjectsClient }; return { - search: ( - strategy: ISearchStrategy, - ...args: Parameters['search']> - ) => this.search(strategy, ...args, deps), - save: (sessionId: string, attributes: Partial) => - this.save(sessionId, attributes, deps), - get: (sessionId: string) => this.get(sessionId, deps), - find: (options: SavedObjectsFindOptions) => this.find(options, deps), - update: (sessionId: string, attributes: Partial) => - this.update(sessionId, attributes, deps), - delete: (sessionId: string) => this.delete(sessionId, deps), + getId: this.getId.bind(this, deps), + trackId: this.trackId.bind(this, deps), + getSearchIdMapping: this.getSearchIdMapping.bind(this, deps), + save: this.save.bind(this, deps), + get: this.get.bind(this, deps), + find: this.find.bind(this, deps), + update: this.update.bind(this, deps), + extend: this.extend.bind(this, deps), + cancel: this.cancel.bind(this, deps), }; }; }; diff --git a/x-pack/plugins/data_enhanced/server/search/session/types.ts b/x-pack/plugins/data_enhanced/server/search/session/types.ts index c30e03f70d2dc..136c37942cb2e 100644 --- a/x-pack/plugins/data_enhanced/server/search/session/types.ts +++ b/x-pack/plugins/data_enhanced/server/search/session/types.ts @@ -4,8 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ +import { ConfigSchema } from '../../../config'; + export enum SearchStatus { IN_PROGRESS = 'in_progress', ERROR = 'error', COMPLETE = 'complete', } + +export type SearchSessionsConfig = ConfigSchema['search']['sessions']; diff --git a/x-pack/plugins/enterprise_search/public/applications/__mocks__/react_router_history.mock.ts b/x-pack/plugins/enterprise_search/public/applications/__mocks__/react_router_history.mock.ts index 1516aa9096eca..d3c11d33fdbf7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/__mocks__/react_router_history.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/__mocks__/react_router_history.mock.ts @@ -24,12 +24,20 @@ export const mockLocation = { state: {}, }; -jest.mock('react-router-dom', () => ({ - ...(jest.requireActual('react-router-dom') as object), - useHistory: jest.fn(() => mockHistory), - useLocation: jest.fn(() => mockLocation), - useParams: jest.fn(() => ({})), -})); +jest.mock('react-router-dom', () => { + const originalModule = jest.requireActual('react-router-dom'); + return { + ...originalModule, + useHistory: jest.fn(() => mockHistory), + useLocation: jest.fn(() => mockLocation), + useParams: jest.fn(() => ({})), + // Note: RR's generatePath() opinionatedly encodeURI()s paths (although this doesn't actually + // show up/affect the final browser URL). Since we already have a generateEncodedPath helper & + // RR is removing this behavior in history 5.0+, I'm mocking tests to remove the extra encoding + // for now to make reading generateEncodedPath URLs a little less of a pain + generatePath: jest.fn((path, params) => decodeURI(originalModule.generatePath(path, params))), + }; +}); /** * For example usage, @see public/applications/shared/react_router_helpers/eui_link.test.tsx diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/engine_logic.mock.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/engine_logic.mock.ts index 6326a41c1d2ca..edc87d7025c5d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/engine_logic.mock.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/__mocks__/engine_logic.mock.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { generatePath } from 'react-router-dom'; +import { generateEncodedPath } from '../utils/encode_path_params'; export const mockEngineValues = { engineName: 'some-engine', @@ -12,7 +12,7 @@ export const mockEngineValues = { }; export const mockGenerateEnginePath = jest.fn((path, pathParams = {}) => - generatePath(path, { engineName: mockEngineValues.engineName, ...pathParams }) + generateEncodedPath(path, { engineName: mockEngineValues.engineName, ...pathParams }) ); jest.mock('../components/engine', () => ({ diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/shared_columns.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/shared_columns.tsx index 16743405e0b5e..8546ee428b681 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/shared_columns.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/components/analytics_tables/shared_columns.tsx @@ -56,7 +56,7 @@ export const ACTIONS_COLUMN = { { defaultMessage: 'View query analytics' } ), type: 'icon', - icon: 'popout', + icon: 'eye', color: 'primary', onClick: (item: Query | RecentQuery) => { const { navigateToUrl } = KibanaLogic.values; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/query_detail.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/query_detail.test.tsx index 7705d342ecdce..42f13a0631a0a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/query_detail.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/query_detail.test.tsx @@ -13,6 +13,7 @@ import { shallow } from 'enzyme'; import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome'; +import { AnalyticsLayout } from '../analytics_layout'; import { AnalyticsCards, AnalyticsChart, QueryClicksTable } from '../components'; import { QueryDetail } from './'; @@ -20,7 +21,7 @@ describe('QueryDetail', () => { const mockBreadcrumbs = ['Engines', 'some-engine', 'Analytics']; beforeEach(() => { - (useParams as jest.Mock).mockReturnValueOnce({ query: 'some-query' }); + (useParams as jest.Mock).mockReturnValue({ query: 'some-query' }); setMockValues({ totalQueriesForQuery: 100, @@ -31,6 +32,7 @@ describe('QueryDetail', () => { it('renders', () => { const wrapper = shallow(); + expect(wrapper.find(AnalyticsLayout).prop('title')).toEqual('"some-query"'); expect(wrapper.find(SetPageChrome).prop('trail')).toEqual([ 'Engines', 'some-engine', @@ -43,4 +45,11 @@ describe('QueryDetail', () => { expect(wrapper.find(AnalyticsChart)).toHaveLength(1); expect(wrapper.find(QueryClicksTable)).toHaveLength(1); }); + + it('renders empty "" search titles correctly', () => { + (useParams as jest.Mock).mockReturnValue({ query: '""' }); + const wrapper = shallow(); + + expect(wrapper.find(AnalyticsLayout).prop('title')).toEqual('""'); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/query_detail.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/query_detail.tsx index d5d864f35f681..0ec81f5caa935 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/query_detail.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/analytics/views/query_detail.tsx @@ -5,7 +5,6 @@ */ import React from 'react'; -import { useParams } from 'react-router-dom'; import { useValues } from 'kea'; import { i18n } from '@kbn/i18n'; @@ -13,6 +12,7 @@ import { EuiSpacer } from '@elastic/eui'; import { SetAppSearchChrome as SetPageChrome } from '../../../../shared/kibana_chrome'; import { BreadcrumbTrail } from '../../../../shared/kibana_chrome/generate_breadcrumbs'; +import { useDecodedParams } from '../../../utils/encode_path_params'; import { AnalyticsLayout } from '../analytics_layout'; import { AnalyticsSection, QueryClicksTable } from '../components'; @@ -27,14 +27,15 @@ interface Props { breadcrumbs: BreadcrumbTrail; } export const QueryDetail: React.FC = ({ breadcrumbs }) => { - const { query } = useParams() as { query: string }; + const { query } = useDecodedParams(); + const queryTitle = query === '""' ? query : `"${query}"`; const { totalQueriesForQuery, queriesPerDayForQuery, startDate, topClicksForQuery } = useValues( AnalyticsLogic ); return ( - + { expect(actions.deleteDocument).toHaveBeenCalledWith('1'); }); - - it('correctly decodes document IDs', () => { - (useParams as jest.Mock).mockReturnValueOnce({ documentId: 'hello%20world%20%26%3F!' }); - const wrapper = shallow(); - expect(wrapper.find('h1').text()).toEqual('Document: hello world &?!'); - }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail.tsx index 1be7e6c53d343..3fadda6165c9b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/documents/document_detail.tsx @@ -23,6 +23,7 @@ import { i18n } from '@kbn/i18n'; import { Loading } from '../../../shared/loading'; import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; import { FlashMessages } from '../../../shared/flash_messages'; +import { useDecodedParams } from '../../utils/encode_path_params'; import { ResultFieldValue } from '../result'; import { DocumentDetailLogic } from './document_detail_logic'; @@ -43,6 +44,7 @@ export const DocumentDetail: React.FC = ({ engineBreadcrumb }) => { const { deleteDocument, getDocumentDetails, setFields } = useActions(DocumentDetailLogic); const { documentId } = useParams() as { documentId: string }; + const { documentId: documentTitle } = useDecodedParams(); useEffect(() => { getDocumentDetails(documentId); @@ -74,13 +76,11 @@ export const DocumentDetail: React.FC = ({ engineBreadcrumb }) => { return ( <> - + -

{DOCUMENT_DETAIL_TITLE(decodeURIComponent(documentId))}

+

{DOCUMENT_DETAIL_TITLE(documentTitle)}

diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx index 0e5a7d56e9065..6f0d8571ab281 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_nav.tsx @@ -174,8 +174,7 @@ export const EngineNav: React.FC = () => { )} {canManageEngineRelevanceTuning && ( diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx index aa8b406cf7774..f4fabc29a6b59 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.test.tsx @@ -16,6 +16,7 @@ import { Switch, Redirect, useParams } from 'react-router-dom'; import { Loading } from '../../../shared/loading'; import { EngineOverview } from '../engine_overview'; import { AnalyticsRouter } from '../analytics'; +import { RelevanceTuning } from '../relevance_tuning'; import { EngineRouter } from './engine_router'; @@ -93,4 +94,11 @@ describe('EngineRouter', () => { expect(wrapper.find(AnalyticsRouter)).toHaveLength(1); }); + + it('renders an relevance tuning view', () => { + setMockValues({ ...values, myRole: { canManageEngineRelevanceTuning: true } }); + const wrapper = shallow(); + + expect(wrapper.find(RelevanceTuning)).toHaveLength(1); + }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx index fd21507a427d5..ba00792237971 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/engine_router.tsx @@ -23,7 +23,7 @@ import { // ENGINE_SCHEMA_PATH, // ENGINE_CRAWLER_PATH, // META_ENGINE_SOURCE_ENGINES_PATH, - // ENGINE_RELEVANCE_TUNING_PATH, + ENGINE_RELEVANCE_TUNING_PATH, // ENGINE_SYNONYMS_PATH, // ENGINE_CURATIONS_PATH, // ENGINE_RESULT_SETTINGS_PATH, @@ -37,6 +37,7 @@ import { Loading } from '../../../shared/loading'; import { EngineOverview } from '../engine_overview'; import { AnalyticsRouter } from '../analytics'; import { DocumentDetail, Documents } from '../documents'; +import { RelevanceTuning } from '../relevance_tuning'; import { EngineLogic } from './'; @@ -44,13 +45,13 @@ export const EngineRouter: React.FC = () => { const { myRole: { canViewEngineAnalytics, + canManageEngineRelevanceTuning, // canViewEngineDocuments, // canViewEngineSchema, // canViewEngineCrawler, // canViewMetaEngineSourceEngines, // canManageEngineSynonyms, // canManageEngineCurations, - // canManageEngineRelevanceTuning, // canManageEngineResultSettings, // canManageEngineSearchUi, // canViewEngineApiLogs, @@ -95,6 +96,11 @@ export const EngineRouter: React.FC = () => { + {canManageEngineRelevanceTuning && ( + + + + )} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/utils.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/utils.ts index b7efcbb6e6b27..8e197eb402ea7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/utils.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engine/utils.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { generatePath } from 'react-router-dom'; +import { generateEncodedPath } from '../../utils/encode_path_params'; import { EngineLogic } from './'; @@ -13,5 +13,5 @@ import { EngineLogic } from './'; */ export const generateEnginePath = (path: string, pathParams: object = {}) => { const { engineName } = EngineLogic.values; - return generatePath(path, { engineName, ...pathParams }); + return generateEncodedPath(path, { engineName, ...pathParams }); }; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx index a9455b4a2306a..34bf0fe1b3a04 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/engines/engines_table.tsx @@ -5,7 +5,6 @@ */ import React from 'react'; -import { generatePath } from 'react-router-dom'; import { useActions } from 'kea'; import { EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui'; import { FormattedMessage, FormattedDate, FormattedNumber } from '@kbn/i18n/react'; @@ -13,6 +12,7 @@ import { i18n } from '@kbn/i18n'; import { TelemetryLogic } from '../../../shared/telemetry'; import { EuiLinkTo } from '../../../shared/react_router_helpers'; +import { generateEncodedPath } from '../../utils/encode_path_params'; import { ENGINE_PATH } from '../../routes'; import { ENGINES_PAGE_SIZE } from '../../../../../common/constants'; @@ -41,7 +41,7 @@ export const EnginesTable: React.FC = ({ const { sendAppSearchTelemetry } = useActions(TelemetryLogic); const engineLinkProps = (engineName: string) => ({ - to: generatePath(ENGINE_PATH, { engineName }), + to: generateEncodedPath(ENGINE_PATH, { engineName }), onClick: () => sendAppSearchTelemetry({ action: 'clicked', diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/index.ts index 40f3ddbf2899b..55070255ac81b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/index.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/index.ts @@ -5,3 +5,4 @@ */ export { RELEVANCE_TUNING_TITLE } from './constants'; +export { RelevanceTuning } from './relevance_tuning'; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.test.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.test.tsx new file mode 100644 index 0000000000000..5934aca6be5f2 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.test.tsx @@ -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 React from 'react'; + +import { shallow } from 'enzyme'; + +import { RelevanceTuning } from './relevance_tuning'; + +describe('RelevanceTuning', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('renders', () => { + const wrapper = shallow(); + expect(wrapper.isEmptyRender()).toBe(false); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.tsx new file mode 100644 index 0000000000000..cca352904930b --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/relevance_tuning.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. + */ + +import React from 'react'; +import { + EuiPageHeader, + EuiPageHeaderSection, + EuiTitle, + EuiPageContentBody, + EuiPageContent, +} from '@elastic/eui'; + +import { SetAppSearchChrome as SetPageChrome } from '../../../shared/kibana_chrome'; +import { FlashMessages } from '../../../shared/flash_messages'; + +import { RELEVANCE_TUNING_TITLE } from './constants'; + +interface Props { + engineBreadcrumb: string[]; +} + +export const RelevanceTuning: React.FC = ({ engineBreadcrumb }) => { + return ( + <> + + + + +

{RELEVANCE_TUNING_TITLE}

+
+
+
+ + + + + + + ); +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx index a3935bb782f90..ff8b373f1bee3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/result/result.tsx @@ -5,7 +5,6 @@ */ import React, { useState, useMemo } from 'react'; -import { generatePath } from 'react-router-dom'; import classNames from 'classnames'; import './result.scss'; @@ -14,6 +13,7 @@ import { EuiPanel, EuiIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ReactRouterHelper } from '../../../shared/react_router_helpers/eui_components'; +import { generateEncodedPath } from '../../utils/encode_path_params'; import { ENGINE_DOCUMENT_DETAIL_PATH } from '../../routes'; import { Schema } from '../../../shared/types'; @@ -52,7 +52,7 @@ export const Result: React.FC = ({ if (schemaForTypeHighlights) return schemaForTypeHighlights[fieldName]; }; - const documentLink = generatePath(ENGINE_DOCUMENT_DETAIL_PATH, { + const documentLink = generateEncodedPath(ENGINE_DOCUMENT_DETAIL_PATH, { engineName: resultMeta.engine, documentId: resultMeta.id, }); @@ -135,7 +135,7 @@ export const Result: React.FC = ({ { defaultMessage: 'Visit document details' } )} > - + )} diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts index 7f12f7d29671a..080f6efcc71fb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts @@ -41,7 +41,7 @@ export const ENGINE_CRAWLER_PATH = `${ENGINE_PATH}/crawler`; export const META_ENGINE_SOURCE_ENGINES_PATH = `${ENGINE_PATH}/engines`; -export const ENGINE_RELEVANCE_TUNING_PATH = `${ENGINE_PATH}/search-settings`; +export const ENGINE_RELEVANCE_TUNING_PATH = `${ENGINE_PATH}/relevance_tuning`; export const ENGINE_SYNONYMS_PATH = `${ENGINE_PATH}/synonyms`; export const ENGINE_CURATIONS_PATH = `${ENGINE_PATH}/curations`; // TODO: Curations sub-pages diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/encode_path_params/index.test.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/encode_path_params/index.test.ts new file mode 100644 index 0000000000000..f311909bdf5be --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/encode_path_params/index.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 '../../../__mocks__/react_router_history.mock'; +import { useParams } from 'react-router-dom'; + +import { encodePathParams, generateEncodedPath, useDecodedParams } from './'; + +describe('encodePathParams', () => { + it('encodeURIComponent()s all object values', () => { + const params = { + someValue: 'hello world???', + anotherValue: 'test!@#$%^&*[]/|;:"<>~`', + }; + expect(encodePathParams(params)).toEqual({ + someValue: 'hello%20world%3F%3F%3F', + anotherValue: 'test!%40%23%24%25%5E%26*%5B%5D%2F%7C%3B%3A%22%3C%3E~%60', + }); + }); +}); + +describe('generateEncodedPath', () => { + it('generates a react router path with encoded path parameters', () => { + expect( + generateEncodedPath('/values/:someValue/:anotherValue/new', { + someValue: 'hello world???', + anotherValue: 'test!@#$%^&*[]/|;:"<>~`', + }) + ).toEqual( + '/values/hello%20world%3F%3F%3F/test!%40%23%24%25%5E%26*%5B%5D%2F%7C%3B%3A%22%3C%3E~%60/new' + ); + }); +}); + +describe('useDecodedParams', () => { + it('decodeURIComponent()s all object values from useParams()', () => { + (useParams as jest.Mock).mockReturnValue({ + someValue: 'hello%20world%3F%3F%3F', + anotherValue: 'test!%40%23%24%25%5E%26*%5B%5D%2F%7C%3B%3A%22%3C%3E~%60', + }); + expect(useDecodedParams()).toEqual({ + someValue: 'hello world???', + anotherValue: 'test!@#$%^&*[]/|;:"<>~`', + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/utils/encode_path_params/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/encode_path_params/index.ts new file mode 100644 index 0000000000000..c8934ba47fe45 --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/app_search/utils/encode_path_params/index.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 { generatePath, useParams } from 'react-router-dom'; + +type PathParams = Record; + +export const encodePathParams = (pathParams: PathParams) => { + const encodedParams: PathParams = {}; + + Object.entries(pathParams).map(([key, value]) => { + encodedParams[key] = encodeURIComponent(value); + }); + + return encodedParams; +}; + +export const generateEncodedPath = (path: string, pathParams: PathParams) => { + return generatePath(path, encodePathParams(pathParams)); +}; + +export const useDecodedParams = () => { + const decodedParams: PathParams = {}; + + const params = useParams(); + Object.entries(params).map(([key, value]) => { + decodedParams[key] = decodeURIComponent(value as string); + }); + + return decodedParams; +}; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts index 17fbbf517f347..94657d88a4d4f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts @@ -282,6 +282,8 @@ export const GITHUB_LINK_TITLE = i18n.translate( export const CUSTOM_SERVICE_TYPE = 'custom'; +export const WORKPLACE_SEARCH_URL_PREFIX = '/app/enterprise_search/workplace_search'; + export const DOCUMENTATION_LINK_TITLE = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.sources.documentation', { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts index 058645bd30862..be74e0494a543 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.test.ts @@ -4,16 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LogicMounter, mockFlashMessageHelpers, mockHttpValues } from '../../../../../__mocks__'; +import { + LogicMounter, + mockFlashMessageHelpers, + mockHttpValues, + mockKibanaValues, +} from '../../../../../__mocks__'; import { AppLogic } from '../../../../app_logic'; jest.mock('../../../../app_logic', () => ({ AppLogic: { values: { isOrganization: true } }, })); +import { SourcesLogic } from '../../sources_logic'; + import { nextTick } from '@kbn/test/jest'; import { CustomSource } from '../../../../types'; +import { SOURCES_PATH, getSourcesPath } from '../../../../routes'; import { sourceConfigData } from '../../../../__mocks__/content_sources.mock'; @@ -28,6 +36,7 @@ import { describe('AddSourceLogic', () => { const { mount } = new LogicMounter(AddSourceLogic); const { http } = mockHttpValues; + const { navigateToUrl } = mockKibanaValues; const { clearFlashMessages, flashAPIErrors } = mockFlashMessageHelpers; const defaultValues = { @@ -264,6 +273,55 @@ describe('AddSourceLogic', () => { }); }); + describe('saveSourceParams', () => { + const params = { + code: 'code123', + state: '"{"state": "foo"}"', + session_state: 'session123', + }; + + const queryString = + 'code=code123&state=%22%7B%22state%22%3A%20%22foo%22%7D%22&session_state=session123'; + + const response = { serviceName: 'name', indexPermissions: false, serviceType: 'zendesk' }; + + beforeEach(() => { + SourcesLogic.mount(); + }); + + it('sends params to server and calls correct methods', async () => { + const setAddedSourceSpy = jest.spyOn(SourcesLogic.actions, 'setAddedSource'); + const { serviceName, indexPermissions, serviceType } = response; + http.get.mockReturnValue(Promise.resolve(response)); + AddSourceLogic.actions.saveSourceParams(queryString); + expect(http.get).toHaveBeenCalledWith('/api/workplace_search/sources/create', { + query: { + ...params, + kibana_host: '', + }, + }); + + await nextTick(); + + expect(setAddedSourceSpy).toHaveBeenCalledWith(serviceName, indexPermissions, serviceType); + expect(navigateToUrl).toHaveBeenCalledWith( + getSourcesPath(SOURCES_PATH, AppLogic.values.isOrganization) + ); + }); + + it('handles error', async () => { + http.get.mockReturnValue(Promise.reject('this is an error')); + + AddSourceLogic.actions.saveSourceParams(queryString); + await nextTick(); + + expect(flashAPIErrors).toHaveBeenCalledWith('this is an error'); + expect(navigateToUrl).toHaveBeenCalledWith( + getSourcesPath(SOURCES_PATH, AppLogic.values.isOrganization) + ); + }); + }); + describe('organization context', () => { describe('getSourceConfigData', () => { it('calls API and sets values', async () => { @@ -301,22 +359,37 @@ describe('AddSourceLogic', () => { AddSourceLogic.actions.getSourceConnectData('github', successCallback); + const query = { + index_permissions: false, + kibana_host: '', + }; + expect(clearFlashMessages).toHaveBeenCalled(); expect(AddSourceLogic.values.buttonLoading).toEqual(true); - expect(http.get).toHaveBeenCalledWith('/api/workplace_search/org/sources/github/prepare'); + expect(http.get).toHaveBeenCalledWith( + '/api/workplace_search/org/sources/github/prepare', + { query } + ); await nextTick(); expect(setSourceConnectDataSpy).toHaveBeenCalledWith(sourceConnectData); expect(successCallback).toHaveBeenCalledWith(sourceConnectData.oauthUrl); expect(setButtonNotLoadingSpy).toHaveBeenCalled(); }); - it('appends query params', () => { + it('passes query params', () => { AddSourceLogic.actions.setSourceSubdomainValue('subdomain'); AddSourceLogic.actions.setSourceIndexPermissionsValue(true); AddSourceLogic.actions.getSourceConnectData('github', successCallback); + const query = { + index_permissions: true, + kibana_host: '', + subdomain: 'subdomain', + }; + expect(http.get).toHaveBeenCalledWith( - '/api/workplace_search/org/sources/github/prepare?subdomain=subdomain&index_permissions=true' + '/api/workplace_search/org/sources/github/prepare', + { query } ); }); @@ -413,7 +486,7 @@ describe('AddSourceLogic', () => { http.put ).toHaveBeenCalledWith( `/api/workplace_search/org/settings/connectors/${sourceConfigData.serviceType}`, - { body: JSON.stringify({ params }) } + { body: JSON.stringify(params) } ); await nextTick(); @@ -436,7 +509,7 @@ describe('AddSourceLogic', () => { }; expect(http.post).toHaveBeenCalledWith('/api/workplace_search/org/settings/connectors', { - body: JSON.stringify({ params: createParams }), + body: JSON.stringify(createParams), }); }); @@ -515,11 +588,15 @@ describe('AddSourceLogic', () => { }); it('getSourceConnectData', () => { + const query = { + kibana_host: '', + }; + AddSourceLogic.actions.getSourceConnectData('github', jest.fn()); - expect(http.get).toHaveBeenCalledWith( - '/api/workplace_search/account/sources/github/prepare' - ); + expect( + http.get + ).toHaveBeenCalledWith('/api/workplace_search/account/sources/github/prepare', { query }); }); it('getSourceReConnectData', () => { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts index a328cbe222afb..988c65d0d188b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/add_source_logic.ts @@ -8,9 +8,15 @@ import { keys, pickBy } from 'lodash'; import { kea, MakeLogicType } from 'kea'; +import { Search } from 'history'; + import { i18n } from '@kbn/i18n'; +import { HttpFetchQuery } from 'src/core/public'; + import { HttpLogic } from '../../../../../shared/http'; +import { KibanaLogic } from '../../../../../shared/kibana'; +import { parseQueryParams } from '../../../../../shared/query_params'; import { flashAPIErrors, @@ -19,9 +25,11 @@ import { } from '../../../../../shared/flash_messages'; import { staticSourceData } from '../../source_data'; -import { CUSTOM_SERVICE_TYPE } from '../../../../constants'; +import { SOURCES_PATH, getSourcesPath } from '../../../../routes'; +import { CUSTOM_SERVICE_TYPE, WORKPLACE_SEARCH_URL_PREFIX } from '../../../../constants'; import { AppLogic } from '../../../../app_logic'; +import { SourcesLogic } from '../../sources_logic'; import { CustomSource } from '../../../../types'; export interface AddSourceProps { @@ -42,6 +50,13 @@ export enum AddSourceSteps { ReAuthenticateStep = 'ReAuthenticate', } +export interface OauthParams { + code: string; + state: string; + session_state: string; + oauth_verifier?: string; +} + export interface AddSourceActions { initializeAddSource: (addSourceProps: AddSourceProps) => { addSourceProps: AddSourceProps }; setAddSourceProps: ({ @@ -75,6 +90,7 @@ export interface AddSourceActions { isUpdating: boolean, successCallback?: () => void ): { isUpdating: boolean; successCallback?(): void }; + saveSourceParams(search: Search): { search: Search }; getSourceConfigData(serviceType: string): { serviceType: string }; getSourceConnectData( serviceType: string, @@ -141,6 +157,15 @@ interface PreContentSourceResponse { githubOrganizations: string[]; } +/** + * Workplace Search needs to know the host for the redirect. As of yet, we do not + * have access to this in Kibana. We parse it from the browser and pass it as a param. + */ +const { + location: { href }, +} = window; +const kibanaHost = href.substr(0, href.indexOf(WORKPLACE_SEARCH_URL_PREFIX)); + export const AddSourceLogic = kea>({ path: ['enterprise_search', 'workplace_search', 'add_source_logic'], actions: { @@ -173,6 +198,7 @@ export const AddSourceLogic = kea ({ search }), createContentSource: ( serviceType: string, successCallback: () => void, @@ -356,14 +382,15 @@ export const AddSourceLogic = kea { + const { http } = HttpLogic.values; + const { isOrganization } = AppLogic.values; + const { navigateToUrl } = KibanaLogic.values; + const { setAddedSource } = SourcesLogic.actions; + const params = (parseQueryParams(search) as unknown) as OauthParams; + const query = { ...params, kibana_host: kibanaHost }; + const route = '/api/workplace_search/sources/create'; + + try { + const response = await http.get(route, { query }); + + const { serviceName, indexPermissions, serviceType } = response; + setAddedSource(serviceName, indexPermissions, serviceType); + } catch (e) { + flashAPIErrors(e); + } finally { + navigateToUrl(getSourcesPath(SOURCES_PATH, isOrganization)); + } + }, createContentSource: async ({ serviceType, successCallback, errorCallback }) => { clearFlashMessages(); const { isOrganization } = AppLogic.values; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.test.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.test.tsx index d29995484540c..6733c72366a7f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.test.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.test.tsx @@ -6,22 +6,22 @@ import '../../../../__mocks__/shallow_useeffect.mock'; -import { setMockValues, setMockActions, mockFlashMessageHelpers } from '../../../../__mocks__'; +import { setMockActions } from '../../../../__mocks__'; import React from 'react'; import { shallow } from 'enzyme'; -import { Redirect, useLocation } from 'react-router-dom'; +import { useLocation } from 'react-router-dom'; + +import { Loading } from '../../../../shared/loading'; import { SourceAdded } from './source_added'; describe('SourceAdded', () => { - const { setErrorMessage } = mockFlashMessageHelpers; - const setAddedSource = jest.fn(); + const saveSourceParams = jest.fn(); beforeEach(() => { - setMockActions({ setAddedSource }); - setMockValues({ isOrganization: true }); + setMockActions({ saveSourceParams }); }); it('renders', () => { @@ -29,26 +29,7 @@ describe('SourceAdded', () => { (useLocation as jest.Mock).mockImplementationOnce(() => ({ search })); const wrapper = shallow(); - expect(wrapper.find(Redirect)).toHaveLength(1); - expect(setAddedSource).toHaveBeenCalled(); - }); - - describe('hasError', () => { - it('passes default error to server', () => { - const search = '?name=foo&hasError=true&serviceType=custom&indexPermissions=false'; - (useLocation as jest.Mock).mockImplementationOnce(() => ({ search })); - shallow(); - - expect(setErrorMessage).toHaveBeenCalledWith('foo failed to connect.'); - }); - - it('passes custom error to server', () => { - const search = - '?name=foo&hasError=true&serviceType=custom&indexPermissions=false&errorMessages[]=custom error'; - (useLocation as jest.Mock).mockImplementationOnce(() => ({ search })); - shallow(); - - expect(setErrorMessage).toHaveBeenCalledWith('custom error'); - }); + expect(wrapper.find(Loading)).toHaveLength(1); + expect(saveSourceParams).toHaveBeenCalled(); }); }); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.tsx index 3f7d99629ca4a..2363f817e4abb 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.tsx @@ -4,52 +4,28 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useEffect } from 'react'; import { Location } from 'history'; -import { useActions, useValues } from 'kea'; -import { Redirect, useLocation } from 'react-router-dom'; +import { useActions } from 'kea'; +import { useLocation } from 'react-router-dom'; -import { i18n } from '@kbn/i18n'; +import { Loading } from '../../../../shared/loading'; -import { setErrorMessage } from '../../../../shared/flash_messages'; - -import { parseQueryParams } from '../../../../../applications/shared/query_params'; - -import { SOURCES_PATH, getSourcesPath } from '../../../routes'; - -import { AppLogic } from '../../../app_logic'; -import { SourcesLogic } from '../sources_logic'; - -interface SourceQueryParams { - name: string; - hasError: boolean; - errorMessages?: string[]; - serviceType: string; - indexPermissions: boolean; -} +import { AddSourceLogic } from './add_source/add_source_logic'; +/** + * This component merely triggers catchs the redirect from the oauth application and initializes the saving + * of the params the oauth plugin sends back. The logic file now redirects back to sources with either a + * success or error message upon completion. + */ export const SourceAdded: React.FC = () => { const { search } = useLocation() as Location; - const { name, hasError, errorMessages, serviceType, indexPermissions } = (parseQueryParams( - search - ) as unknown) as SourceQueryParams; - const { setAddedSource } = useActions(SourcesLogic); - const { isOrganization } = useValues(AppLogic); - const decodedName = decodeURIComponent(name); + const { saveSourceParams } = useActions(AddSourceLogic); - if (hasError) { - const defaultError = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.sources.sourceAdded.error', - { - defaultMessage: '{decodedName} failed to connect.', - values: { decodedName }, - } - ); - setErrorMessage(errorMessages ? errorMessages.join(' ') : defaultError); - } else { - setAddedSource(decodedName, indexPermissions, serviceType); - } + useEffect(() => { + saveSourceParams(search); + }, []); - return ; + return ; }; diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts index 48b8a06b2549c..5e106a7f42f57 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts @@ -311,3 +311,157 @@ export const SOURCE_NAME_LABEL = i18n.translate( defaultMessage: 'Source name', } ); + +export const ORG_SOURCES_LINK = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.org.link', + { + defaultMessage: 'Add an organization content source', + } +); + +export const ORG_SOURCES_HEADER_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.org.title', + { + defaultMessage: 'Organization sources', + } +); + +export const ORG_SOURCES_HEADER_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.org.description', + { + defaultMessage: + 'Organization sources are available to the entire organization and can be assigned to specific user groups.', + } +); + +export const PRIVATE_LINK_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.private.link', + { + defaultMessage: 'Add a private content source', + } +); + +export const PRIVATE_CAN_CREATE_PAGE_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.private.canCreate.title', + { + defaultMessage: 'Manage private content sources', + } +); + +export const PRIVATE_VIEW_ONLY_PAGE_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.private.vewOnly.title', + { + defaultMessage: 'Review Group Sources', + } +); + +export const PRIVATE_VIEW_ONLY_PAGE_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.private.vewOnly.description', + { + defaultMessage: 'Review the status of all sources shared with your Group.', + } +); + +export const PRIVATE_CAN_CREATE_PAGE_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.private.canCreate.description', + { + defaultMessage: + 'Review the status of all connected private sources, and manage private sources for your account.', + } +); + +export const PRIVATE_HEADER_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.private.header.title', + { + defaultMessage: 'My private content sources', + } +); + +export const PRIVATE_HEADER_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.private.header.description', + { + defaultMessage: 'Private content sources are available only to you.', + } +); + +export const PRIVATE_SHARED_SOURCES_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.private.privateShared.header.title', + { + defaultMessage: 'Shared content sources', + } +); + +export const PRIVATE_EMPTY_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.private.empty.title', + { + defaultMessage: 'You have no private sources', + } +); +export const SHARED_EMPTY_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.shared.empty.title', + { + defaultMessage: 'No content source available', + } +); + +export const SHARED_EMPTY_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.shared.empty.description', + { + defaultMessage: + 'Once content sources are shared with you, they will be displayed here, and available via the search experience.', + } +); + +export const AND = i18n.translate('xpack.enterpriseSearch.workplaceSearch.and', { + defaultMessage: 'and', +}); + +export const LICENSE_CALLOUT_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.licenseCallout.title', + { + defaultMessage: 'Private Sources are no longer available', + } +); + +export const LICENSE_CALLOUT_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.licenseCallout.description', + { + defaultMessage: 'Contact your search experience administrator for more information.', + } +); + +export const SOURCE_DISABLED_CALLOUT_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.sourceDisabled.title', + { + defaultMessage: 'Content source is disabled', + } +); + +export const SOURCE_DISABLED_CALLOUT_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.sourceDisabled.description', + { + defaultMessage: + 'Your organization’s license level has changed. Your data is safe, but document-level permissions are no longer supported and searching of this source has been disabled. Upgrade to a Platinum license to re-enable this source.', + } +); + +export const SOURCE_DISABLED_CALLOUT_BUTTON = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.sourceDisabled.button', + { + defaultMessage: 'Explore Platinum license', + } +); + +export const DOCUMENT_PERMISSIONS_LINK = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.documentPermissionsLink', + { + defaultMessage: 'Learn more about document-level permission configuration', + } +); + +export const UNDERSTAND_BUTTON = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.understandButton', + { + defaultMessage: 'I understand', + } +); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/organization_sources.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/organization_sources.tsx index fdb536dd79771..3081301fe0a9f 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/organization_sources.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/organization_sources.tsx @@ -12,6 +12,12 @@ import { Link, Redirect } from 'react-router-dom'; import { EuiButton } from '@elastic/eui'; import { ADD_SOURCE_PATH, getSourcesPath } from '../../routes'; +import { + ORG_SOURCES_LINK, + ORG_SOURCES_HEADER_TITLE, + ORG_SOURCES_HEADER_DESCRIPTION, +} from './constants'; + import { Loading } from '../../../shared/loading'; import { ContentSection } from '../../components/shared/content_section'; import { SourcesTable } from '../../components/shared/sources_table'; @@ -21,11 +27,6 @@ import { SourcesLogic } from './sources_logic'; import { SourcesView } from './sources_view'; -const ORG_LINK_TITLE = 'Add an organization content source'; -const ORG_HEADER_TITLE = 'Organization sources'; -const ORG_HEADER_DESCRIPTION = - 'Organization sources are available to the entire organization and can be assigned to specific user groups.'; - export const OrganizationSources: React.FC = () => { const { initializeSources, setSourceSearchability, resetSourcesState } = useActions(SourcesLogic); @@ -40,28 +41,22 @@ export const OrganizationSources: React.FC = () => { if (contentSources.length === 0) return ; - const linkTitle = ORG_LINK_TITLE; - const headerTitle = ORG_HEADER_TITLE; - const headerDescription = ORG_HEADER_DESCRIPTION; - const sectionTitle = ''; - const sectionDescription = ''; - return ( - {linkTitle} + {ORG_SOURCES_LINK} } - description={headerDescription} + description={ORG_SOURCES_HEADER_DESCRIPTION} alignItems="flexStart" /> - + { const { hasPlatinumLicense } = useValues(LicensingLogic); const { initializeSources, setSourceSearchability, resetSourcesState } = useActions(SourcesLogic); @@ -112,7 +119,7 @@ export const PrivateSources: React.FC = () => { - You have no private sources} /> + {PRIVATE_EMPTY_TITLE}} /> @@ -124,13 +131,8 @@ export const PrivateSources: React.FC = () => { No content source available} - body={ -

- Once content sources are shared with you, they will be displayed here, and available - via the search experience. -

- } + title={

{SHARED_EMPTY_TITLE}

} + body={

{SHARED_EMPTY_DESCRIPTION}

} />
@@ -140,16 +142,21 @@ export const PrivateSources: React.FC = () => { const hasPrivateSources = privateContentSources?.length > 0; const privateSources = hasPrivateSources ? privateSourcesTable : privateSourcesEmptyState; - const groupsSentence = `${groups.slice(0, groups.length - 1).join(', ')}, and ${groups.slice( + const groupsSentence = `${groups.slice(0, groups.length - 1).join(', ')}, ${AND} ${groups.slice( -1 )}`; const sharedSources = ( @@ -157,8 +164,8 @@ export const PrivateSources: React.FC = () => { const licenseCallout = ( <> - -

Contact your search experience administrator for more information.

+ +

{LICENSE_CALLOUT_DESCRIPTION}

diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx index f46743778a168..67995a4920925 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/source_router.tsx @@ -17,6 +17,12 @@ import { SendWorkplaceSearchTelemetry as SendTelemetry } from '../../../shared/t import { NAV } from '../../constants'; +import { + SOURCE_DISABLED_CALLOUT_TITLE, + SOURCE_DISABLED_CALLOUT_DESCRIPTION, + SOURCE_DISABLED_CALLOUT_BUTTON, +} from './constants'; + import { ENT_SEARCH_LICENSE_MANAGEMENT, REINDEX_JOB_PATH, @@ -80,14 +86,10 @@ export const SourceRouter: React.FC = () => { const callout = ( <> - -

- Your organization’s license level has changed. Your data is safe, but document-level - permissions are no longer supported and searching of this source has been disabled. - Upgrade to a Platinum license to re-enable this source. -

+ +

{SOURCE_DISABLED_CALLOUT_DESCRIPTION}

- Explore Platinum license + {SOURCE_DISABLED_CALLOUT_BUTTON}
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_view.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_view.tsx index 9e6c8f5b7319e..f8a2d345c8513 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_view.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/sources_view.tsx @@ -8,6 +8,9 @@ import React from 'react'; import { useActions, useValues } from 'kea'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + import { EuiButton, EuiLink, @@ -27,6 +30,12 @@ import { SourceIcon } from '../../components/shared/source_icon'; import { EXTERNAL_IDENTITIES_DOCS_URL, DOCUMENT_PERMISSIONS_DOCS_URL } from '../../routes'; +import { + EXTERNAL_IDENTITIES_LINK, + DOCUMENT_PERMISSIONS_LINK, + UNDERSTAND_BUTTON, +} from './constants'; + import { SourcesLogic } from './sources_logic'; interface SourcesViewProps { @@ -59,35 +68,53 @@ export const SourcesView: React.FC = ({ children }) => { - {addedSourceName} requires additional configuration + + {i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sourcesView.modal.heading', + { + defaultMessage: '{addedSourceName} requires additional configuration', + values: { addedSourceName }, + } + )} +

- {addedSourceName} has been successfully connected and initial content synchronization - is already underway. Since you have elected to synchronize document-level permission - information, you must now provide user and group mappings using the  - - External Identities API - - . + + {EXTERNAL_IDENTITIES_LINK} + + ), + }} + />

- Documents will not be searchable from Workplace Search until user and group mappings - have been configured.  - - Learn more about document-level permission configuration - - . + + {DOCUMENT_PERMISSIONS_LINK} + + ), + }} + />

- I understand + {UNDERSTAND_BUTTON} diff --git a/x-pack/plugins/enterprise_search/server/__mocks__/router.mock.ts b/x-pack/plugins/enterprise_search/server/__mocks__/router.mock.ts index c02d5cf0ff130..819cabec44f07 100644 --- a/x-pack/plugins/enterprise_search/server/__mocks__/router.mock.ts +++ b/x-pack/plugins/enterprise_search/server/__mocks__/router.mock.ts @@ -50,12 +50,7 @@ export class MockRouter { }; public callRoute = async (request: MockRouterRequest) => { - const routerCalls = this.router[this.method].mock.calls as any[]; - if (!routerCalls.length) throw new Error('No routes registered.'); - - const route = routerCalls.find(([router]: any) => router.path === this.path); - if (!route) throw new Error('No matching registered routes found - check method/path keys'); - + const route = this.findRouteRegistration(); const [, handler] = route; const context = {} as jest.Mocked; await handler(context, httpServerMock.createKibanaRequest(request as any), this.response); @@ -68,7 +63,8 @@ export class MockRouter { public validateRoute = (request: MockRouterRequest) => { if (!this.payload) throw new Error('Cannot validate wihout a payload type specified.'); - const [config] = this.router[this.method].mock.calls[0]; + const route = this.findRouteRegistration(); + const [config] = route; const validate = config.validate as RouteValidatorConfig<{}, {}, {}>; const payloadValidation = validate[this.payload] as { validate(request: KibanaRequest): void }; @@ -84,6 +80,16 @@ export class MockRouter { public shouldThrow = (request: MockRouterRequest) => { expect(() => this.validateRoute(request)).toThrow(); }; + + private findRouteRegistration = () => { + const routerCalls = this.router[this.method].mock.calls as any[]; + if (!routerCalls.length) throw new Error('No routes registered.'); + + const route = routerCalls.find(([router]: any) => router.path === this.path); + if (!route) throw new Error('No matching registered routes found - check method/path keys'); + + return route; + }; } /** diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts index a20e7854db171..c384826f469f8 100644 --- a/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/index.ts @@ -11,6 +11,7 @@ import { registerCredentialsRoutes } from './credentials'; import { registerSettingsRoutes } from './settings'; import { registerAnalyticsRoutes } from './analytics'; import { registerDocumentsRoutes, registerDocumentRoutes } from './documents'; +import { registerSearchSettingsRoutes } from './search_settings'; export const registerAppSearchRoutes = (dependencies: RouteDependencies) => { registerEnginesRoutes(dependencies); @@ -19,4 +20,5 @@ export const registerAppSearchRoutes = (dependencies: RouteDependencies) => { registerAnalyticsRoutes(dependencies); registerDocumentsRoutes(dependencies); registerDocumentRoutes(dependencies); + registerSearchSettingsRoutes(dependencies); }; diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.test.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.test.ts new file mode 100644 index 0000000000000..56a6e6297d1f8 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.test.ts @@ -0,0 +1,216 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { MockRouter, mockRequestHandler, mockDependencies } from '../../__mocks__'; + +import { registerSearchSettingsRoutes } from './search_settings'; + +describe('search settings routes', () => { + const boosts = { + types: [ + { + type: 'value', + factor: 6.2, + value: ['1313'], + }, + ], + hp: [ + { + function: 'exponential', + type: 'functional', + factor: 1, + operation: 'add', + }, + ], + }; + const resultFields = { + id: { + raw: {}, + }, + hp: { + raw: {}, + }, + name: { + raw: {}, + }, + }; + const searchFields = { + hp: { + weight: 1, + }, + name: { + weight: 1, + }, + id: { + weight: 1, + }, + }; + const searchSettings = { + boosts, + result_fields: resultFields, + search_fields: searchFields, + }; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + describe('GET /api/app_search/engines/{name}/search_settings/details', () => { + const mockRouter = new MockRouter({ + method: 'get', + path: '/api/app_search/engines/{engineName}/search_settings/details', + }); + + beforeEach(() => { + registerSearchSettingsRoutes({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request to enterprise search', () => { + mockRouter.callRoute({ + params: { engineName: 'some-engine' }, + }); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/as/engines/:engineName/search_settings/details', + }); + }); + }); + + describe('PUT /api/app_search/engines/{name}/search_settings', () => { + const mockRouter = new MockRouter({ + method: 'put', + path: '/api/app_search/engines/{engineName}/search_settings', + payload: 'body', + }); + + beforeEach(() => { + registerSearchSettingsRoutes({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request to enterprise search', () => { + mockRouter.callRoute({ + params: { engineName: 'some-engine' }, + body: searchSettings, + }); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/as/engines/:engineName/search_settings', + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { body: searchSettings }; + mockRouter.shouldValidate(request); + }); + + it('missing required fields', () => { + const request = { body: {} }; + mockRouter.shouldThrow(request); + }); + }); + }); + + describe('POST /api/app_search/engines/{name}/search_settings/reset', () => { + const mockRouter = new MockRouter({ + method: 'post', + path: '/api/app_search/engines/{engineName}/search_settings/reset', + }); + + beforeEach(() => { + registerSearchSettingsRoutes({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request to enterprise search', () => { + mockRouter.callRoute({ + params: { engineName: 'some-engine' }, + }); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/as/engines/:engineName/search_settings/reset', + }); + }); + }); + + describe('POST /api/app_search/engines/{name}/search_settings_search', () => { + const mockRouter = new MockRouter({ + method: 'post', + path: '/api/app_search/engines/{engineName}/search_settings_search', + payload: 'body', + }); + + beforeEach(() => { + registerSearchSettingsRoutes({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request to enterprise search', () => { + mockRouter.callRoute({ + params: { engineName: 'some-engine' }, + body: searchSettings, + }); + + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/as/engines/:engineName/search_settings_search', + }); + }); + + describe('validates body', () => { + it('correctly', () => { + const request = { + body: { + boosts, + search_fields: searchFields, + }, + }; + mockRouter.shouldValidate(request); + }); + + it('missing required fields', () => { + const request = { body: {} }; + mockRouter.shouldThrow(request); + }); + }); + + describe('validates query', () => { + const queryRouter = new MockRouter({ + method: 'post', + path: '/api/app_search/engines/{engineName}/search_settings_search', + payload: 'query', + }); + + it('correctly', () => { + registerSearchSettingsRoutes({ + ...mockDependencies, + router: queryRouter.router, + }); + + const request = { + query: { + query: 'foo', + }, + }; + queryRouter.shouldValidate(request); + }); + + it('missing required fields', () => { + const request = { query: {} }; + queryRouter.shouldThrow(request); + }); + }); + }); +}); diff --git a/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.ts b/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.ts new file mode 100644 index 0000000000000..eb50d736ac975 --- /dev/null +++ b/x-pack/plugins/enterprise_search/server/routes/app_search/search_settings.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 { schema } from '@kbn/config-schema'; + +import { RouteDependencies } from '../../plugin'; + +// We only do a very light type check here, and allow unknowns, because the request is validated +// on the ent-search server, so it would be redundant to check it here as well. +const boosts = schema.recordOf( + schema.string(), + schema.arrayOf(schema.object({}, { unknowns: 'allow' })) +); +const resultFields = schema.recordOf(schema.string(), schema.object({}, { unknowns: 'allow' })); +const searchFields = schema.recordOf(schema.string(), schema.object({}, { unknowns: 'allow' })); + +const searchSettingsSchema = schema.object({ + boosts, + result_fields: resultFields, + search_fields: searchFields, +}); + +export function registerSearchSettingsRoutes({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/app_search/engines/{engineName}/search_settings/details', + validate: { + params: schema.object({ + engineName: schema.string(), + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: `/as/engines/:engineName/search_settings/details`, + }) + ); + + router.post( + { + path: '/api/app_search/engines/{engineName}/search_settings/reset', + validate: { + params: schema.object({ + engineName: schema.string(), + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: `/as/engines/:engineName/search_settings/reset`, + }) + ); + + router.put( + { + path: '/api/app_search/engines/{engineName}/search_settings', + validate: { + params: schema.object({ + engineName: schema.string(), + }), + body: searchSettingsSchema, + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: `/as/engines/:engineName/search_settings`, + }) + ); + + router.post( + { + path: '/api/app_search/engines/{engineName}/search_settings_search', + validate: { + params: schema.object({ + engineName: schema.string(), + }), + body: schema.object({ + boosts, + search_fields: searchFields, + }), + query: schema.object({ + query: schema.string(), + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: `/as/engines/:engineName/search_settings_search`, + }) + ); +} diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts index 9625d20d6a3cc..9e772257141c1 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.test.ts @@ -39,6 +39,7 @@ import { registerOrgSourceReindexJobStatusRoute, registerOrgSourceOauthConfigurationsRoute, registerOrgSourceOauthConfigurationRoute, + registerOauthConnectorParamsRoute, } from './sources'; const mockConfig = { @@ -1092,6 +1093,68 @@ describe('sources routes', () => { }); }); + describe('POST /api/workplace_search/org/settings/connectors', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'post', + path: '/api/workplace_search/org/settings/connectors', + payload: 'body', + }); + + registerOrgSourceOauthConfigurationsRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/org/settings/connectors', + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { body: mockConfig }; + mockRouter.shouldValidate(request); + }); + }); + }); + + describe('PUT /api/workplace_search/org/settings/connectors', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'put', + path: '/api/workplace_search/org/settings/connectors', + payload: 'body', + }); + + registerOrgSourceOauthConfigurationsRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/org/settings/connectors', + }); + }); + + describe('validates', () => { + it('correctly', () => { + const request = { body: mockConfig }; + mockRouter.shouldValidate(request); + }); + }); + }); + describe('GET /api/workplace_search/org/settings/connectors/{serviceType}', () => { let mockRouter: MockRouter; @@ -1199,4 +1262,28 @@ describe('sources routes', () => { }); }); }); + + describe('GET /api/workplace_search/sources/create', () => { + let mockRouter: MockRouter; + + beforeEach(() => { + jest.clearAllMocks(); + mockRouter = new MockRouter({ + method: 'get', + path: '/api/workplace_search/sources/create', + payload: 'query', + }); + + registerOauthConnectorParamsRoute({ + ...mockDependencies, + router: mockRouter.router, + }); + }); + + it('creates a request handler', () => { + expect(mockRequestHandler.createRequest).toHaveBeenCalledWith({ + path: '/ws/sources/create', + }); + }); + }); }); diff --git a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts index a2f950a54471e..4446e0cb26784 100644 --- a/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts +++ b/x-pack/plugins/enterprise_search/server/routes/workplace_search/sources.ts @@ -25,14 +25,14 @@ const pageSchema = schema.object({ total_results: schema.number(), }); -const oAuthConfigSchema = schema.object({ +const oauthConfigSchema = schema.object({ base_url: schema.maybe(schema.string()), client_id: schema.maybe(schema.string()), client_secret: schema.maybe(schema.string()), service_type: schema.string(), - private_key: schema.string(), - public_key: schema.string(), - consumer_key: schema.string(), + private_key: schema.maybe(schema.string()), + public_key: schema.maybe(schema.string()), + consumer_key: schema.maybe(schema.string()), }); const displayFieldSchema = schema.object({ @@ -50,6 +50,7 @@ const displaySettingsSchema = schema.object({ detailFields: schema.oneOf([schema.arrayOf(displayFieldSchema), displayFieldSchema]), }); +// Account routes export function registerAccountSourcesRoute({ router, enterpriseSearchRequestHandler, @@ -252,6 +253,10 @@ export function registerAccountPrepareSourcesRoute({ params: schema.object({ serviceType: schema.string(), }), + query: schema.object({ + kibana_host: schema.string(), + subdomain: schema.maybe(schema.string()), + }), }, }, enterpriseSearchRequestHandler.createRequest({ @@ -390,6 +395,7 @@ export function registerAccountSourceReindexJobStatusRoute({ ); } +// Org routes export function registerOrgSourcesRoute({ router, enterpriseSearchRequestHandler, @@ -592,6 +598,11 @@ export function registerOrgPrepareSourcesRoute({ params: schema.object({ serviceType: schema.string(), }), + query: schema.object({ + kibana_host: schema.string(), + index_permissions: schema.boolean(), + subdomain: schema.maybe(schema.string()), + }), }, }, enterpriseSearchRequestHandler.createRequest({ @@ -743,6 +754,30 @@ export function registerOrgSourceOauthConfigurationsRoute({ path: '/ws/org/settings/connectors', }) ); + + router.post( + { + path: '/api/workplace_search/org/settings/connectors', + validate: { + body: oauthConfigSchema, + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/settings/connectors', + }) + ); + + router.put( + { + path: '/api/workplace_search/org/settings/connectors', + validate: { + body: oauthConfigSchema, + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/org/settings/connectors', + }) + ); } export function registerOrgSourceOauthConfigurationRoute({ @@ -770,7 +805,7 @@ export function registerOrgSourceOauthConfigurationRoute({ params: schema.object({ serviceType: schema.string(), }), - body: oAuthConfigSchema, + body: oauthConfigSchema, }, }, enterpriseSearchRequestHandler.createRequest({ @@ -785,7 +820,7 @@ export function registerOrgSourceOauthConfigurationRoute({ params: schema.object({ serviceType: schema.string(), }), - body: oAuthConfigSchema, + body: oauthConfigSchema, }, }, enterpriseSearchRequestHandler.createRequest({ @@ -808,6 +843,30 @@ export function registerOrgSourceOauthConfigurationRoute({ ); } +// Same route is used for org and account. `state` passes the context. +export function registerOauthConnectorParamsRoute({ + router, + enterpriseSearchRequestHandler, +}: RouteDependencies) { + router.get( + { + path: '/api/workplace_search/sources/create', + validate: { + query: schema.object({ + kibana_host: schema.string(), + code: schema.string(), + session_state: schema.string(), + state: schema.string(), + oauth_verifier: schema.maybe(schema.string()), + }), + }, + }, + enterpriseSearchRequestHandler.createRequest({ + path: '/ws/sources/create', + }) + ); +} + export const registerSourcesRoutes = (dependencies: RouteDependencies) => { registerAccountSourcesRoute(dependencies); registerAccountSourcesStatusRoute(dependencies); @@ -841,4 +900,5 @@ export const registerSourcesRoutes = (dependencies: RouteDependencies) => { registerOrgSourceReindexJobStatusRoute(dependencies); registerOrgSourceOauthConfigurationsRoute(dependencies); registerOrgSourceOauthConfigurationRoute(dependencies); + registerOauthConnectorParamsRoute(dependencies); }; diff --git a/x-pack/plugins/ml/common/constants/file_datavisualizer.ts b/x-pack/plugins/file_upload/common/constants.ts similarity index 100% rename from x-pack/plugins/ml/common/constants/file_datavisualizer.ts rename to x-pack/plugins/file_upload/common/constants.ts diff --git a/x-pack/plugins/infra/server/graphql/source_status/index.ts b/x-pack/plugins/file_upload/common/index.ts similarity index 80% rename from x-pack/plugins/infra/server/graphql/source_status/index.ts rename to x-pack/plugins/file_upload/common/index.ts index abc91fa3815c8..6c1725d61c059 100644 --- a/x-pack/plugins/infra/server/graphql/source_status/index.ts +++ b/x-pack/plugins/file_upload/common/index.ts @@ -4,4 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { createSourceStatusResolvers } from './resolvers'; +export * from './constants'; +export * from './types'; diff --git a/x-pack/plugins/file_upload/common/types.ts b/x-pack/plugins/file_upload/common/types.ts new file mode 100644 index 0000000000000..229983f1c535a --- /dev/null +++ b/x-pack/plugins/file_upload/common/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. + */ + +export interface ImportResponse { + success: boolean; + id: string; + index?: string; + pipelineId?: string; + docCount: number; + failures: ImportFailure[]; + error?: any; + ingestError?: boolean; +} + +export interface ImportFailure { + item: number; + reason: string; + doc: ImportDoc; +} + +export interface Doc { + message: string; +} + +export type ImportDoc = Doc | string; + +export interface Settings { + pipeline?: string; + index: string; + body: any[]; + [key: string]: any; +} + +export interface Mappings { + _meta?: { + created_by: string; + }; + properties: { + [key: string]: any; + }; +} + +export interface IngestPipelineWrapper { + id: string; + pipeline: IngestPipeline; +} + +export interface IngestPipeline { + description: string; + processors: any[]; +} diff --git a/x-pack/plugins/file_upload/jest.config.js b/x-pack/plugins/file_upload/jest.config.js new file mode 100644 index 0000000000000..6a042a4cc5c1e --- /dev/null +++ b/x-pack/plugins/file_upload/jest.config.js @@ -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. + */ + +module.exports = { + preset: '@kbn/test', + rootDir: '../../..', + roots: ['/x-pack/plugins/file_upload'], +}; diff --git a/x-pack/plugins/file_upload/kibana.json b/x-pack/plugins/file_upload/kibana.json new file mode 100644 index 0000000000000..7ca024174ec6a --- /dev/null +++ b/x-pack/plugins/file_upload/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "fileUpload", + "version": "8.0.0", + "kibanaVersion": "kibana", + "server": true, + "ui": false, + "requiredPlugins": ["usageCollection"] +} diff --git a/x-pack/plugins/file_upload/server/error_wrapper.ts b/x-pack/plugins/file_upload/server/error_wrapper.ts new file mode 100644 index 0000000000000..fb41d30e34fae --- /dev/null +++ b/x-pack/plugins/file_upload/server/error_wrapper.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 { boomify, isBoom } from '@hapi/boom'; +import { ResponseError, CustomHttpResponseOptions } from 'kibana/server'; + +export function wrapError(error: any): CustomHttpResponseOptions { + const boom = isBoom(error) + ? error + : boomify(error, { statusCode: error.status ?? error.statusCode }); + const statusCode = boom.output.statusCode; + return { + body: { + message: boom, + ...(statusCode !== 500 && error.body ? { attributes: { body: error.body } } : {}), + }, + headers: boom.output.headers as { [key: string]: string }, + statusCode, + }; +} diff --git a/x-pack/plugins/ml/server/models/file_data_visualizer/import_data.ts b/x-pack/plugins/file_upload/server/import_data.ts similarity index 95% rename from x-pack/plugins/ml/server/models/file_data_visualizer/import_data.ts rename to x-pack/plugins/file_upload/server/import_data.ts index 26dba7c2f00c1..1eb495d6570c2 100644 --- a/x-pack/plugins/ml/server/models/file_data_visualizer/import_data.ts +++ b/x-pack/plugins/file_upload/server/import_data.ts @@ -5,19 +5,20 @@ */ import { IScopedClusterClient } from 'kibana/server'; -import { INDEX_META_DATA_CREATED_BY } from '../../../common/constants/file_datavisualizer'; +import { INDEX_META_DATA_CREATED_BY } from '../common/constants'; import { ImportResponse, ImportFailure, Settings, Mappings, IngestPipelineWrapper, -} from '../../../common/types/file_datavisualizer'; -import { InputData } from './file_data_visualizer'; +} from '../common'; + +export type InputData = any[]; export function importDataProvider({ asCurrentUser }: IScopedClusterClient) { async function importData( - id: string, + id: string | undefined, index: string, settings: Settings, mappings: Mappings, @@ -77,7 +78,7 @@ export function importDataProvider({ asCurrentUser }: IScopedClusterClient) { } catch (error) { return { success: false, - id, + id: id!, index: createdIndex, pipelineId: createdPipelineId, error: error.body !== undefined ? error.body : error, diff --git a/x-pack/plugins/infra/server/graphql/sources/index.ts b/x-pack/plugins/file_upload/server/index.ts similarity index 70% rename from x-pack/plugins/infra/server/graphql/sources/index.ts rename to x-pack/plugins/file_upload/server/index.ts index ee187d8c31bec..44a208b7924bc 100644 --- a/x-pack/plugins/infra/server/graphql/sources/index.ts +++ b/x-pack/plugins/file_upload/server/index.ts @@ -4,5 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export { createSourcesResolvers } from './resolvers'; -export { sourcesSchema } from './schema.gql'; +import { FileUploadPlugin } from './plugin'; + +export const plugin = () => new FileUploadPlugin(); diff --git a/x-pack/plugins/file_upload/server/plugin.ts b/x-pack/plugins/file_upload/server/plugin.ts new file mode 100644 index 0000000000000..eea3239e52d1c --- /dev/null +++ b/x-pack/plugins/file_upload/server/plugin.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 { CoreSetup, CoreStart, Plugin } from 'src/core/server'; +import { fileUploadRoutes } from './routes'; +import { initFileUploadTelemetry } from './telemetry'; +import { UsageCollectionSetup } from '../../../../src/plugins/usage_collection/server'; + +interface SetupDeps { + usageCollection: UsageCollectionSetup; +} + +export class FileUploadPlugin implements Plugin { + async setup(coreSetup: CoreSetup, plugins: SetupDeps) { + fileUploadRoutes(coreSetup.http.createRouter()); + + initFileUploadTelemetry(coreSetup, plugins.usageCollection); + } + + start(core: CoreStart) {} +} diff --git a/x-pack/plugins/file_upload/server/routes.ts b/x-pack/plugins/file_upload/server/routes.ts new file mode 100644 index 0000000000000..c98f413caba64 --- /dev/null +++ b/x-pack/plugins/file_upload/server/routes.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 { IRouter, IScopedClusterClient } from 'kibana/server'; +import { MAX_FILE_SIZE_BYTES, IngestPipelineWrapper, Mappings, Settings } from '../common'; +import { wrapError } from './error_wrapper'; +import { InputData, importDataProvider } from './import_data'; + +import { updateTelemetry } from './telemetry'; +import { importFileBodySchema, importFileQuerySchema } from './schemas'; + +function importData( + client: IScopedClusterClient, + id: string | undefined, + index: string, + settings: Settings, + mappings: Mappings, + ingestPipeline: IngestPipelineWrapper, + data: InputData +) { + const { importData: importDataFunc } = importDataProvider(client); + return importDataFunc(id, index, settings, mappings, ingestPipeline, data); +} + +/** + * Routes for the file upload. + */ +export function fileUploadRoutes(router: IRouter) { + /** + * @apiGroup FileDataVisualizer + * + * @api {post} /api/file_upload/import Import file data + * @apiName ImportFile + * @apiDescription Imports file data into elasticsearch index. + * + * @apiSchema (query) importFileQuerySchema + * @apiSchema (body) importFileBodySchema + */ + router.post( + { + path: '/api/file_upload/import', + validate: { + query: importFileQuerySchema, + body: importFileBodySchema, + }, + options: { + body: { + accepts: ['application/json'], + maxBytes: MAX_FILE_SIZE_BYTES, + }, + tags: ['access:ml:canFindFileStructure'], + }, + }, + async (context, request, response) => { + try { + const { id } = request.query; + const { index, data, settings, mappings, ingestPipeline } = request.body; + + // `id` being `undefined` tells us that this is a new import due to create a new index. + // follow-up import calls to just add additional data will include the `id` of the created + // index, we'll ignore those and don't increment the counter. + if (id === undefined) { + await updateTelemetry(); + } + + const result = await importData( + context.core.elasticsearch.client, + id, + index, + settings, + mappings, + // @ts-expect-error + ingestPipeline, + data + ); + return response.ok({ body: result }); + } catch (e) { + return response.customError(wrapError(e)); + } + } + ); +} diff --git a/x-pack/plugins/file_upload/server/schemas.ts b/x-pack/plugins/file_upload/server/schemas.ts new file mode 100644 index 0000000000000..79db26cdb8c05 --- /dev/null +++ b/x-pack/plugins/file_upload/server/schemas.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 { schema } from '@kbn/config-schema'; + +export const importFileQuerySchema = schema.object({ + id: schema.maybe(schema.string()), +}); + +export const importFileBodySchema = schema.object({ + index: schema.string(), + data: schema.arrayOf(schema.any()), + settings: schema.maybe(schema.any()), + /** Mappings */ + mappings: schema.any(), + /** Ingest pipeline definition */ + ingestPipeline: schema.object({ + id: schema.maybe(schema.string()), + pipeline: schema.maybe(schema.any()), + }), +}); diff --git a/x-pack/plugins/ml/server/lib/telemetry/index.ts b/x-pack/plugins/file_upload/server/telemetry/index.ts similarity index 82% rename from x-pack/plugins/ml/server/lib/telemetry/index.ts rename to x-pack/plugins/file_upload/server/telemetry/index.ts index b5ec80daf1787..92d8ab425a773 100644 --- a/x-pack/plugins/ml/server/lib/telemetry/index.ts +++ b/x-pack/plugins/file_upload/server/telemetry/index.ts @@ -4,5 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { initMlTelemetry } from './ml_usage_collector'; +export { initFileUploadTelemetry } from './usage_collector'; export { updateTelemetry } from './telemetry'; diff --git a/x-pack/plugins/ml/server/lib/telemetry/internal_repository.ts b/x-pack/plugins/file_upload/server/telemetry/internal_repository.ts similarity index 100% rename from x-pack/plugins/ml/server/lib/telemetry/internal_repository.ts rename to x-pack/plugins/file_upload/server/telemetry/internal_repository.ts diff --git a/x-pack/plugins/ml/server/lib/telemetry/mappings.ts b/x-pack/plugins/file_upload/server/telemetry/mappings.ts similarity index 86% rename from x-pack/plugins/ml/server/lib/telemetry/mappings.ts rename to x-pack/plugins/file_upload/server/telemetry/mappings.ts index 5aaf9f8c79dc0..3d22bcb4162fd 100644 --- a/x-pack/plugins/ml/server/lib/telemetry/mappings.ts +++ b/x-pack/plugins/file_upload/server/telemetry/mappings.ts @@ -7,13 +7,13 @@ import { SavedObjectsType } from 'src/core/server'; import { TELEMETRY_DOC_ID } from './telemetry'; -export const mlTelemetryMappingsType: SavedObjectsType = { +export const telemetryMappingsType: SavedObjectsType = { name: TELEMETRY_DOC_ID, hidden: false, namespaceType: 'agnostic', mappings: { properties: { - file_data_visualizer: { + file_upload: { properties: { index_creation_count: { type: 'long', diff --git a/x-pack/plugins/ml/server/lib/telemetry/telemetry.test.ts b/x-pack/plugins/file_upload/server/telemetry/telemetry.test.ts similarity index 97% rename from x-pack/plugins/ml/server/lib/telemetry/telemetry.test.ts rename to x-pack/plugins/file_upload/server/telemetry/telemetry.test.ts index f41c4fda93a54..2ad36338f4928 100644 --- a/x-pack/plugins/ml/server/lib/telemetry/telemetry.test.ts +++ b/x-pack/plugins/file_upload/server/telemetry/telemetry.test.ts @@ -34,7 +34,7 @@ describe('ml plugin telemetry', () => { it('should update existing telemetry', async () => { const internalRepo = mockInit({ attributes: { - file_data_visualizer: { + file_upload: { index_creation_count: 2, }, }, diff --git a/x-pack/plugins/ml/server/lib/telemetry/telemetry.ts b/x-pack/plugins/file_upload/server/telemetry/telemetry.ts similarity index 89% rename from x-pack/plugins/ml/server/lib/telemetry/telemetry.ts rename to x-pack/plugins/file_upload/server/telemetry/telemetry.ts index 06577d6937101..aac45d5d0f871 100644 --- a/x-pack/plugins/ml/server/lib/telemetry/telemetry.ts +++ b/x-pack/plugins/file_upload/server/telemetry/telemetry.ts @@ -9,10 +9,10 @@ import { ISavedObjectsRepository } from 'kibana/server'; import { getInternalRepository } from './internal_repository'; -export const TELEMETRY_DOC_ID = 'ml-telemetry'; +export const TELEMETRY_DOC_ID = 'file-upload-usage-collection-telemetry'; export interface Telemetry { - file_data_visualizer: { + file_upload: { index_creation_count: number; }; } @@ -23,7 +23,7 @@ export interface TelemetrySavedObject { export function initTelemetry(): Telemetry { return { - file_data_visualizer: { + file_upload: { index_creation_count: 0, }, }; @@ -74,8 +74,8 @@ export async function updateTelemetry(internalRepo?: ISavedObjectsRepository) { function incrementCounts(telemetry: Telemetry) { return { - file_data_visualizer: { - index_creation_count: telemetry.file_data_visualizer.index_creation_count + 1, + file_upload: { + index_creation_count: telemetry.file_upload.index_creation_count + 1, }, }; } diff --git a/x-pack/plugins/ml/server/lib/telemetry/ml_usage_collector.ts b/x-pack/plugins/file_upload/server/telemetry/usage_collector.ts similarity index 62% rename from x-pack/plugins/ml/server/lib/telemetry/ml_usage_collector.ts rename to x-pack/plugins/file_upload/server/telemetry/usage_collector.ts index 35c6936598c40..4a1334ef53d95 100644 --- a/x-pack/plugins/ml/server/lib/telemetry/ml_usage_collector.ts +++ b/x-pack/plugins/file_upload/server/telemetry/usage_collector.ts @@ -8,23 +8,26 @@ import { CoreSetup } from 'kibana/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { getTelemetry, initTelemetry, Telemetry } from './telemetry'; -import { mlTelemetryMappingsType } from './mappings'; +import { telemetryMappingsType } from './mappings'; import { setInternalRepository } from './internal_repository'; -export function initMlTelemetry(coreSetup: CoreSetup, usageCollection: UsageCollectionSetup) { - coreSetup.savedObjects.registerType(mlTelemetryMappingsType); - registerMlUsageCollector(usageCollection); +export function initFileUploadTelemetry( + coreSetup: CoreSetup, + usageCollection: UsageCollectionSetup +) { + coreSetup.savedObjects.registerType(telemetryMappingsType); + registerUsageCollector(usageCollection); coreSetup.getStartServices().then(([core]) => { setInternalRepository(core.savedObjects.createInternalRepository); }); } -function registerMlUsageCollector(usageCollection: UsageCollectionSetup): void { - const mlUsageCollector = usageCollection.makeUsageCollector({ - type: 'mlTelemetry', +function registerUsageCollector(usageCollectionSetup: UsageCollectionSetup): void { + const usageCollector = usageCollectionSetup.makeUsageCollector({ + type: 'fileUpload', isReady: () => true, schema: { - file_data_visualizer: { + file_upload: { index_creation_count: { type: 'long' }, }, }, @@ -38,5 +41,5 @@ function registerMlUsageCollector(usageCollection: UsageCollectionSetup): void { }, }); - usageCollection.registerCollector(mlUsageCollector); + usageCollectionSetup.registerCollector(usageCollector); } diff --git a/x-pack/plugins/file_upload/tsconfig.json b/x-pack/plugins/file_upload/tsconfig.json new file mode 100644 index 0000000000000..f985a4599d5fe --- /dev/null +++ b/x-pack/plugins/file_upload/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": ["common/**/*", "public/**/*", "server/**/*"], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + { "path": "../../../src/plugins/usage_collection/tsconfig.json" } + ] +} diff --git a/x-pack/plugins/fleet/common/types/models/data_stream.ts b/x-pack/plugins/fleet/common/types/models/data_stream.ts index abc9ffcf6be6a..3bebdfcf9d997 100644 --- a/x-pack/plugins/fleet/common/types/models/data_stream.ts +++ b/x-pack/plugins/fleet/common/types/models/data_stream.ts @@ -11,7 +11,7 @@ export interface DataStream { type: string; package: string; package_version: string; - last_activity: string; + last_activity_ms: number; size_in_bytes: number; dashboards: Array<{ id: string; diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/index.tsx index c614518c1930b..23fa4025a93dc 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/index.tsx +++ b/x-pack/plugins/fleet/public/applications/fleet/sections/data_stream/list_page/index.tsx @@ -121,14 +121,14 @@ export const DataStreamListPage: React.FunctionComponent<{}> = () => { }, }, { - field: 'last_activity', + field: 'last_activity_ms', sortable: true, width: '25%', dataType: 'date', name: i18n.translate('xpack.fleet.dataStreamList.lastActivityColumnTitle', { defaultMessage: 'Last activity', }), - render: (date: DataStream['last_activity']) => { + render: (date: DataStream['last_activity_ms']) => { try { const formatter = fieldFormats.getInstance('date'); return formatter.convert(date); diff --git a/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts b/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts index 4820f25c05f96..e9487ef792b63 100644 --- a/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/data_streams/handlers.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { RequestHandler, SavedObjectsClientContract } from 'src/core/server'; +import { keyBy, keys, merge } from 'lodash'; import { DataStream } from '../../types'; import { GetDataStreamsResponse, KibanaAssetType, KibanaSavedObjectType } from '../../../common'; import { getPackageSavedObjects, getKibanaSavedObject } from '../../services/epm/packages/get'; @@ -11,150 +12,179 @@ import { defaultIngestErrorHandler } from '../../errors'; const DATA_STREAM_INDEX_PATTERN = 'logs-*-*,metrics-*-*,traces-*-*'; +interface ESDataStreamInfoResponse { + data_streams: Array<{ + name: string; + timestamp_field: { + name: string; + }; + indices: Array<{ index_name: string; index_uuid: string }>; + generation: number; + _meta?: { + package?: { + name: string; + }; + managed_by?: string; + managed?: boolean; + [key: string]: any; + }; + status: string; + template: string; + ilm_policy: string; + hidden: boolean; + }>; +} + +interface ESDataStreamStatsResponse { + data_streams: Array<{ + data_stream: string; + backing_indices: number; + store_size_bytes: number; + maximum_timestamp: number; + }>; +} + export const getListHandler: RequestHandler = async (context, request, response) => { const callCluster = context.core.elasticsearch.legacy.client.callAsCurrentUser; + const body: GetDataStreamsResponse = { + data_streams: [], + }; try { - // Get stats (size on disk) of all potentially matching indices - const { indices: indexStats } = await callCluster('indices.stats', { - index: DATA_STREAM_INDEX_PATTERN, - metric: ['store'], - }); + // Get matching data streams, their stats, and package SOs + const [ + { data_streams: dataStreamsInfo }, + { data_streams: dataStreamStats }, + packageSavedObjects, + ] = await Promise.all([ + callCluster('transport.request', { + method: 'GET', + path: `/_data_stream/${DATA_STREAM_INDEX_PATTERN}`, + }) as Promise, + callCluster('transport.request', { + method: 'GET', + path: `/_data_stream/${DATA_STREAM_INDEX_PATTERN}/_stats`, + }) as Promise, + getPackageSavedObjects(context.core.savedObjects.client), + ]); + const dataStreamsInfoByName = keyBy(dataStreamsInfo, 'name'); + const dataStreamsStatsByName = keyBy(dataStreamStats, 'data_stream'); + + // Combine data stream info + const dataStreams = merge(dataStreamsInfoByName, dataStreamsStatsByName); + const dataStreamNames = keys(dataStreams); + + // Map package SOs + const packageSavedObjectsByName = keyBy(packageSavedObjects.saved_objects, 'id'); + const packageMetadata: any = {}; + + // Query additional information for each data stream + const dataStreamPromises = dataStreamNames.map(async (dataStreamName) => { + const dataStream = dataStreams[dataStreamName]; + const dataStreamResponse: DataStream = { + index: dataStreamName, + dataset: '', + namespace: '', + type: '', + package: dataStream._meta?.package?.name || '', + package_version: '', + last_activity_ms: dataStream.maximum_timestamp, + size_in_bytes: dataStream.store_size_bytes, + dashboards: [], + }; - // Get all matching indices and info about each - // This returns the top 100,000 indices (as buckets) by last activity - const { aggregations } = await callCluster('search', { - index: DATA_STREAM_INDEX_PATTERN, - body: { - size: 0, - query: { - bool: { - must: [ - { - exists: { - field: 'data_stream.namespace', + // Query backing indices to extract data stream dataset, namespace, and type values + const { + aggregations: { dataset, namespace, type }, + } = await callCluster('search', { + index: dataStream.indices.map((index) => index.index_name), + body: { + size: 0, + query: { + bool: { + must: [ + { + exists: { + field: 'data_stream.namespace', + }, }, - }, - { - exists: { - field: 'data_stream.dataset', + { + exists: { + field: 'data_stream.dataset', + }, }, - }, - ], + ], + }, }, - }, - aggs: { - index: { - terms: { - field: '_index', - size: 100000, - order: { - last_activity: 'desc', + aggs: { + dataset: { + terms: { + field: 'data_stream.dataset', + size: 1, }, }, - aggs: { - dataset: { - terms: { - field: 'data_stream.dataset', - size: 1, - }, - }, - namespace: { - terms: { - field: 'data_stream.namespace', - size: 1, - }, + namespace: { + terms: { + field: 'data_stream.namespace', + size: 1, }, - type: { - terms: { - field: 'data_stream.type', - size: 1, - }, - }, - last_activity: { - max: { - field: '@timestamp', - }, + }, + type: { + terms: { + field: 'data_stream.type', + size: 1, }, }, }, }, - }, - }); - - const body: GetDataStreamsResponse = { - data_streams: [], - }; - - if (!(aggregations && aggregations.index && aggregations.index.buckets)) { - return response.ok({ - body, }); - } - const { - index: { buckets: indexResults }, - } = aggregations; - - const packageSavedObjects = await getPackageSavedObjects(context.core.savedObjects.client); - const packageMetadata: any = {}; - - const dataStreamsPromises = (indexResults as any[]).map(async (result) => { - const { - key: indexName, - dataset: { buckets: datasetBuckets }, - namespace: { buckets: namespaceBuckets }, - type: { buckets: typeBuckets }, - last_activity: { value_as_string: lastActivity }, - } = result; - - // We don't have a reliable way to associate index with package ID, so - // this is a hack to extract the package ID from the first part of the dataset name - // with fallback to extraction from index name - const pkg = datasetBuckets.length - ? datasetBuckets[0].key.split('.')[0] - : indexName.split('-')[1].split('.')[0]; - const pkgSavedObject = packageSavedObjects.saved_objects.filter((p) => p.id === pkg); - - // if - // - the datastream is associated with a package - // - and the package has been installed through EPM - // - and we didn't pick the metadata in an earlier iteration of this map() - if (pkg !== '' && pkgSavedObject.length > 0 && !packageMetadata[pkg]) { - // then pick the dashboards from the package saved object - const dashboards = - pkgSavedObject[0].attributes?.installed_kibana?.filter( - (o) => o.type === KibanaSavedObjectType.dashboard - ) || []; - // and then pick the human-readable titles from the dashboard saved objects - const enhancedDashboards = await getEnhancedDashboards( - context.core.savedObjects.client, - dashboards - ); - - packageMetadata[pkg] = { - version: pkgSavedObject[0].attributes?.version || '', - dashboards: enhancedDashboards, - }; + // Set values from backing indices query + dataStreamResponse.dataset = dataset.buckets[0]?.key || ''; + dataStreamResponse.namespace = namespace.buckets[0]?.key || ''; + dataStreamResponse.type = type.buckets[0]?.key || ''; + + // Find package saved object + const pkgName = dataStreamResponse.package; + const pkgSavedObject = pkgName ? packageSavedObjectsByName[pkgName] : null; + + if (pkgSavedObject) { + // if + // - the data stream is associated with a package + // - and the package has been installed through EPM + // - and we didn't pick the metadata in an earlier iteration of this map() + if (!packageMetadata[pkgName]) { + // then pick the dashboards from the package saved object + const dashboards = + pkgSavedObject.attributes?.installed_kibana?.filter( + (o) => o.type === KibanaSavedObjectType.dashboard + ) || []; + // and then pick the human-readable titles from the dashboard saved objects + const enhancedDashboards = await getEnhancedDashboards( + context.core.savedObjects.client, + dashboards + ); + + packageMetadata[pkgName] = { + version: pkgSavedObject.attributes?.version || '', + dashboards: enhancedDashboards, + }; + } + + // Set values from package information + dataStreamResponse.package = pkgName; + dataStreamResponse.package_version = packageMetadata[pkgName].version; + dataStreamResponse.dashboards = packageMetadata[pkgName].dashboards; } - return { - index: indexName, - dataset: datasetBuckets.length ? datasetBuckets[0].key : '', - namespace: namespaceBuckets.length ? namespaceBuckets[0].key : '', - type: typeBuckets.length ? typeBuckets[0].key : '', - package: pkgSavedObject.length ? pkg : '', - package_version: packageMetadata[pkg] ? packageMetadata[pkg].version : '', - last_activity: lastActivity, - size_in_bytes: indexStats[indexName] ? indexStats[indexName].total.store.size_in_bytes : 0, - dashboards: packageMetadata[pkg] ? packageMetadata[pkg].dashboards : [], - }; + return dataStreamResponse; }); - const dataStreams: DataStream[] = await Promise.all(dataStreamsPromises); - - body.data_streams = dataStreams; - + // Return final data streams objects sorted by last activity, decending + // After filtering out data streams that are missing dataset/namespace/type fields + body.data_streams = (await Promise.all(dataStreamPromises)) + .filter(({ dataset, namespace, type }) => dataset && namespace && type) + .sort((a, b) => b.last_activity_ms - a.last_activity_ms); return response.ok({ body, }); diff --git a/x-pack/plugins/fleet/server/services/agents/crud_so.ts b/x-pack/plugins/fleet/server/services/agents/crud_so.ts index 73a751157b753..f12bbd97ebc3a 100644 --- a/x-pack/plugins/fleet/server/services/agents/crud_so.ts +++ b/x-pack/plugins/fleet/server/services/agents/crud_so.ts @@ -12,18 +12,35 @@ import { AgentSOAttributes, Agent, ListWithKuery } from '../../types'; import { escapeSearchQueryPhrase, normalizeKuery, findAllSOs } from '../saved_object'; import { savedObjectToAgent } from './saved_objects'; import { appContextService } from '../../services'; +import { esKuery, KueryNode } from '../../../../../../src/plugins/data/server'; const ACTIVE_AGENT_CONDITION = `${AGENT_SAVED_OBJECT_TYPE}.attributes.active:true`; const INACTIVE_AGENT_CONDITION = `NOT (${ACTIVE_AGENT_CONDITION})`; -function _joinFilters(filters: string[], operator = 'AND') { - return filters.reduce((acc: string | undefined, filter) => { - if (acc) { - return `${acc} ${operator} (${filter})`; - } - - return `(${filter})`; - }, undefined); +function _joinFilters(filters: Array) { + return filters + .filter((filter) => filter !== undefined) + .reduce((acc: KueryNode | undefined, kuery: string | KueryNode | undefined): + | KueryNode + | undefined => { + if (kuery === undefined) { + return acc; + } + const kueryNode: KueryNode = + typeof kuery === 'string' + ? esKuery.fromKueryExpression(normalizeKuery(AGENT_SAVED_OBJECT_TYPE, kuery)) + : kuery; + + if (!acc) { + return kueryNode; + } + + return { + type: 'function', + function: 'and', + arguments: [acc, kueryNode], + }; + }, undefined as KueryNode | undefined); } export async function listAgents( @@ -46,19 +63,18 @@ export async function listAgents( showInactive = false, showUpgradeable, } = options; - const filters = []; + const filters: Array = []; if (kuery && kuery !== '') { - filters.push(normalizeKuery(AGENT_SAVED_OBJECT_TYPE, kuery)); + filters.push(kuery); } if (showInactive === false) { filters.push(ACTIVE_AGENT_CONDITION); } - let { saved_objects: agentSOs, total } = await soClient.find({ type: AGENT_SAVED_OBJECT_TYPE, - filter: _joinFilters(filters), + filter: _joinFilters(filters) || '', sortField, sortOrder, page, @@ -94,7 +110,7 @@ export async function listAllAgents( const filters = []; if (kuery && kuery !== '') { - filters.push(normalizeKuery(AGENT_SAVED_OBJECT_TYPE, kuery)); + filters.push(kuery); } if (showInactive === false) { diff --git a/x-pack/plugins/fleet/server/services/agents/status.ts b/x-pack/plugins/fleet/server/services/agents/status.ts index ba8f8fc363857..726d188f723dd 100644 --- a/x-pack/plugins/fleet/server/services/agents/status.ts +++ b/x-pack/plugins/fleet/server/services/agents/status.ts @@ -11,6 +11,8 @@ import { AGENT_EVENT_SAVED_OBJECT_TYPE, AGENT_SAVED_OBJECT_TYPE } from '../../co import { AgentStatus } from '../../types'; import { AgentStatusKueryHelper } from '../../../common/services'; +import { esKuery, KueryNode } from '../../../../../../src/plugins/data/server'; +import { normalizeKuery } from '../saved_object'; export async function getAgentStatusById( soClient: SavedObjectsClientContract, @@ -26,13 +28,24 @@ export const getAgentStatus = AgentStatusKueryHelper.getAgentStatus; function joinKuerys(...kuerys: Array) { return kuerys .filter((kuery) => kuery !== undefined) - .reduce((acc, kuery) => { - if (acc === '') { - return `(${kuery})`; + .reduce((acc: KueryNode | undefined, kuery: string | undefined): KueryNode | undefined => { + if (kuery === undefined) { + return acc; } + const normalizedKuery: KueryNode = esKuery.fromKueryExpression( + normalizeKuery(AGENT_SAVED_OBJECT_TYPE, kuery || '') + ); - return `${acc} and (${kuery})`; - }, ''); + if (!acc) { + return normalizedKuery; + } + + return { + type: 'function', + function: 'and', + arguments: [acc, normalizedKuery], + }; + }, undefined as KueryNode | undefined); } export async function getAgentStatusForAgentPolicy( @@ -58,6 +71,7 @@ export async function getAgentStatusForAgentPolicy( ...[ kuery, filterKuery, + `${AGENT_SAVED_OBJECT_TYPE}.attributes.active:true`, agentPolicyId ? `${AGENT_SAVED_OBJECT_TYPE}.policy_id:"${agentPolicyId}"` : undefined, ] ), diff --git a/x-pack/plugins/fleet/server/types/rest_spec/common.ts b/x-pack/plugins/fleet/server/types/rest_spec/common.ts index cdb23da5b6b11..88d60116672de 100644 --- a/x-pack/plugins/fleet/server/types/rest_spec/common.ts +++ b/x-pack/plugins/fleet/server/types/rest_spec/common.ts @@ -11,7 +11,12 @@ export const ListWithKuerySchema = schema.object({ sortField: schema.maybe(schema.string()), sortOrder: schema.maybe(schema.oneOf([schema.literal('desc'), schema.literal('asc')])), showUpgradeable: schema.maybe(schema.boolean()), - kuery: schema.maybe(schema.string()), + kuery: schema.maybe( + schema.oneOf([ + schema.string(), + schema.any(), // KueryNode + ]) + ), }); export type ListWithKuery = TypeOf; diff --git a/x-pack/plugins/graph/public/services/persistence/deserialize.test.ts b/x-pack/plugins/graph/public/services/persistence/deserialize.test.ts index 815f5ad3ecafe..5ac51227cacb8 100644 --- a/x-pack/plugins/graph/public/services/persistence/deserialize.test.ts +++ b/x-pack/plugins/graph/public/services/persistence/deserialize.test.ts @@ -5,7 +5,7 @@ */ import { GraphWorkspaceSavedObject, IndexPatternSavedObject, Workspace } from '../../types'; -import { migrateLegacyIndexPatternRef, savedWorkspaceToAppState } from './deserialize'; +import { migrateLegacyIndexPatternRef, savedWorkspaceToAppState, mapFields } from './deserialize'; import { createWorkspace } from '../../angular/graph_client_workspace'; import { outlinkEncoders } from '../../helpers/outlink_encoders'; import { IndexPattern } from '../../../../../../src/plugins/data/public'; @@ -119,9 +119,9 @@ describe('deserialize', () => { savedWorkspace, { getNonScriptedFields: () => [ - { name: 'field1', type: 'string', aggregatable: true }, - { name: 'field2', type: 'string', aggregatable: true }, - { name: 'field3', type: 'string', aggregatable: true }, + { name: 'field1', type: 'string', aggregatable: true, isMapped: true }, + { name: 'field2', type: 'string', aggregatable: true, isMapped: true }, + { name: 'field3', type: 'string', aggregatable: true, isMapped: true }, ], } as IndexPattern, workspace @@ -236,4 +236,22 @@ describe('deserialize', () => { expect(workspacePayload).toEqual(savedWorkspace); }); }); + + describe('mapFields', () => { + it('should not include unmapped fields', () => { + const indexPattern = { + getNonScriptedFields: () => [ + { name: 'field1', type: 'string', aggregatable: true, isMapped: true }, + { name: 'field2', type: 'string', aggregatable: true, isMapped: true }, + { name: 'runtimeField', type: 'string', aggregatable: true, isMapped: false }, + { name: 'field3', type: 'string', aggregatable: true, isMapped: true }, + ], + } as IndexPattern; + expect(mapFields(indexPattern).map(({ name }) => name)).toEqual([ + 'field1', + 'field2', + 'field3', + ]); + }); + }); }); diff --git a/x-pack/plugins/graph/public/services/persistence/deserialize.ts b/x-pack/plugins/graph/public/services/persistence/deserialize.ts index a25e4b7e5e3dc..61bba7efe5ef2 100644 --- a/x-pack/plugins/graph/public/services/persistence/deserialize.ts +++ b/x-pack/plugins/graph/public/services/persistence/deserialize.ts @@ -104,7 +104,11 @@ export function mapFields(indexPattern: IndexPattern): WorkspaceField[] { return indexPattern .getNonScriptedFields() .filter( - (field) => !blockedFieldNames.includes(field.name) && !indexPatternsUtils.isNestedField(field) + (field) => + // Make sure to only include mapped fields, e.g. no index pattern runtime fields + field.isMapped && + !blockedFieldNames.includes(field.name) && + !indexPatternsUtils.isNestedField(field) ) .map((field, index) => ({ name: field.name, diff --git a/x-pack/plugins/graph/public/state_management/datasource.test.ts b/x-pack/plugins/graph/public/state_management/datasource.test.ts index 13b7080d776a2..4dc4dd3059b51 100644 --- a/x-pack/plugins/graph/public/state_management/datasource.test.ts +++ b/x-pack/plugins/graph/public/state_management/datasource.test.ts @@ -25,7 +25,7 @@ describe('datasource saga', () => { get: jest.fn(() => Promise.resolve({ title: 'test-pattern', - getNonScriptedFields: () => [{ name: 'field1', type: 'string' }], + getNonScriptedFields: () => [{ name: 'field1', type: 'string', isMapped: true }], } as IndexPattern) ), }, diff --git a/x-pack/plugins/grokdebugger/tsconfig.json b/x-pack/plugins/grokdebugger/tsconfig.json new file mode 100644 index 0000000000000..34cf8d74c0024 --- /dev/null +++ b/x-pack/plugins/grokdebugger/tsconfig.json @@ -0,0 +1,24 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "common/**/*", + "public/**/*", + "server/**/*", + "../../typings/**/*", + ], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + { "path": "../../../src/plugins/dev_tools/tsconfig.json"}, + { "path": "../../../src/plugins/home/tsconfig.json"}, + { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, + { "path": "../../../src/plugins/es_ui_shared/tsconfig.json" }, + { "path": "../licensing/tsconfig.json" } + ] +} diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx index d9256ec916ec8..5d5d8a85163c8 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx @@ -197,7 +197,9 @@ export const setup = async (arg?: { appServicesContext: Partial async (value: string) => { - await createFormToggleAction(`${phase}-setReplicasSwitch`)(true); + if (!exists(`${phase}-selectedReplicaCount`)) { + await createFormToggleAction(`${phase}-setReplicasSwitch`)(true); + } await createFormSetValueAction(`${phase}-selectedReplicaCount`)(value); }; @@ -248,8 +250,11 @@ export const setup = async (arg?: { appServicesContext: Partial exists('policyFormErrorsCallout'), timeline: { hasRolloverIndicator: () => exists('timelineHotPhaseRolloverToolTip'), hasHotPhase: () => exists('ilmTimelineHotPhase'), @@ -263,6 +268,7 @@ export const setup = async (arg?: { appServicesContext: Partial exists('phaseErrorIndicator-hot'), ...createForceMergeActions('hot'), ...createIndexPriorityActions('hot'), ...createShrinkActions('hot'), @@ -276,6 +282,7 @@ export const setup = async (arg?: { appServicesContext: Partial exists('phaseErrorIndicator-warm'), ...createShrinkActions('warm'), ...createForceMergeActions('warm'), setReadonly: setReadonly('warm'), @@ -290,6 +297,7 @@ export const setup = async (arg?: { appServicesContext: Partial exists('phaseErrorIndicator-cold'), ...createIndexPriorityActions('cold'), ...createSearchableSnapshotActions('cold'), }, diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts index 05793a4bed581..9cff3953c2e1b 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.test.ts @@ -29,6 +29,7 @@ window.scrollTo = jest.fn(); describe('', () => { let testBed: EditPolicyTestBed; const { server, httpRequestsMockHelpers } = setupEnvironment(); + afterAll(() => { server.restore(); }); @@ -852,4 +853,132 @@ describe('', () => { expect(actions.timeline.hasRolloverIndicator()).toBe(false); }); }); + + describe('policy error notifications', () => { + beforeAll(() => { + jest.useFakeTimers(); + }); + + afterAll(() => { + jest.useRealTimers(); + }); + beforeEach(async () => { + httpRequestsMockHelpers.setLoadPolicies([getDefaultHotPhasePolicy('my_policy')]); + httpRequestsMockHelpers.setListNodes({ + nodesByRoles: {}, + nodesByAttributes: { test: ['123'] }, + isUsingDeprecatedDataRoleConfig: false, + }); + httpRequestsMockHelpers.setLoadSnapshotPolicies([]); + + await act(async () => { + testBed = await setup(); + }); + + const { component } = testBed; + component.update(); + }); + + // For new we rely on a setTimeout to ensure that error messages have time to populate + // the form object before we look at the form object. See: + // x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/form_errors_context.tsx + // for where this logic lives. + const runTimers = () => { + const { component } = testBed; + act(() => { + jest.runAllTimers(); + }); + component.update(); + }; + + test('shows phase error indicators correctly', async () => { + // This test simulates a user configuring a policy phase by phase. The flow is the following: + // 0. Start with policy with no validation issues present + // 1. Configure hot, introducing a validation error + // 2. Configure warm, introducing a validation error + // 3. Configure cold, introducing a validation error + // 4. Fix validation error in hot + // 5. Fix validation error in warm + // 6. Fix validation error in cold + // We assert against each of these progressive states. + + const { actions } = testBed; + + // 0. No validation issues + expect(actions.hasGlobalErrorCallout()).toBe(false); + expect(actions.hot.hasErrorIndicator()).toBe(false); + expect(actions.warm.hasErrorIndicator()).toBe(false); + expect(actions.cold.hasErrorIndicator()).toBe(false); + + // 1. Hot phase validation issue + await actions.hot.toggleForceMerge(true); + await actions.hot.setForcemergeSegmentsCount('-22'); + runTimers(); + expect(actions.hasGlobalErrorCallout()).toBe(true); + expect(actions.hot.hasErrorIndicator()).toBe(true); + expect(actions.warm.hasErrorIndicator()).toBe(false); + expect(actions.cold.hasErrorIndicator()).toBe(false); + + // 2. Warm phase validation issue + await actions.warm.enable(true); + await actions.warm.toggleForceMerge(true); + await actions.warm.setForcemergeSegmentsCount('-22'); + await runTimers(); + expect(actions.hasGlobalErrorCallout()).toBe(true); + expect(actions.hot.hasErrorIndicator()).toBe(true); + expect(actions.warm.hasErrorIndicator()).toBe(true); + expect(actions.cold.hasErrorIndicator()).toBe(false); + + // 3. Cold phase validation issue + await actions.cold.enable(true); + await actions.cold.setReplicas('-33'); + await runTimers(); + expect(actions.hasGlobalErrorCallout()).toBe(true); + expect(actions.hot.hasErrorIndicator()).toBe(true); + expect(actions.warm.hasErrorIndicator()).toBe(true); + expect(actions.cold.hasErrorIndicator()).toBe(true); + + // 4. Fix validation issue in hot + await actions.hot.setForcemergeSegmentsCount('1'); + await runTimers(); + expect(actions.hasGlobalErrorCallout()).toBe(true); + expect(actions.hot.hasErrorIndicator()).toBe(false); + expect(actions.warm.hasErrorIndicator()).toBe(true); + expect(actions.cold.hasErrorIndicator()).toBe(true); + + // 5. Fix validation issue in warm + await actions.warm.setForcemergeSegmentsCount('1'); + await runTimers(); + expect(actions.hasGlobalErrorCallout()).toBe(true); + expect(actions.hot.hasErrorIndicator()).toBe(false); + expect(actions.warm.hasErrorIndicator()).toBe(false); + expect(actions.cold.hasErrorIndicator()).toBe(true); + + // 6. Fix validation issue in cold + await actions.cold.setReplicas('1'); + await runTimers(); + expect(actions.hasGlobalErrorCallout()).toBe(false); + expect(actions.hot.hasErrorIndicator()).toBe(false); + expect(actions.warm.hasErrorIndicator()).toBe(false); + expect(actions.cold.hasErrorIndicator()).toBe(false); + }); + + test('global error callout should show if there are any form errors', async () => { + const { actions } = testBed; + + expect(actions.hasGlobalErrorCallout()).toBe(false); + expect(actions.hot.hasErrorIndicator()).toBe(false); + expect(actions.warm.hasErrorIndicator()).toBe(false); + expect(actions.cold.hasErrorIndicator()).toBe(false); + + await actions.saveAsNewPolicy(true); + await actions.setPolicyName(''); + await runTimers(); + + expect(actions.hasGlobalErrorCallout()).toBe(true); + expect(actions.hot.hasErrorIndicator()).toBe(false); + expect(actions.warm.hasErrorIndicator()).toBe(false); + expect(actions.cold.hasErrorIndicator()).toBe(false); + }); + }); }); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/described_form_row/toggle_field_with_described_form_row.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/described_form_row/toggle_field_with_described_form_row.tsx index 779dbe47914a1..cddcd92a0f72a 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/described_form_row/toggle_field_with_described_form_row.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/described_form_row/toggle_field_with_described_form_row.tsx @@ -5,7 +5,7 @@ */ import React, { FunctionComponent } from 'react'; -import { UseField } from '../../../../../shared_imports'; +import { UseField } from '../../form'; import { DescribedFormRow, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/form_errors.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/form_errors.tsx deleted file mode 100644 index ed7ca60417679..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/form_errors.tsx +++ /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 React, { cloneElement, Children, Fragment, ReactElement } from 'react'; -import { EuiFormRow, EuiFormRowProps } from '@elastic/eui'; - -type Props = EuiFormRowProps & { - isShowingErrors: boolean; - errors?: string | string[] | null; -}; - -export const ErrableFormRow: React.FunctionComponent = ({ - isShowingErrors, - errors, - children, - ...rest -}) => { - const _errors = errors ? (Array.isArray(errors) ? errors : [errors]) : undefined; - return ( - 0)} - error={errors} - {...rest} - > - - {Children.map(children, (child) => - cloneElement(child as ReactElement, { - isInvalid: errors && isShowingErrors && errors.length > 0, - }) - )} - - - ); -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/form_errors_callout.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/form_errors_callout.tsx new file mode 100644 index 0000000000000..4e4adc6530f3d --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/form_errors_callout.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, { FunctionComponent } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiCallOut, EuiSpacer } from '@elastic/eui'; + +import { useFormErrorsContext } from '../form'; + +const i18nTexts = { + callout: { + title: i18n.translate('xpack.indexLifecycleMgmt.policyErrorCalloutTitle', { + defaultMessage: 'This policy contains errors', + }), + body: i18n.translate('xpack.indexLifecycleMgmt.policyErrorCalloutDescription', { + defaultMessage: 'Please fix all errors before saving the policy.', + }), + }, +}; + +export const FormErrorsCallout: FunctionComponent = () => { + const { + errors: { hasErrors }, + } = useFormErrorsContext(); + + if (!hasErrors) { + return null; + } + + return ( + <> + + {i18nTexts.callout.body} + + + + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts index 960b632d70bd4..c384ef7531bb0 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts @@ -5,7 +5,6 @@ */ export { ActiveBadge } from './active_badge'; -export { ErrableFormRow } from './form_errors'; export { LearnMoreLink } from './learn_more_link'; export { OptionalLabel } from './optional_label'; export { PolicyJsonFlyout } from './policy_json_flyout'; @@ -13,5 +12,6 @@ export { DescribedFormRow, ToggleFieldWithDescribedFormRow } from './described_f export { FieldLoadingError } from './field_loading_error'; export { ActiveHighlight } from './active_highlight'; export { Timeline } from './timeline'; +export { FormErrorsCallout } from './form_errors_callout'; export * from './phases'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx index 976f584ef4d3a..405cdd5bcde7b 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx @@ -23,6 +23,7 @@ import { IndexPriorityField, ReplicasField, } from '../shared_fields'; + import { Phase } from '../phase'; const i18nTexts = { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/delete_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/delete_phase.tsx index 5c43bb413eb5e..a3196ddcf0234 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/delete_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/delete_phase.tsx @@ -9,7 +9,9 @@ import { get } from 'lodash'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiDescribedFormGroup, EuiTextColor, EuiFormRow } from '@elastic/eui'; -import { useFormData, UseField, ToggleField } from '../../../../../../shared_imports'; +import { useFormData, ToggleField } from '../../../../../../shared_imports'; + +import { UseField } from '../../../form'; import { ActiveBadge, LearnMoreLink, OptionalLabel } from '../../index'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/hot_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/hot_phase.tsx index 02de47f8c56ef..70740ddb81f8e 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/hot_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/hot_phase/hot_phase.tsx @@ -19,11 +19,11 @@ import { EuiIcon, } from '@elastic/eui'; -import { useFormData, UseField, SelectField, NumericField } from '../../../../../../shared_imports'; +import { useFormData, SelectField, NumericField } from '../../../../../../shared_imports'; import { i18nTexts } from '../../../i18n_texts'; -import { ROLLOVER_EMPTY_VALIDATION, useConfigurationIssues } from '../../../form'; +import { ROLLOVER_EMPTY_VALIDATION, useConfigurationIssues, UseField } from '../../../form'; import { useEditPolicyContext } from '../../../edit_policy_context'; @@ -38,8 +38,8 @@ import { ReadonlyField, ShrinkField, } from '../shared_fields'; - import { Phase } from '../phase'; + import { maxSizeStoredUnits, maxAgeUnits } from './constants'; export const HotPhase: FunctionComponent = () => { diff --git a/x-pack/plugins/infra/common/graphql/root/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/phase/index.ts similarity index 84% rename from x-pack/plugins/infra/common/graphql/root/index.ts rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/phase/index.ts index 47417b6376307..d8c6fec557dce 100644 --- a/x-pack/plugins/infra/common/graphql/root/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/phase/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { rootSchema } from './schema.gql'; +export { Phase } from './phase'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/phase/phase.tsx similarity index 79% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/phase.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/phase/phase.tsx index 6de18f1c1d3cb..829c75bdced64 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/phase/phase.tsx @@ -18,11 +18,14 @@ import { import { get } from 'lodash'; import { FormattedMessage } from '@kbn/i18n/react'; -import { ToggleField, UseField, useFormData } from '../../../../../shared_imports'; -import { i18nTexts } from '../../i18n_texts'; +import { ToggleField, useFormData } from '../../../../../../shared_imports'; +import { i18nTexts } from '../../../i18n_texts'; -import { ActiveHighlight } from '../active_highlight'; -import { MinAgeField } from './shared_fields'; +import { UseField } from '../../../form'; +import { ActiveHighlight } from '../../active_highlight'; +import { MinAgeField } from '../shared_fields'; + +import { PhaseErrorIndicator } from './phase_error_indicator'; interface Props { phase: 'hot' | 'warm' | 'cold'; @@ -63,9 +66,16 @@ export const Phase: FunctionComponent = ({ children, phase }) => { )} - -

{i18nTexts.editPolicy.titles[phase]}

-
+ + + +

{i18nTexts.editPolicy.titles[phase]}

+
+
+ + + +
@@ -74,7 +84,7 @@ export const Phase: FunctionComponent = ({ children, phase }) => { @@ -102,7 +112,7 @@ export const Phase: FunctionComponent = ({ children, phase }) => { )} - + {i18nTexts.editPolicy.descriptions[phase]} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/phase/phase_error_indicator.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/phase/phase_error_indicator.tsx new file mode 100644 index 0000000000000..f156ddcaee962 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/phase/phase_error_indicator.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 { i18n } from '@kbn/i18n'; +import React, { FunctionComponent, memo } from 'react'; +import { EuiIconTip } from '@elastic/eui'; + +import { useFormErrorsContext } from '../../../form'; + +interface Props { + phase: 'hot' | 'warm' | 'cold'; +} + +const i18nTexts = { + toolTipContent: i18n.translate('xpack.indexLifecycleMgmt.phaseErrorIcon.tooltipDescription', { + defaultMessage: 'This phase contains errors.', + }), +}; + +/** + * This component hooks into the form state and updates whenever new form data is inputted. + */ +export const PhaseErrorIndicator: FunctionComponent = memo(({ phase }) => { + const { errors } = useFormErrorsContext(); + + if (Object.keys(errors[phase]).length) { + return ( +
+ +
+ ); + } + + return null; +}); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/data_tier_allocation.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/data_tier_allocation.tsx index 8af5314c16b1e..3dc0d6d45a9bd 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/data_tier_allocation.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/data_tier_allocation.tsx @@ -4,16 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ +import { i18n } from '@kbn/i18n'; import React, { FunctionComponent } from 'react'; import { get } from 'lodash'; -import { i18n } from '@kbn/i18n'; import { EuiText, EuiSpacer, EuiSuperSelectOption } from '@elastic/eui'; -import { UseField, SuperSelectField, useFormData } from '../../../../../../../../shared_imports'; +import { SuperSelectField, useFormData } from '../../../../../../../../shared_imports'; import { PhaseWithAllocation } from '../../../../../../../../../common/types'; import { DataTierAllocationType } from '../../../../../types'; +import { UseField } from '../../../../../form'; + import { NodeAllocation } from './node_allocation'; import { SharedProps } from './types'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/node_allocation.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/node_allocation.tsx index 9f60337166f49..371cb95f80915 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/node_allocation.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/data_tier_allocation_field/components/node_allocation.tsx @@ -4,13 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ +import { i18n } from '@kbn/i18n'; import React, { useState, FunctionComponent } from 'react'; import { get } from 'lodash'; import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; import { EuiButtonEmpty, EuiText, EuiSpacer } from '@elastic/eui'; -import { UseField, SelectField, useFormData } from '../../../../../../../../shared_imports'; +import { SelectField, useFormData } from '../../../../../../../../shared_imports'; + +import { UseField } from '../../../../../form'; import { LearnMoreLink } from '../../../../learn_more_link'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/forcemerge_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/forcemerge_field.tsx index 8d6807c90dae8..cd6e9f83eb13f 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/forcemerge_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/forcemerge_field.tsx @@ -7,12 +7,14 @@ import React, { useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { UseField, CheckBoxField, NumericField } from '../../../../../../shared_imports'; +import { CheckBoxField, NumericField } from '../../../../../../shared_imports'; import { i18nTexts } from '../../../i18n_texts'; import { useEditPolicyContext } from '../../../edit_policy_context'; +import { UseField } from '../../../form'; + import { LearnMoreLink, DescribedFormRow } from '../../'; interface Props { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/index_priority_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/index_priority_field.tsx index 570033812c247..79b4b49cbb653 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/index_priority_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/index_priority_field.tsx @@ -4,14 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ +import { i18n } from '@kbn/i18n'; import React, { FunctionComponent, useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; import { EuiSpacer, EuiTextColor } from '@elastic/eui'; -import { UseField, NumericField } from '../../../../../../shared_imports'; -import { LearnMoreLink, DescribedFormRow } from '../..'; +import { NumericField } from '../../../../../../shared_imports'; + import { useEditPolicyContext } from '../../../edit_policy_context'; +import { UseField } from '../../../form'; + +import { LearnMoreLink, DescribedFormRow } from '../..'; interface Props { phase: 'hot' | 'warm' | 'cold'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_field/min_age_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_field/min_age_field.tsx index 8a84b7fa0e762..9937ae2a0b5b3 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_field/min_age_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_field/min_age_field.tsx @@ -3,8 +3,8 @@ * 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, { FunctionComponent } from 'react'; import { i18n } from '@kbn/i18n'; +import React, { FunctionComponent } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { @@ -17,7 +17,9 @@ import { EuiText, } from '@elastic/eui'; -import { UseField, getFieldValidityAndErrorMessage } from '../../../../../../../shared_imports'; +import { getFieldValidityAndErrorMessage } from '../../../../../../../shared_imports'; + +import { UseField } from '../../../../form'; import { getUnitsAriaLabelForPhase, getTimingLabelForPhase } from './util'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/replicas_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/replicas_field.tsx index 6d8e019ff8a0c..189fdc2fdf6d3 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/replicas_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/replicas_field.tsx @@ -7,8 +7,11 @@ import React, { FunctionComponent } from 'react'; import { i18n } from '@kbn/i18n'; -import { UseField, NumericField } from '../../../../../../shared_imports'; +import { NumericField } from '../../../../../../shared_imports'; + import { useEditPolicyContext } from '../../../edit_policy_context'; +import { UseField } from '../../../form'; + import { DescribedFormRow } from '../../described_form_row'; interface Props { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/searchable_snapshot_field/searchable_snapshot_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/searchable_snapshot_field/searchable_snapshot_field.tsx index 5fa192158fb3b..0050fe1d87e91 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/searchable_snapshot_field/searchable_snapshot_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/searchable_snapshot_field/searchable_snapshot_field.tsx @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { get } from 'lodash'; import { i18n } from '@kbn/i18n'; +import { get } from 'lodash'; import React, { FunctionComponent, useState, useEffect } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { @@ -17,7 +17,6 @@ import { } from '@elastic/eui'; import { - UseField, ComboBoxField, useKibana, fieldValidators, @@ -25,7 +24,7 @@ import { } from '../../../../../../../shared_imports'; import { useEditPolicyContext } from '../../../../edit_policy_context'; -import { useConfigurationIssues } from '../../../../form'; +import { useConfigurationIssues, UseField } from '../../../../form'; import { i18nTexts } from '../../../../i18n_texts'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/shrink_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/shrink_field.tsx index da200e9e68d17..33f6fc83b1840 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/shrink_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/shrink_field.tsx @@ -7,9 +7,10 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTextColor } from '@elastic/eui'; import React, { FunctionComponent } from 'react'; -import { UseField, NumericField } from '../../../../../../shared_imports'; +import { NumericField } from '../../../../../../shared_imports'; import { useEditPolicyContext } from '../../../edit_policy_context'; +import { UseField } from '../../../form'; import { i18nTexts } from '../../../i18n_texts'; import { LearnMoreLink, DescribedFormRow } from '../../'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/snapshot_policies_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/snapshot_policies_field.tsx index 05f12f6ba61cb..ece362e5ae01b 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/snapshot_policies_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/snapshot_policies_field.tsx @@ -11,9 +11,11 @@ import { i18n } from '@kbn/i18n'; import { EuiCallOut, EuiComboBoxOptionOption, EuiLink, EuiSpacer } from '@elastic/eui'; -import { UseField, ComboBoxField, useFormData } from '../../../../../../shared_imports'; +import { ComboBoxField, useFormData } from '../../../../../../shared_imports'; import { useLoadSnapshotPolicies } from '../../../../../services/api'; + import { useEditPolicyContext } from '../../../edit_policy_context'; +import { UseField } from '../../../form'; import { FieldLoadingError } from '../../'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx index b1cf41773de3c..420abdf020138 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx @@ -4,15 +4,13 @@ * 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, useMemo, useState } from 'react'; import { get } from 'lodash'; import { RouteComponentProps } from 'react-router-dom'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { i18n } from '@kbn/i18n'; - import { EuiButton, EuiButtonEmpty, @@ -31,9 +29,12 @@ import { EuiTitle, } from '@elastic/eui'; -import { TextField, UseField, useForm, useFormData } from '../../../shared_imports'; +import { TextField, useForm, useFormData } from '../../../shared_imports'; import { toasts } from '../../services/notification'; +import { createDocLink } from '../../services/documentation'; + +import { UseField } from './form'; import { savePolicy } from './save_policy'; @@ -44,6 +45,7 @@ import { PolicyJsonFlyout, WarmPhase, Timeline, + FormErrorsCallout, } from './components'; import { createPolicyNameValidations, createSerializer, deserializer, Form, schema } from './form'; @@ -51,7 +53,6 @@ import { createPolicyNameValidations, createSerializer, deserializer, Form, sche import { useEditPolicyContext } from './edit_policy_context'; import { FormInternal } from './types'; -import { createDocLink } from '../../services/documentation'; export interface Props { history: RouteComponentProps['history']; @@ -253,6 +254,8 @@ export const EditPolicy: React.FunctionComponent = ({ history }) => { + + diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/components/enhanced_use_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/components/enhanced_use_field.tsx new file mode 100644 index 0000000000000..332a8c2ba369e --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/components/enhanced_use_field.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 React, { useEffect, useRef, useMemo, useCallback } from 'react'; + +// We wrap this component for edit policy so we do not export it from the "shared_imports" dir to avoid +// accidentally using the non-enhanced version. +import { UseField } from '../../../../../../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; + +import { Phases } from '../../../../../../common/types'; + +import { UseFieldProps, FormData } from '../../../../../shared_imports'; + +import { useFormErrorsContext } from '../form_errors_context'; + +const isXPhaseField = (phase: keyof Phases) => (fieldPath: string): boolean => + fieldPath.startsWith(`phases.${phase}`) || fieldPath.startsWith(`_meta.${phase}`); + +const isHotPhaseField = isXPhaseField('hot'); +const isWarmPhaseField = isXPhaseField('warm'); +const isColdPhaseField = isXPhaseField('cold'); +const isDeletePhaseField = isXPhaseField('delete'); + +const determineFieldPhase = (fieldPath: string): keyof Phases | 'other' => { + if (isHotPhaseField(fieldPath)) { + return 'hot'; + } + if (isWarmPhaseField(fieldPath)) { + return 'warm'; + } + if (isColdPhaseField(fieldPath)) { + return 'cold'; + } + if (isDeletePhaseField(fieldPath)) { + return 'delete'; + } + return 'other'; +}; + +export const EnhancedUseField = ( + props: UseFieldProps +): React.ReactElement | null => { + const { path } = props; + const isMounted = useRef(false); + const phase = useMemo(() => determineFieldPhase(path), [path]); + const { addError, clearError } = useFormErrorsContext(); + + const onError = useCallback( + (errors: string[] | null) => { + if (!isMounted.current) { + return; + } + if (errors) { + addError(phase, path, errors); + } else { + clearError(phase, path); + } + }, + [phase, path, addError, clearError] + ); + + useEffect(() => { + isMounted.current = true; + return () => { + isMounted.current = false; + }; + }, []); + + return ; +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/components/form.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/components/form.tsx index 2b3411e394a90..cad029478c49a 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/components/form.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/components/form.tsx @@ -9,6 +9,7 @@ import React, { FunctionComponent } from 'react'; import { Form as LibForm, FormHook } from '../../../../../shared_imports'; import { ConfigurationIssuesProvider } from '../configuration_issues_context'; +import { FormErrorsProvider } from '../form_errors_context'; interface Props { form: FormHook; @@ -16,6 +17,8 @@ interface Props { export const Form: FunctionComponent = ({ form, children }) => ( - {children} + + {children} + ); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/components/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/components/index.ts index 15d8d4ed272e5..06cfa5daf599e 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/components/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/components/index.ts @@ -5,3 +5,5 @@ */ export { Form } from './form'; + +export { EnhancedUseField } from './enhanced_use_field'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/form_errors_context.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/form_errors_context.tsx new file mode 100644 index 0000000000000..e4c01e35476fc --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/form_errors_context.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, { createContext, useContext, FunctionComponent, useState, useCallback } from 'react'; + +import { Phases as _Phases } from '../../../../../common/types'; + +import { useFormContext } from '../../../../shared_imports'; + +import { FormInternal } from '../types'; + +type Phases = keyof _Phases; + +type PhasesAndOther = Phases | 'other'; + +interface ErrorGroup { + [fieldPath: string]: string[]; +} + +interface Errors { + hasErrors: boolean; + hot: ErrorGroup; + warm: ErrorGroup; + cold: ErrorGroup; + delete: ErrorGroup; + /** + * Errors that are not specific to a phase should go here. + */ + other: ErrorGroup; +} + +interface ContextValue { + errors: Errors; + addError(phase: PhasesAndOther, fieldPath: string, errorMessages: string[]): void; + clearError(phase: PhasesAndOther, fieldPath: string): void; +} + +const FormErrorsContext = createContext(null as any); + +const createEmptyErrors = (): Errors => ({ + hasErrors: false, + hot: {}, + warm: {}, + cold: {}, + delete: {}, + other: {}, +}); + +export const FormErrorsProvider: FunctionComponent = ({ children }) => { + const [errors, setErrors] = useState(createEmptyErrors); + const form = useFormContext(); + + const addError: ContextValue['addError'] = useCallback( + (phase, fieldPath, errorMessages) => { + setErrors((previousErrors) => ({ + ...previousErrors, + hasErrors: true, + [phase]: { + ...previousErrors[phase], + [fieldPath]: errorMessages, + }, + })); + }, + [setErrors] + ); + + const clearError: ContextValue['clearError'] = useCallback( + (phase, fieldPath) => { + if (form.getErrors().length) { + setErrors((previousErrors) => { + const { + [phase]: { [fieldPath]: fieldErrorToOmit, ...restOfPhaseErrors }, + ...otherPhases + } = previousErrors; + + const hasErrors = + Object.keys(restOfPhaseErrors).length === 0 && + Object.keys(otherPhases).some((phaseErrors) => !!Object.keys(phaseErrors).length); + + return { + ...previousErrors, + hasErrors, + [phase]: restOfPhaseErrors, + }; + }); + } else { + setErrors(createEmptyErrors); + } + }, + [form, setErrors] + ); + + return ( + + {children} + + ); +}; + +export const useFormErrorsContext = () => { + const ctx = useContext(FormErrorsContext); + if (!ctx) { + throw new Error('useFormErrorsContext can only be used inside of FormErrorsProvider'); + } + + return ctx; +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/index.ts index 66fe498cbac87..e8a63295b4b0c 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/index.ts @@ -12,9 +12,11 @@ export { schema } from './schema'; export * from './validations'; -export { Form } from './components'; +export { Form, EnhancedUseField as UseField } from './components'; export { ConfigurationIssuesProvider, useConfigurationIssues, } from './configuration_issues_context'; + +export { FormErrorsProvider, useFormErrorsContext } from './form_errors_context'; diff --git a/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts b/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts index fdb25dec6f1fd..daaf1fa6ffd66 100644 --- a/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts +++ b/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts @@ -12,7 +12,9 @@ export { useFormData, Form, FormHook, - UseField, + FieldHook, + FormData, + Props as UseFieldProps, FieldConfig, OnFormUpdateArg, ValidationFunc, diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.helpers.ts index 4cc40c71e4efd..0d18ad6e06692 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_clone.helpers.ts @@ -21,4 +21,4 @@ const testBedConfig: TestBedConfig = { const initTestBed = registerTestBed(WithAppDependencies(TemplateClone), testBedConfig); -export const setup = formSetup.bind(null, initTestBed); +export const setup: any = formSetup.bind(null, initTestBed); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.helpers.ts index 330723d87a3de..f1681c7ad6d96 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_create.helpers.ts @@ -23,4 +23,4 @@ const initTestBed = registerTestBed( testBedConfig ); -export const setup = formSetup.bind(null, initTestBed); +export const setup: any = formSetup.bind(null, initTestBed); diff --git a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.helpers.ts b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.helpers.ts index 21ecf18aa0d38..4c3578b5f401d 100644 --- a/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.helpers.ts +++ b/x-pack/plugins/index_management/__jest__/client_integration/index_template_wizard/template_edit.helpers.ts @@ -21,4 +21,4 @@ const testBedConfig: TestBedConfig = { const initTestBed = registerTestBed(WithAppDependencies(TemplateEdit), testBedConfig); -export const setup = formSetup.bind(null, initTestBed); +export const setup: any = formSetup.bind(null, initTestBed); diff --git a/x-pack/plugins/index_management/tsconfig.json b/x-pack/plugins/index_management/tsconfig.json new file mode 100644 index 0000000000000..87be6cfc2d627 --- /dev/null +++ b/x-pack/plugins/index_management/tsconfig.json @@ -0,0 +1,31 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "__jest__/**/*", + "common/**/*", + "public/**/*", + "server/**/*", + "test/**/*", + "../../typings/**/*", + ], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + { "path": "../../../src/plugins/home/tsconfig.json" }, + { "path": "../../../src/plugins/management/tsconfig.json" }, + { "path": "../../../src/plugins/share/tsconfig.json" }, + { "path": "../../../src/plugins/usage_collection/tsconfig.json" }, + { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, + { "path": "../../../src/plugins/es_ui_shared/tsconfig.json" }, + { "path": "../features/tsconfig.json" }, + { "path": "../licensing/tsconfig.json" }, + { "path": "../security/tsconfig.json" }, + { "path": "../runtime_fields/tsconfig.json" } + ] +} diff --git a/x-pack/plugins/infra/common/graphql/root/schema.gql.ts b/x-pack/plugins/infra/common/graphql/root/schema.gql.ts deleted file mode 100644 index 1665334827e8e..0000000000000 --- a/x-pack/plugins/infra/common/graphql/root/schema.gql.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 gql from 'graphql-tag'; - -export const rootSchema = gql` - schema { - query: Query - mutation: Mutation - } - - type Query - - type Mutation -`; diff --git a/x-pack/plugins/infra/common/graphql/shared/fragments.gql_query.ts b/x-pack/plugins/infra/common/graphql/shared/fragments.gql_query.ts deleted file mode 100644 index c324813b65efb..0000000000000 --- a/x-pack/plugins/infra/common/graphql/shared/fragments.gql_query.ts +++ /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 gql from 'graphql-tag'; - -export const sharedFragments = { - InfraTimeKey: gql` - fragment InfraTimeKeyFields on InfraTimeKey { - time - tiebreaker - } - `, - InfraSourceFields: gql` - fragment InfraSourceFields on InfraSource { - id - version - updatedAt - origin - } - `, - InfraLogEntryFields: gql` - fragment InfraLogEntryFields on InfraLogEntry { - gid - key { - time - tiebreaker - } - columns { - ... on InfraLogEntryTimestampColumn { - columnId - timestamp - } - ... on InfraLogEntryMessageColumn { - columnId - message { - ... on InfraLogMessageFieldSegment { - field - value - } - ... on InfraLogMessageConstantSegment { - constant - } - } - } - ... on InfraLogEntryFieldColumn { - columnId - field - value - } - } - } - `, - InfraLogEntryHighlightFields: gql` - fragment InfraLogEntryHighlightFields on InfraLogEntry { - gid - key { - time - tiebreaker - } - columns { - ... on InfraLogEntryMessageColumn { - columnId - message { - ... on InfraLogMessageFieldSegment { - field - highlights - } - } - } - ... on InfraLogEntryFieldColumn { - columnId - field - highlights - } - } - } - `, -}; diff --git a/x-pack/plugins/infra/common/graphql/shared/schema.gql.ts b/x-pack/plugins/infra/common/graphql/shared/schema.gql.ts deleted file mode 100644 index 071313817eff3..0000000000000 --- a/x-pack/plugins/infra/common/graphql/shared/schema.gql.ts +++ /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 gql from 'graphql-tag'; - -export const sharedSchema = gql` - "A representation of the log entry's position in the event stream" - type InfraTimeKey { - "The timestamp of the event that the log entry corresponds to" - time: Float! - "The tiebreaker that disambiguates events with the same timestamp" - tiebreaker: Float! - } - - input InfraTimeKeyInput { - time: Float! - tiebreaker: Float! - } - - enum InfraIndexType { - ANY - LOGS - METRICS - } - - enum InfraNodeType { - pod - container - host - awsEC2 - awsS3 - awsRDS - awsSQS - } -`; diff --git a/x-pack/plugins/infra/common/graphql/types.ts b/x-pack/plugins/infra/common/graphql/types.ts deleted file mode 100644 index 4a18c3d5ff334..0000000000000 --- a/x-pack/plugins/infra/common/graphql/types.ts +++ /dev/null @@ -1,1117 +0,0 @@ -/* tslint:disable */ - -// ==================================================== -// START: Typescript template -// ==================================================== - -// ==================================================== -// Types -// ==================================================== - -export interface Query { - /** Get an infrastructure data source by id.The resolution order for the source configuration attributes is as followswith the first defined value winning:1. The attributes of the saved object with the given 'id'.2. The attributes defined in the static Kibana configuration key'xpack.infra.sources.default'.3. The hard-coded default values.As a consequence, querying a source that doesn't exist doesn't error out,but returns the configured or hardcoded defaults. */ - source: InfraSource; - /** Get a list of all infrastructure data sources */ - allSources: InfraSource[]; -} -/** A source of infrastructure data */ -export interface InfraSource { - /** The id of the source */ - id: string; - /** The version number the source configuration was last persisted with */ - version?: string | null; - /** The timestamp the source configuration was last persisted at */ - updatedAt?: number | null; - /** The origin of the source (one of 'fallback', 'internal', 'stored') */ - origin: string; - /** The raw configuration of the source */ - configuration: InfraSourceConfiguration; - /** The status of the source */ - status: InfraSourceStatus; - /** A consecutive span of log entries surrounding a point in time */ - logEntriesAround: InfraLogEntryInterval; - /** A consecutive span of log entries within an interval */ - logEntriesBetween: InfraLogEntryInterval; - /** Sequences of log entries matching sets of highlighting queries within an interval */ - logEntryHighlights: InfraLogEntryInterval[]; - - /** A snapshot of nodes */ - snapshot?: InfraSnapshotResponse | null; - - metrics: InfraMetricData[]; -} -/** A set of configuration options for an infrastructure data source */ -export interface InfraSourceConfiguration { - /** The name of the data source */ - name: string; - /** A description of the data source */ - description: string; - /** The alias to read metric data from */ - metricAlias: string; - /** The alias to read log data from */ - logAlias: string; - /** The field mapping to use for this source */ - fields: InfraSourceFields; - /** The columns to use for log display */ - logColumns: InfraSourceLogColumn[]; -} -/** A mapping of semantic fields to their document counterparts */ -export interface InfraSourceFields { - /** The field to identify a container by */ - container: string; - /** The fields to identify a host by */ - host: string; - /** The fields to use as the log message */ - message: string[]; - /** The field to identify a pod by */ - pod: string; - /** The field to use as a tiebreaker for log events that have identical timestamps */ - tiebreaker: string; - /** The field to use as a timestamp for metrics and logs */ - timestamp: string; -} -/** The built-in timestamp log column */ -export interface InfraSourceTimestampLogColumn { - timestampColumn: InfraSourceTimestampLogColumnAttributes; -} - -export interface InfraSourceTimestampLogColumnAttributes { - /** A unique id for the column */ - id: string; -} -/** The built-in message log column */ -export interface InfraSourceMessageLogColumn { - messageColumn: InfraSourceMessageLogColumnAttributes; -} - -export interface InfraSourceMessageLogColumnAttributes { - /** A unique id for the column */ - id: string; -} -/** A log column containing a field value */ -export interface InfraSourceFieldLogColumn { - fieldColumn: InfraSourceFieldLogColumnAttributes; -} - -export interface InfraSourceFieldLogColumnAttributes { - /** A unique id for the column */ - id: string; - /** The field name this column refers to */ - field: string; -} -/** The status of an infrastructure data source */ -export interface InfraSourceStatus { - /** Whether the configured metric alias exists */ - metricAliasExists: boolean; - /** Whether the configured log alias exists */ - logAliasExists: boolean; - /** Whether the configured alias or wildcard pattern resolve to any metric indices */ - metricIndicesExist: boolean; - /** Whether the configured alias or wildcard pattern resolve to any log indices */ - logIndicesExist: boolean; - /** The list of indices in the metric alias */ - metricIndices: string[]; - /** The list of indices in the log alias */ - logIndices: string[]; - /** The list of fields defined in the index mappings */ - indexFields: InfraIndexField[]; -} -/** A descriptor of a field in an index */ -export interface InfraIndexField { - /** The name of the field */ - name: string; - /** The type of the field's values as recognized by Kibana */ - type: string; - /** Whether the field's values can be efficiently searched for */ - searchable: boolean; - /** Whether the field's values can be aggregated */ - aggregatable: boolean; - /** Whether the field should be displayed based on event.module and a ECS allowed list */ - displayable: boolean; -} -/** A consecutive sequence of log entries */ -export interface InfraLogEntryInterval { - /** The key corresponding to the start of the interval covered by the entries */ - start?: InfraTimeKey | null; - /** The key corresponding to the end of the interval covered by the entries */ - end?: InfraTimeKey | null; - /** Whether there are more log entries available before the start */ - hasMoreBefore: boolean; - /** Whether there are more log entries available after the end */ - hasMoreAfter: boolean; - /** The query the log entries were filtered by */ - filterQuery?: string | null; - /** The query the log entries were highlighted with */ - highlightQuery?: string | null; - /** A list of the log entries */ - entries: InfraLogEntry[]; -} -/** A representation of the log entry's position in the event stream */ -export interface InfraTimeKey { - /** The timestamp of the event that the log entry corresponds to */ - time: number; - /** The tiebreaker that disambiguates events with the same timestamp */ - tiebreaker: number; -} -/** A log entry */ -export interface InfraLogEntry { - /** A unique representation of the log entry's position in the event stream */ - key: InfraTimeKey; - /** The log entry's id */ - gid: string; - /** The source id */ - source: string; - /** The columns used for rendering the log entry */ - columns: InfraLogEntryColumn[]; -} -/** A special built-in column that contains the log entry's timestamp */ -export interface InfraLogEntryTimestampColumn { - /** The id of the corresponding column configuration */ - columnId: string; - /** The timestamp */ - timestamp: number; -} -/** A special built-in column that contains the log entry's constructed message */ -export interface InfraLogEntryMessageColumn { - /** The id of the corresponding column configuration */ - columnId: string; - /** A list of the formatted log entry segments */ - message: InfraLogMessageSegment[]; -} -/** A segment of the log entry message that was derived from a field */ -export interface InfraLogMessageFieldSegment { - /** The field the segment was derived from */ - field: string; - /** The segment's message */ - value: string; - /** A list of highlighted substrings of the value */ - highlights: string[]; -} -/** A segment of the log entry message that was derived from a string literal */ -export interface InfraLogMessageConstantSegment { - /** The segment's message */ - constant: string; -} -/** A column that contains the value of a field of the log entry */ -export interface InfraLogEntryFieldColumn { - /** The id of the corresponding column configuration */ - columnId: string; - /** The field name of the column */ - field: string; - /** The value of the field in the log entry */ - value: string; - /** A list of highlighted substrings of the value */ - highlights: string[]; -} - -export interface InfraSnapshotResponse { - /** Nodes of type host, container or pod grouped by 0, 1 or 2 terms */ - nodes: InfraSnapshotNode[]; -} - -export interface InfraSnapshotNode { - path: InfraSnapshotNodePath[]; - - metric: InfraSnapshotNodeMetric; -} - -export interface InfraSnapshotNodePath { - value: string; - - label: string; - - ip?: string | null; -} - -export interface InfraSnapshotNodeMetric { - name: InfraSnapshotMetricType; - - value?: number | null; - - avg?: number | null; - - max?: number | null; -} - -export interface InfraMetricData { - id?: InfraMetric | null; - - series: InfraDataSeries[]; -} - -export interface InfraDataSeries { - id: string; - - label: string; - - data: InfraDataPoint[]; -} - -export interface InfraDataPoint { - timestamp: number; - - value?: number | null; -} - -export interface Mutation { - /** Create a new source of infrastructure data */ - createSource: UpdateSourceResult; - /** Modify an existing source */ - updateSource: UpdateSourceResult; - /** Delete a source of infrastructure data */ - deleteSource: DeleteSourceResult; -} -/** The result of a successful source update */ -export interface UpdateSourceResult { - /** The source that was updated */ - source: InfraSource; -} -/** The result of a source deletion operations */ -export interface DeleteSourceResult { - /** The id of the source that was deleted */ - id: string; -} - -// ==================================================== -// InputTypes -// ==================================================== - -export interface InfraTimeKeyInput { - time: number; - - tiebreaker: number; -} -/** A highlighting definition */ -export interface InfraLogEntryHighlightInput { - /** The query to highlight by */ - query: string; - /** The number of highlighted documents to include beyond the beginning of the interval */ - countBefore: number; - /** The number of highlighted documents to include beyond the end of the interval */ - countAfter: number; -} - -export interface InfraTimerangeInput { - /** The interval string to use for last bucket. The format is '{value}{unit}'. For example '5m' would return the metrics for the last 5 minutes of the timespan. */ - interval: string; - /** The end of the timerange */ - to: number; - /** The beginning of the timerange */ - from: number; -} - -export interface InfraSnapshotGroupbyInput { - /** The label to use in the results for the group by for the terms group by */ - label?: string | null; - /** The field to group by from a terms aggregation, this is ignored by the filter type */ - field?: string | null; -} - -export interface InfraSnapshotMetricInput { - /** The type of metric */ - type: InfraSnapshotMetricType; -} - -export interface InfraNodeIdsInput { - nodeId: string; - - cloudId?: string | null; -} -/** The properties to update the source with */ -export interface UpdateSourceInput { - /** The name of the data source */ - name?: string | null; - /** A description of the data source */ - description?: string | null; - /** The alias to read metric data from */ - metricAlias?: string | null; - /** The alias to read log data from */ - logAlias?: string | null; - /** The field mapping to use for this source */ - fields?: UpdateSourceFieldsInput | null; - /** Default view for inventory */ - inventoryDefaultView?: string | null; - /** Default view for Metrics Explorer */ - metricsExplorerDefaultView?: string | null; - /** The log columns to display for this source */ - logColumns?: UpdateSourceLogColumnInput[] | null; -} -/** The mapping of semantic fields of the source to be created */ -export interface UpdateSourceFieldsInput { - /** The field to identify a container by */ - container?: string | null; - /** The fields to identify a host by */ - host?: string | null; - /** The field to identify a pod by */ - pod?: string | null; - /** The field to use as a tiebreaker for log events that have identical timestamps */ - tiebreaker?: string | null; - /** The field to use as a timestamp for metrics and logs */ - timestamp?: string | null; -} -/** One of the log column types to display for this source */ -export interface UpdateSourceLogColumnInput { - /** A custom field log column */ - fieldColumn?: UpdateSourceFieldLogColumnInput | null; - /** A built-in message log column */ - messageColumn?: UpdateSourceMessageLogColumnInput | null; - /** A built-in timestamp log column */ - timestampColumn?: UpdateSourceTimestampLogColumnInput | null; -} - -export interface UpdateSourceFieldLogColumnInput { - id: string; - - field: string; -} - -export interface UpdateSourceMessageLogColumnInput { - id: string; -} - -export interface UpdateSourceTimestampLogColumnInput { - id: string; -} - -// ==================================================== -// Arguments -// ==================================================== - -export interface SourceQueryArgs { - /** The id of the source */ - id: string; -} -export interface LogEntriesAroundInfraSourceArgs { - /** The sort key that corresponds to the point in time */ - key: InfraTimeKeyInput; - /** The maximum number of preceding to return */ - countBefore?: number | null; - /** The maximum number of following to return */ - countAfter?: number | null; - /** The query to filter the log entries by */ - filterQuery?: string | null; -} -export interface LogEntriesBetweenInfraSourceArgs { - /** The sort key that corresponds to the start of the interval */ - startKey: InfraTimeKeyInput; - /** The sort key that corresponds to the end of the interval */ - endKey: InfraTimeKeyInput; - /** The query to filter the log entries by */ - filterQuery?: string | null; -} -export interface LogEntryHighlightsInfraSourceArgs { - /** The sort key that corresponds to the start of the interval */ - startKey: InfraTimeKeyInput; - /** The sort key that corresponds to the end of the interval */ - endKey: InfraTimeKeyInput; - /** The query to filter the log entries by */ - filterQuery?: string | null; - /** The highlighting to apply to the log entries */ - highlights: InfraLogEntryHighlightInput[]; -} -export interface SnapshotInfraSourceArgs { - timerange: InfraTimerangeInput; - - filterQuery?: string | null; -} -export interface MetricsInfraSourceArgs { - nodeIds: InfraNodeIdsInput; - - nodeType: InfraNodeType; - - timerange: InfraTimerangeInput; - - metrics: InfraMetric[]; -} -export interface IndexFieldsInfraSourceStatusArgs { - indexType?: InfraIndexType | null; -} -export interface NodesInfraSnapshotResponseArgs { - type: InfraNodeType; - - groupBy: InfraSnapshotGroupbyInput[]; - - metric: InfraSnapshotMetricInput; -} -export interface CreateSourceMutationArgs { - /** The id of the source */ - id: string; - - sourceProperties: UpdateSourceInput; -} -export interface UpdateSourceMutationArgs { - /** The id of the source */ - id: string; - /** The properties to update the source with */ - sourceProperties: UpdateSourceInput; -} -export interface DeleteSourceMutationArgs { - /** The id of the source */ - id: string; -} - -// ==================================================== -// Enums -// ==================================================== - -export enum InfraIndexType { - ANY = 'ANY', - LOGS = 'LOGS', - METRICS = 'METRICS', -} - -export enum InfraNodeType { - pod = 'pod', - container = 'container', - host = 'host', - awsEC2 = 'awsEC2', - awsS3 = 'awsS3', - awsRDS = 'awsRDS', - awsSQS = 'awsSQS', -} - -export enum InfraSnapshotMetricType { - count = 'count', - cpu = 'cpu', - load = 'load', - memory = 'memory', - tx = 'tx', - rx = 'rx', - logRate = 'logRate', - diskIOReadBytes = 'diskIOReadBytes', - diskIOWriteBytes = 'diskIOWriteBytes', - s3TotalRequests = 's3TotalRequests', - s3NumberOfObjects = 's3NumberOfObjects', - s3BucketSize = 's3BucketSize', - s3DownloadBytes = 's3DownloadBytes', - s3UploadBytes = 's3UploadBytes', - rdsConnections = 'rdsConnections', - rdsQueriesExecuted = 'rdsQueriesExecuted', - rdsActiveTransactions = 'rdsActiveTransactions', - rdsLatency = 'rdsLatency', - sqsMessagesVisible = 'sqsMessagesVisible', - sqsMessagesDelayed = 'sqsMessagesDelayed', - sqsMessagesSent = 'sqsMessagesSent', - sqsMessagesEmpty = 'sqsMessagesEmpty', - sqsOldestMessage = 'sqsOldestMessage', -} - -export enum InfraMetric { - hostSystemOverview = 'hostSystemOverview', - hostCpuUsage = 'hostCpuUsage', - hostFilesystem = 'hostFilesystem', - hostK8sOverview = 'hostK8sOverview', - hostK8sCpuCap = 'hostK8sCpuCap', - hostK8sDiskCap = 'hostK8sDiskCap', - hostK8sMemoryCap = 'hostK8sMemoryCap', - hostK8sPodCap = 'hostK8sPodCap', - hostLoad = 'hostLoad', - hostMemoryUsage = 'hostMemoryUsage', - hostNetworkTraffic = 'hostNetworkTraffic', - hostDockerOverview = 'hostDockerOverview', - hostDockerInfo = 'hostDockerInfo', - hostDockerTop5ByCpu = 'hostDockerTop5ByCpu', - hostDockerTop5ByMemory = 'hostDockerTop5ByMemory', - podOverview = 'podOverview', - podCpuUsage = 'podCpuUsage', - podMemoryUsage = 'podMemoryUsage', - podLogUsage = 'podLogUsage', - podNetworkTraffic = 'podNetworkTraffic', - containerOverview = 'containerOverview', - containerCpuKernel = 'containerCpuKernel', - containerCpuUsage = 'containerCpuUsage', - containerDiskIOOps = 'containerDiskIOOps', - containerDiskIOBytes = 'containerDiskIOBytes', - containerMemory = 'containerMemory', - containerNetworkTraffic = 'containerNetworkTraffic', - nginxHits = 'nginxHits', - nginxRequestRate = 'nginxRequestRate', - nginxActiveConnections = 'nginxActiveConnections', - nginxRequestsPerConnection = 'nginxRequestsPerConnection', - awsOverview = 'awsOverview', - awsCpuUtilization = 'awsCpuUtilization', - awsNetworkBytes = 'awsNetworkBytes', - awsNetworkPackets = 'awsNetworkPackets', - awsDiskioBytes = 'awsDiskioBytes', - awsDiskioOps = 'awsDiskioOps', - awsEC2CpuUtilization = 'awsEC2CpuUtilization', - awsEC2DiskIOBytes = 'awsEC2DiskIOBytes', - awsEC2NetworkTraffic = 'awsEC2NetworkTraffic', - awsS3TotalRequests = 'awsS3TotalRequests', - awsS3NumberOfObjects = 'awsS3NumberOfObjects', - awsS3BucketSize = 'awsS3BucketSize', - awsS3DownloadBytes = 'awsS3DownloadBytes', - awsS3UploadBytes = 'awsS3UploadBytes', - awsRDSCpuTotal = 'awsRDSCpuTotal', - awsRDSConnections = 'awsRDSConnections', - awsRDSQueriesExecuted = 'awsRDSQueriesExecuted', - awsRDSActiveTransactions = 'awsRDSActiveTransactions', - awsRDSLatency = 'awsRDSLatency', - awsSQSMessagesVisible = 'awsSQSMessagesVisible', - awsSQSMessagesDelayed = 'awsSQSMessagesDelayed', - awsSQSMessagesSent = 'awsSQSMessagesSent', - awsSQSMessagesEmpty = 'awsSQSMessagesEmpty', - awsSQSOldestMessage = 'awsSQSOldestMessage', - custom = 'custom', -} - -// ==================================================== -// Unions -// ==================================================== - -/** All known log column types */ -export type InfraSourceLogColumn = - | InfraSourceTimestampLogColumn - | InfraSourceMessageLogColumn - | InfraSourceFieldLogColumn; - -/** A column of a log entry */ -export type InfraLogEntryColumn = - | InfraLogEntryTimestampColumn - | InfraLogEntryMessageColumn - | InfraLogEntryFieldColumn; - -/** A segment of the log entry message */ -export type InfraLogMessageSegment = InfraLogMessageFieldSegment | InfraLogMessageConstantSegment; - -// ==================================================== -// END: Typescript template -// ==================================================== - -// ==================================================== -// Documents -// ==================================================== - -export namespace LogEntryHighlightsQuery { - export type Variables = { - sourceId?: string | null; - startKey: InfraTimeKeyInput; - endKey: InfraTimeKeyInput; - filterQuery?: string | null; - highlights: InfraLogEntryHighlightInput[]; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'InfraSource'; - - id: string; - - logEntryHighlights: LogEntryHighlights[]; - }; - - export type LogEntryHighlights = { - __typename?: 'InfraLogEntryInterval'; - - start?: Start | null; - - end?: End | null; - - entries: Entries[]; - }; - - export type Start = InfraTimeKeyFields.Fragment; - - export type End = InfraTimeKeyFields.Fragment; - - export type Entries = InfraLogEntryHighlightFields.Fragment; -} - -export namespace MetricsQuery { - export type Variables = { - sourceId: string; - timerange: InfraTimerangeInput; - metrics: InfraMetric[]; - nodeId: string; - cloudId?: string | null; - nodeType: InfraNodeType; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'InfraSource'; - - id: string; - - metrics: Metrics[]; - }; - - export type Metrics = { - __typename?: 'InfraMetricData'; - - id?: InfraMetric | null; - - series: Series[]; - }; - - export type Series = { - __typename?: 'InfraDataSeries'; - - id: string; - - label: string; - - data: Data[]; - }; - - export type Data = { - __typename?: 'InfraDataPoint'; - - timestamp: number; - - value?: number | null; - }; -} - -export namespace CreateSourceConfigurationMutation { - export type Variables = { - sourceId: string; - sourceProperties: UpdateSourceInput; - }; - - export type Mutation = { - __typename?: 'Mutation'; - - createSource: CreateSource; - }; - - export type CreateSource = { - __typename?: 'UpdateSourceResult'; - - source: Source; - }; - - export type Source = { - __typename?: 'InfraSource'; - - configuration: Configuration; - - status: Status; - } & InfraSourceFields.Fragment; - - export type Configuration = SourceConfigurationFields.Fragment; - - export type Status = SourceStatusFields.Fragment; -} - -export namespace SourceQuery { - export type Variables = { - sourceId?: string | null; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'InfraSource'; - - configuration: Configuration; - - status: Status; - } & InfraSourceFields.Fragment; - - export type Configuration = SourceConfigurationFields.Fragment; - - export type Status = SourceStatusFields.Fragment; -} - -export namespace UpdateSourceMutation { - export type Variables = { - sourceId?: string | null; - sourceProperties: UpdateSourceInput; - }; - - export type Mutation = { - __typename?: 'Mutation'; - - updateSource: UpdateSource; - }; - - export type UpdateSource = { - __typename?: 'UpdateSourceResult'; - - source: Source; - }; - - export type Source = { - __typename?: 'InfraSource'; - - configuration: Configuration; - - status: Status; - } & InfraSourceFields.Fragment; - - export type Configuration = SourceConfigurationFields.Fragment; - - export type Status = SourceStatusFields.Fragment; -} - -export namespace WaffleNodesQuery { - export type Variables = { - sourceId: string; - timerange: InfraTimerangeInput; - filterQuery?: string | null; - metric: InfraSnapshotMetricInput; - groupBy: InfraSnapshotGroupbyInput[]; - type: InfraNodeType; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'InfraSource'; - - id: string; - - snapshot?: Snapshot | null; - }; - - export type Snapshot = { - __typename?: 'InfraSnapshotResponse'; - - nodes: Nodes[]; - }; - - export type Nodes = { - __typename?: 'InfraSnapshotNode'; - - path: Path[]; - - metric: Metric; - }; - - export type Path = { - __typename?: 'InfraSnapshotNodePath'; - - value: string; - - label: string; - - ip?: string | null; - }; - - export type Metric = { - __typename?: 'InfraSnapshotNodeMetric'; - - name: InfraSnapshotMetricType; - - value?: number | null; - - avg?: number | null; - - max?: number | null; - }; -} - -export namespace LogEntries { - export type Variables = { - sourceId?: string | null; - timeKey: InfraTimeKeyInput; - countBefore?: number | null; - countAfter?: number | null; - filterQuery?: string | null; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'InfraSource'; - - id: string; - - logEntriesAround: LogEntriesAround; - }; - - export type LogEntriesAround = { - __typename?: 'InfraLogEntryInterval'; - - start?: Start | null; - - end?: End | null; - - hasMoreBefore: boolean; - - hasMoreAfter: boolean; - - entries: Entries[]; - }; - - export type Start = InfraTimeKeyFields.Fragment; - - export type End = InfraTimeKeyFields.Fragment; - - export type Entries = InfraLogEntryFields.Fragment; -} - -export namespace SourceConfigurationFields { - export type Fragment = { - __typename?: 'InfraSourceConfiguration'; - - name: string; - - description: string; - - logAlias: string; - - metricAlias: string; - - fields: Fields; - - logColumns: LogColumns[]; - - inventoryDefaultView: string; - - metricsExplorerDefaultView: string; - }; - - export type Fields = { - __typename?: 'InfraSourceFields'; - - container: string; - - host: string; - - message: string[]; - - pod: string; - - tiebreaker: string; - - timestamp: string; - }; - - export type LogColumns = - | InfraSourceTimestampLogColumnInlineFragment - | InfraSourceMessageLogColumnInlineFragment - | InfraSourceFieldLogColumnInlineFragment; - - export type InfraSourceTimestampLogColumnInlineFragment = { - __typename?: 'InfraSourceTimestampLogColumn'; - - timestampColumn: TimestampColumn; - }; - - export type TimestampColumn = { - __typename?: 'InfraSourceTimestampLogColumnAttributes'; - - id: string; - }; - - export type InfraSourceMessageLogColumnInlineFragment = { - __typename?: 'InfraSourceMessageLogColumn'; - - messageColumn: MessageColumn; - }; - - export type MessageColumn = { - __typename?: 'InfraSourceMessageLogColumnAttributes'; - - id: string; - }; - - export type InfraSourceFieldLogColumnInlineFragment = { - __typename?: 'InfraSourceFieldLogColumn'; - - fieldColumn: FieldColumn; - }; - - export type FieldColumn = { - __typename?: 'InfraSourceFieldLogColumnAttributes'; - - id: string; - - field: string; - }; -} - -export namespace SourceStatusFields { - export type Fragment = { - __typename?: 'InfraSourceStatus'; - - indexFields: IndexFields[]; - - logIndicesExist: boolean; - - metricIndicesExist: boolean; - }; - - export type IndexFields = { - __typename?: 'InfraIndexField'; - - name: string; - - type: string; - - searchable: boolean; - - aggregatable: boolean; - - displayable: boolean; - }; -} - -export namespace InfraTimeKeyFields { - export type Fragment = { - __typename?: 'InfraTimeKey'; - - time: number; - - tiebreaker: number; - }; -} - -export namespace InfraSourceFields { - export type Fragment = { - __typename?: 'InfraSource'; - - id: string; - - version?: string | null; - - updatedAt?: number | null; - - origin: string; - }; -} - -export namespace InfraLogEntryFields { - export type Fragment = { - __typename?: 'InfraLogEntry'; - - gid: string; - - key: Key; - - columns: Columns[]; - }; - - export type Key = { - __typename?: 'InfraTimeKey'; - - time: number; - - tiebreaker: number; - }; - - export type Columns = - | InfraLogEntryTimestampColumnInlineFragment - | InfraLogEntryMessageColumnInlineFragment - | InfraLogEntryFieldColumnInlineFragment; - - export type InfraLogEntryTimestampColumnInlineFragment = { - __typename?: 'InfraLogEntryTimestampColumn'; - - columnId: string; - - timestamp: number; - }; - - export type InfraLogEntryMessageColumnInlineFragment = { - __typename?: 'InfraLogEntryMessageColumn'; - - columnId: string; - - message: Message[]; - }; - - export type Message = - | InfraLogMessageFieldSegmentInlineFragment - | InfraLogMessageConstantSegmentInlineFragment; - - export type InfraLogMessageFieldSegmentInlineFragment = { - __typename?: 'InfraLogMessageFieldSegment'; - - field: string; - - value: string; - }; - - export type InfraLogMessageConstantSegmentInlineFragment = { - __typename?: 'InfraLogMessageConstantSegment'; - - constant: string; - }; - - export type InfraLogEntryFieldColumnInlineFragment = { - __typename?: 'InfraLogEntryFieldColumn'; - - columnId: string; - - field: string; - - value: string; - }; -} - -export namespace InfraLogEntryHighlightFields { - export type Fragment = { - __typename?: 'InfraLogEntry'; - - gid: string; - - key: Key; - - columns: Columns[]; - }; - - export type Key = { - __typename?: 'InfraTimeKey'; - - time: number; - - tiebreaker: number; - }; - - export type Columns = - | InfraLogEntryMessageColumnInlineFragment - | InfraLogEntryFieldColumnInlineFragment; - - export type InfraLogEntryMessageColumnInlineFragment = { - __typename?: 'InfraLogEntryMessageColumn'; - - columnId: string; - - message: Message[]; - }; - - export type Message = InfraLogMessageFieldSegmentInlineFragment; - - export type InfraLogMessageFieldSegmentInlineFragment = { - __typename?: 'InfraLogMessageFieldSegment'; - - field: string; - - highlights: string[]; - }; - - export type InfraLogEntryFieldColumnInlineFragment = { - __typename?: 'InfraLogEntryFieldColumn'; - - columnId: string; - - field: string; - - highlights: string[]; - }; -} diff --git a/x-pack/plugins/infra/common/http_api/log_sources/log_source_configuration.ts b/x-pack/plugins/infra/common/http_api/log_sources/log_source_configuration.ts index 7581e29692356..df7d80d33f1e6 100644 --- a/x-pack/plugins/infra/common/http_api/log_sources/log_source_configuration.ts +++ b/x-pack/plugins/infra/common/http_api/log_sources/log_source_configuration.ts @@ -53,6 +53,7 @@ export const logSourceColumnConfigurationRT = rt.union([ logSourceMessageColumnConfigurationRT, logSourceFieldColumnConfigurationRT, ]); +export type LogSourceColumnConfiguration = rt.TypeOf; export const logSourceConfigurationPropertiesRT = rt.strict({ name: rt.string, diff --git a/x-pack/plugins/infra/common/http_api/node_details_api.ts b/x-pack/plugins/infra/common/http_api/node_details_api.ts index 0ef5ae82baeb9..6de21da53c36b 100644 --- a/x-pack/plugins/infra/common/http_api/node_details_api.ts +++ b/x-pack/plugins/infra/common/http_api/node_details_api.ts @@ -17,18 +17,20 @@ const NodeDetailsDataPointRT = rt.intersection([ }), ]); -const NodeDetailsDataSeries = rt.type({ +const NodeDetailsDataSeriesRT = rt.type({ id: rt.string, label: rt.string, data: rt.array(NodeDetailsDataPointRT), }); +export type NodeDetailsDataSeries = rt.TypeOf; + export const NodeDetailsMetricDataRT = rt.intersection([ rt.partial({ id: rt.union([InventoryMetricRT, rt.null]), }), rt.type({ - series: rt.array(NodeDetailsDataSeries), + series: rt.array(NodeDetailsDataSeriesRT), }), ]); diff --git a/x-pack/plugins/infra/common/http_api/source_api.ts b/x-pack/plugins/infra/common/http_api/source_api.ts index be50989358c72..52a8d43da53b5 100644 --- a/x-pack/plugins/infra/common/http_api/source_api.ts +++ b/x-pack/plugins/infra/common/http_api/source_api.ts @@ -39,18 +39,30 @@ const SavedSourceConfigurationFieldsRuntimeType = rt.partial({ timestamp: rt.string, }); +export type InfraSavedSourceConfigurationFields = rt.TypeOf< + typeof SavedSourceConfigurationFieldColumnRuntimeType +>; + export const SavedSourceConfigurationTimestampColumnRuntimeType = rt.type({ timestampColumn: rt.type({ id: rt.string, }), }); +export type InfraSourceConfigurationTimestampColumn = rt.TypeOf< + typeof SavedSourceConfigurationTimestampColumnRuntimeType +>; + export const SavedSourceConfigurationMessageColumnRuntimeType = rt.type({ messageColumn: rt.type({ id: rt.string, }), }); +export type InfraSourceConfigurationMessageColumn = rt.TypeOf< + typeof SavedSourceConfigurationMessageColumnRuntimeType +>; + export const SavedSourceConfigurationFieldColumnRuntimeType = rt.type({ fieldColumn: rt.type({ id: rt.string, @@ -64,6 +76,10 @@ export const SavedSourceConfigurationColumnRuntimeType = rt.union([ SavedSourceConfigurationFieldColumnRuntimeType, ]); +export type InfraSavedSourceConfigurationColumn = rt.TypeOf< + typeof SavedSourceConfigurationColumnRuntimeType +>; + export const SavedSourceConfigurationRuntimeType = rt.partial({ name: rt.string, description: rt.string, @@ -136,12 +152,30 @@ const SourceConfigurationFieldsRuntimeType = rt.type({ ...StaticSourceConfigurationFieldsRuntimeType.props, }); +export type InfraSourceConfigurationFields = rt.TypeOf; + export const SourceConfigurationRuntimeType = rt.type({ ...SavedSourceConfigurationRuntimeType.props, fields: SourceConfigurationFieldsRuntimeType, logColumns: rt.array(SavedSourceConfigurationColumnRuntimeType), }); +const SourceStatusFieldRuntimeType = rt.type({ + name: rt.string, + type: rt.string, + searchable: rt.boolean, + aggregatable: rt.boolean, + displayable: rt.boolean, +}); + +export type InfraSourceIndexField = rt.TypeOf; + +const SourceStatusRuntimeType = rt.type({ + logIndicesExist: rt.boolean, + metricIndicesExist: rt.boolean, + indexFields: rt.array(SourceStatusFieldRuntimeType), +}); + export const SourceRuntimeType = rt.intersection([ rt.type({ id: rt.string, @@ -155,31 +189,19 @@ export const SourceRuntimeType = rt.intersection([ rt.partial({ version: rt.string, updatedAt: rt.number, + status: SourceStatusRuntimeType, }), ]); +export interface InfraSourceStatus extends rt.TypeOf {} + export interface InfraSourceConfiguration extends rt.TypeOf {} export interface InfraSource extends rt.TypeOf {} -const SourceStatusFieldRuntimeType = rt.type({ - name: rt.string, - type: rt.string, - searchable: rt.boolean, - aggregatable: rt.boolean, - displayable: rt.boolean, -}); - -const SourceStatusRuntimeType = rt.type({ - logIndicesExist: rt.boolean, - metricIndicesExist: rt.boolean, - indexFields: rt.array(SourceStatusFieldRuntimeType), -}); - export const SourceResponseRuntimeType = rt.type({ source: SourceRuntimeType, - status: SourceStatusRuntimeType, }); export type SourceResponse = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/log_entry/log_entry.ts b/x-pack/plugins/infra/common/log_entry/log_entry.ts index eec1fb59f3091..837249b65b2e5 100644 --- a/x-pack/plugins/infra/common/log_entry/log_entry.ts +++ b/x-pack/plugins/infra/common/log_entry/log_entry.ts @@ -6,87 +6,78 @@ import * as rt from 'io-ts'; import { TimeKey } from '../time'; -import { logEntryCursorRT } from './log_entry_cursor'; import { jsonArrayRT } from '../typed_json'; - -export interface LogEntryOrigin { - id: string; - index: string; - type: string; -} +import { logEntryCursorRT } from './log_entry_cursor'; export type LogEntryTime = TimeKey; -export interface LogEntryFieldsMapping { - message: string; - tiebreaker: string; - time: string; -} - -export function isEqual(time1: LogEntryTime, time2: LogEntryTime) { - return time1.time === time2.time && time1.tiebreaker === time2.tiebreaker; -} - -export function isLess(time1: LogEntryTime, time2: LogEntryTime) { - return ( - time1.time < time2.time || (time1.time === time2.time && time1.tiebreaker < time2.tiebreaker) - ); -} - -export function isLessOrEqual(time1: LogEntryTime, time2: LogEntryTime) { - return ( - time1.time < time2.time || (time1.time === time2.time && time1.tiebreaker <= time2.tiebreaker) - ); -} - -export function isBetween(min: LogEntryTime, max: LogEntryTime, operand: LogEntryTime) { - return isLessOrEqual(min, operand) && isLessOrEqual(operand, max); -} - +/** + * message parts + */ export const logMessageConstantPartRT = rt.type({ constant: rt.string, }); +export type LogMessageConstantPart = rt.TypeOf; + export const logMessageFieldPartRT = rt.type({ field: rt.string, value: jsonArrayRT, highlights: rt.array(rt.string), }); +export type LogMessageFieldPart = rt.TypeOf; export const logMessagePartRT = rt.union([logMessageConstantPartRT, logMessageFieldPartRT]); +export type LogMessagePart = rt.TypeOf; + +/** + * columns + */ export const logTimestampColumnRT = rt.type({ columnId: rt.string, timestamp: rt.number }); +export type LogTimestampColumn = rt.TypeOf; + export const logFieldColumnRT = rt.type({ columnId: rt.string, field: rt.string, value: jsonArrayRT, highlights: rt.array(rt.string), }); +export type LogFieldColumn = rt.TypeOf; + export const logMessageColumnRT = rt.type({ columnId: rt.string, message: rt.array(logMessagePartRT), }); +export type LogMessageColumn = rt.TypeOf; export const logColumnRT = rt.union([logTimestampColumnRT, logFieldColumnRT, logMessageColumnRT]); +export type LogColumn = rt.TypeOf; +/** + * fields + */ export const logEntryContextRT = rt.union([ rt.type({}), rt.type({ 'container.id': rt.string }), rt.type({ 'host.name': rt.string, 'log.file.path': rt.string }), ]); +export type LogEntryContext = rt.TypeOf; + +export const logEntryFieldRT = rt.type({ + field: rt.string, + value: jsonArrayRT, +}); +export type LogEntryField = rt.TypeOf; + +/** + * entry + */ export const logEntryRT = rt.type({ id: rt.string, + index: rt.string, cursor: logEntryCursorRT, columns: rt.array(logColumnRT), context: logEntryContextRT, }); - -export type LogMessageConstantPart = rt.TypeOf; -export type LogMessageFieldPart = rt.TypeOf; -export type LogMessagePart = rt.TypeOf; -export type LogEntryContext = rt.TypeOf; export type LogEntry = rt.TypeOf; -export type LogTimestampColumn = rt.TypeOf; -export type LogFieldColumn = rt.TypeOf; -export type LogMessageColumn = rt.TypeOf; -export type LogColumn = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/log_entry/log_entry_cursor.ts b/x-pack/plugins/infra/common/log_entry/log_entry_cursor.ts index 280403dd5438d..b11a48822e758 100644 --- a/x-pack/plugins/infra/common/log_entry/log_entry_cursor.ts +++ b/x-pack/plugins/infra/common/log_entry/log_entry_cursor.ts @@ -11,9 +11,23 @@ export const logEntryCursorRT = rt.type({ time: rt.number, tiebreaker: rt.number, }); - export type LogEntryCursor = rt.TypeOf; +export const logEntryBeforeCursorRT = rt.type({ + before: rt.union([logEntryCursorRT, rt.literal('last')]), +}); +export type LogEntryBeforeCursor = rt.TypeOf; + +export const logEntryAfterCursorRT = rt.type({ + after: rt.union([logEntryCursorRT, rt.literal('first')]), +}); +export type LogEntryAfterCursor = rt.TypeOf; + +export const logEntryAroundCursorRT = rt.type({ + center: logEntryCursorRT, +}); +export type LogEntryAroundCursor = rt.TypeOf; + export const getLogEntryCursorFromHit = (hit: { sort: [number, number] }) => decodeOrThrow(logEntryCursorRT)({ time: hit.sort[0], diff --git a/x-pack/plugins/infra/common/search_strategies/log_entries/log_entries.ts b/x-pack/plugins/infra/common/search_strategies/log_entries/log_entries.ts new file mode 100644 index 0000000000000..b2a879c3b72fd --- /dev/null +++ b/x-pack/plugins/infra/common/search_strategies/log_entries/log_entries.ts @@ -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 * as rt from 'io-ts'; +import { DslQuery } from '../../../../../../src/plugins/data/common'; +import { logSourceColumnConfigurationRT } from '../../http_api/log_sources'; +import { + logEntryAfterCursorRT, + logEntryBeforeCursorRT, + logEntryCursorRT, + logEntryRT, +} from '../../log_entry'; +import { JsonObject, jsonObjectRT } from '../../typed_json'; +import { searchStrategyErrorRT } from '../common/errors'; + +export const LOG_ENTRIES_SEARCH_STRATEGY = 'infra-log-entries'; + +const logEntriesBaseSearchRequestParamsRT = rt.intersection([ + rt.type({ + sourceId: rt.string, + startTimestamp: rt.number, + endTimestamp: rt.number, + size: rt.number, + }), + rt.partial({ + query: jsonObjectRT, + columns: rt.array(logSourceColumnConfigurationRT), + highlightPhrase: rt.string, + }), +]); + +export const logEntriesBeforeSearchRequestParamsRT = rt.intersection([ + logEntriesBaseSearchRequestParamsRT, + logEntryBeforeCursorRT, +]); + +export const logEntriesAfterSearchRequestParamsRT = rt.intersection([ + logEntriesBaseSearchRequestParamsRT, + logEntryAfterCursorRT, +]); + +export const logEntriesSearchRequestParamsRT = rt.union([ + logEntriesBaseSearchRequestParamsRT, + logEntriesBeforeSearchRequestParamsRT, + logEntriesAfterSearchRequestParamsRT, +]); + +export type LogEntriesSearchRequestParams = rt.TypeOf; + +export type LogEntriesSearchRequestQuery = JsonObject | DslQuery; + +export const logEntriesSearchResponsePayloadRT = rt.intersection([ + rt.type({ + data: rt.intersection([ + rt.type({ + entries: rt.array(logEntryRT), + topCursor: rt.union([logEntryCursorRT, rt.null]), + bottomCursor: rt.union([logEntryCursorRT, rt.null]), + }), + rt.partial({ + hasMoreBefore: rt.boolean, + hasMoreAfter: rt.boolean, + }), + ]), + }), + rt.partial({ + errors: rt.array(searchStrategyErrorRT), + }), +]); + +export type LogEntriesSearchResponsePayload = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/search_strategies/log_entries/log_entry.ts b/x-pack/plugins/infra/common/search_strategies/log_entries/log_entry.ts index af6bd203f980e..986f6baf04488 100644 --- a/x-pack/plugins/infra/common/search_strategies/log_entries/log_entry.ts +++ b/x-pack/plugins/infra/common/search_strategies/log_entries/log_entry.ts @@ -5,8 +5,7 @@ */ import * as rt from 'io-ts'; -import { logEntryCursorRT } from '../../log_entry'; -import { jsonArrayRT } from '../../typed_json'; +import { logEntryCursorRT, logEntryFieldRT } from '../../log_entry'; import { searchStrategyErrorRT } from '../common/errors'; export const LOG_ENTRY_SEARCH_STRATEGY = 'infra-log-entry'; @@ -18,18 +17,11 @@ export const logEntrySearchRequestParamsRT = rt.type({ export type LogEntrySearchRequestParams = rt.TypeOf; -const logEntryFieldRT = rt.type({ - field: rt.string, - value: jsonArrayRT, -}); - -export type LogEntryField = rt.TypeOf; - export const logEntryRT = rt.type({ id: rt.string, index: rt.string, fields: rt.array(logEntryFieldRT), - key: logEntryCursorRT, + cursor: logEntryCursorRT, }); export type LogEntry = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/typed_json.ts b/x-pack/plugins/infra/common/typed_json.ts index f3e7608910e09..5aec8d3eaf2cc 100644 --- a/x-pack/plugins/infra/common/typed_json.ts +++ b/x-pack/plugins/infra/common/typed_json.ts @@ -7,6 +7,8 @@ import * as rt from 'io-ts'; import { JsonArray, JsonObject, JsonValue } from '../../../../src/plugins/kibana_utils/common'; +export { JsonArray, JsonObject, JsonValue }; + export const jsonScalarRT = rt.union([rt.null, rt.boolean, rt.number, rt.string]); export const jsonValueRT: rt.Type = rt.recursion('JsonValue', () => diff --git a/x-pack/plugins/infra/docs/arch.md b/x-pack/plugins/infra/docs/arch.md index f3d7312a3491d..89b00cd19d1d9 100644 --- a/x-pack/plugins/infra/docs/arch.md +++ b/x-pack/plugins/infra/docs/arch.md @@ -7,7 +7,7 @@ In this arch, we use 3 main terms to describe the code: - **Libs / Domain Libs** - Business logic & data formatting (though complex formatting might call utils) - **Adapters** - code that directly calls 3rd party APIs and data sources, exposing clean easy to stub APIs - **Composition Files** - composes adapters into libs based on where the code is running -- **Implementation layer** - The API such as rest endpoints or graphql schema on the server, and the state management / UI on the client +- **Implementation layer** - The API such as rest endpoints on the server, and the state management / UI on the client ## Arch Visual Example @@ -85,7 +85,7 @@ An example structure might be... | | | | |-- kibana_angular // if an adapter has more than one file... | | | | | |-- index.html | | | | | |-- index.ts - | | | | | + | | | | | | | | | |-- ui_harness.ts | | | | | | |-- domains diff --git a/x-pack/plugins/infra/docs/arch_client.md b/x-pack/plugins/infra/docs/arch_client.md index cdc4746357216..b40c9aaf1ff58 100644 --- a/x-pack/plugins/infra/docs/arch_client.md +++ b/x-pack/plugins/infra/docs/arch_client.md @@ -26,7 +26,7 @@ However, components that tweak EUI should go into `/public/components/eui/${comp If using an EUI component that has not yet been typed, types should be placed into `/types/eui.d.ts` -## Containers (Also: [see GraphQL docs](docs/graphql.md)) +## Containers - HOC's based on Apollo. - One folder per data type e.g. `host`. Folder name should be singular. diff --git a/x-pack/plugins/infra/docs/graphql.md b/x-pack/plugins/infra/docs/graphql.md deleted file mode 100644 index 5584a5ce7c0d1..0000000000000 --- a/x-pack/plugins/infra/docs/graphql.md +++ /dev/null @@ -1,53 +0,0 @@ -# GraphQL In Infra UI - -- The combined graphql schema collected from both the `public` and `server` directories is exported to `common/all.gql_schema.ts` for the purpose of automatic type generation only. - -## Server - -- Under `/server/graphql` there are files for each domain of data's graph schema and resolvers. - - Each file has 2 exports `${domain}Schema` e.g. `fieldsSchema`, and `create${domain}Resolvers` e.g. `createFieldResolvers` -- `/server/infra_server.ts` imports all schema and resolvers and passing the full schema to the server -- Resolvers should be used to call composed libs, rather than directly performing any meaningful amount of data processing. -- Resolvers should, however, only pass the required data into libs; that is to say all args for example would not be passed into a lib unless all were needed. - -## Client - -- Under `/public/containers/${domain}/` there is a file for each container. Each file has two exports, the query name e.g. `AllHosts` and the apollo HOC in the pattern of `with${queryName}` e.g. `withAllHosts`. This is done for two reasons: - - 1. It makes the code uniform, thus easier to reason about later. - 2. If reformatting the data using a transform, it lets us re-type the data clearly. - -- Containers should use the apollo props callback to pass ONLY the props and data needed to children. e.g. - - ```ts - import { Hosts, Pods, HostsAndPods } from '../../common/types'; - - // used to generate the `HostsAndPods` type imported above - export const hostsAndPods = gql` - # ... - `; - - type HostsAndPodsProps = { - hosts: Hosts; - pods: Pods; - } - - export const withHostsAndPods = graphql< - {}, - HostsAndPods.Query, - HostsAndPods.Variables, - HostsAndPodsProps - >(hostsAndPods, { - props: ({ data, ownProps }) => ({ - hosts: hostForMap(data && data.hosts ? data.hosts : []), -  pods: podsFromHosts(data && data.hosts ? data.hosts : []) - ...ownProps, - }), - }); - ``` - - as `ownProps` are the props passed to the wrapped component, they should just be forwarded. - -## Types - -- The command `yarn build-graphql-types` derives the schema, query and mutation types and stores them in `common/types.ts` for use on both the client and server. diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/__snapshots__/expression_row.test.tsx.snap b/x-pack/plugins/infra/public/alerting/metric_threshold/components/__snapshots__/expression_row.test.tsx.snap new file mode 100644 index 0000000000000..1571b981632d7 --- /dev/null +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/__snapshots__/expression_row.test.tsx.snap @@ -0,0 +1,22 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ExpressionRow should render a helpText for the of expression 1`] = ` + + + , + } + } +/> +`; diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.test.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.test.tsx index 8fae6c6a51343..ea0c5a6e54b53 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.test.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.test.tsx @@ -81,4 +81,21 @@ describe('ExpressionRow', () => { wrapper.html().match('0.5') ?? []; expect(valueMatch).toBeTruthy(); }); + + it('should render a helpText for the of expression', async () => { + const expression = { + metric: 'system.load.1', + comparator: Comparator.GT, + threshold: [0.5], + timeSize: 1, + timeUnit: 'm', + aggType: 'avg', + } as MetricExpression; + + const { wrapper } = await setup(expression as MetricExpression); + + const helpText = wrapper.find('[data-test-subj="ofExpression"]').prop('helpText'); + + expect(helpText).toMatchSnapshot(); + }); }); diff --git a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.tsx b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.tsx index cdab53c92d32c..62c373a7a341f 100644 --- a/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.tsx +++ b/x-pack/plugins/infra/public/alerting/metric_threshold/components/expression_row.tsx @@ -5,7 +5,15 @@ */ import React, { useCallback, useState, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; -import { EuiFlexGroup, EuiFlexItem, EuiButtonIcon, EuiSpacer, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiButtonIcon, + EuiSpacer, + EuiText, + EuiLink, +} from '@elastic/eui'; import { IFieldType } from 'src/plugins/data/public'; import { pctToDecimal, decimalToPct } from '../../../../common/utils/corrected_percent_convert'; import { @@ -154,6 +162,26 @@ export const ExpressionRow: React.FC = (props) => { aggType={aggType} errors={errors} onChangeSelectedAggField={updateMetric} + helpText={ + + + + ), + }} + /> + } + data-test-subj="ofExpression" /> )} diff --git a/x-pack/plugins/infra/public/apps/common_providers.tsx b/x-pack/plugins/infra/public/apps/common_providers.tsx index ebfa412410dd7..ad1c1e1129de4 100644 --- a/x-pack/plugins/infra/public/apps/common_providers.tsx +++ b/x-pack/plugins/infra/public/apps/common_providers.tsx @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ApolloClient } from 'apollo-client'; import { AppMountParameters, CoreStart } from 'kibana/public'; import React, { useMemo } from 'react'; import { @@ -15,32 +14,28 @@ import { EuiThemeProvider } from '../../../../../src/plugins/kibana_react/common import { TriggersAndActionsUIPublicPluginStart } from '../../../triggers_actions_ui/public'; import { createKibanaContextForPlugin } from '../hooks/use_kibana'; import { InfraClientStartDeps } from '../types'; -import { ApolloClientContext } from '../utils/apollo_context'; import { HeaderActionMenuProvider } from '../utils/header_action_menu_provider'; import { NavigationWarningPromptProvider } from '../utils/navigation_warning_prompt'; import { TriggersActionsProvider } from '../utils/triggers_actions_context'; import { Storage } from '../../../../../src/plugins/kibana_utils/public'; export const CommonInfraProviders: React.FC<{ - apolloClient: ApolloClient<{}>; appName: string; storage: Storage; triggersActionsUI: TriggersAndActionsUIPublicPluginStart; setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; -}> = ({ apolloClient, children, triggersActionsUI, setHeaderActionMenu, appName, storage }) => { +}> = ({ children, triggersActionsUI, setHeaderActionMenu, appName, storage }) => { const [darkMode] = useUiSetting$('theme:darkMode'); return ( - - - - - {children} - - - - + + + + {children} + + + ); }; diff --git a/x-pack/plugins/infra/public/apps/logs_app.tsx b/x-pack/plugins/infra/public/apps/logs_app.tsx index 381c75c4b9a27..8a6a2e273f2c8 100644 --- a/x-pack/plugins/infra/public/apps/logs_app.tsx +++ b/x-pack/plugins/infra/public/apps/logs_app.tsx @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ApolloClient } from 'apollo-client'; import { History } from 'history'; import { CoreStart } from 'kibana/public'; import React from 'react'; @@ -17,7 +16,6 @@ import { NotFoundPage } from '../pages/404'; import { LinkToLogsPage } from '../pages/link_to/link_to_logs'; import { LogsPage } from '../pages/logs'; import { InfraClientStartDeps } from '../types'; -import { createApolloClient } from '../utils/apollo_client'; import { CommonInfraProviders, CoreProviders } from './common_providers'; import { prepareMountElement } from './common_styles'; @@ -26,14 +24,12 @@ export const renderApp = ( plugins: InfraClientStartDeps, { element, history, setHeaderActionMenu }: AppMountParameters ) => { - const apolloClient = createApolloClient(core.http.fetch); const storage = new Storage(window.localStorage); prepareMountElement(element); ReactDOM.render( ; core: CoreStart; history: History; plugins: InfraClientStartDeps; setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; storage: Storage; -}> = ({ apolloClient, core, history, plugins, setHeaderActionMenu, storage }) => { +}> = ({ core, history, plugins, setHeaderActionMenu, storage }) => { const uiCapabilities = core.application.capabilities; return ( { - const apolloClient = createApolloClient(core.http.fetch); const storage = new Storage(window.localStorage); prepareMountElement(element); ReactDOM.render( ; core: CoreStart; history: History; plugins: InfraClientStartDeps; setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; storage: Storage; -}> = ({ apolloClient, core, history, plugins, setHeaderActionMenu, storage }) => { +}> = ({ core, history, plugins, setHeaderActionMenu, storage }) => { const uiCapabilities = core.application.capabilities; return ( { + return defer(() => { + switch (options.strategy) { + case LOG_ENTRIES_SEARCH_STRATEGY: + if (params.after?.time === params.endTimestamp || params.before?.time === params.startTimestamp) { + return of({ + id: 'EMPTY_FAKE_RESPONSE', + total: 1, + loaded: 1, + isRunning: false, + isPartial: false, + rawResponse: ENTRIES_EMPTY, + }); + } else { + const entries = generateFakeEntries( + 200, + params.startTimestamp, + params.endTimestamp, + params.columns || DEFAULT_SOURCE_CONFIGURATION.data.configuration.logColumns + ); + return of({ + id: 'FAKE_RESPONSE', + total: 1, + loaded: 1, + isRunning: false, + isPartial: false, + rawResponse: { + data: { + entries, + topCursor: entries[0].cursor, + bottomCursor: entries[entries.length - 1].cursor, + hasMoreBefore: false, + }, + errors: [], + } + }); + } + default: + return of({ + id: 'FAKE_RESPONSE', + rawResponse: {}, + }); + } + }).pipe(delay(2000)); + }, + }, +}; + + export const fetch = function (url, params) { switch (url) { case '/api/infra/log_source_configurations/default': return DEFAULT_SOURCE_CONFIGURATION; - case '/api/log_entries/entries': - const body = JSON.parse(params.body); - if (body.after?.time === body.endTimestamp || body.before?.time === body.startTimestamp) { - return ENTRIES_EMPTY; - } else { - const entries = generateFakeEntries( - 200, - body.startTimestamp, - body.endTimestamp, - body.columns || DEFAULT_SOURCE_CONFIGURATION.data.configuration.logColumns - ); - return { - data: { - entries, - topCursor: entries[0].cursor, - bottomCursor: entries[entries.length - 1].cursor, - hasMoreBefore: false, - }, - }; - } default: return {}; } @@ -67,7 +100,7 @@ export const Template = (args) => ; (story) => ( - + {story()} diff --git a/x-pack/plugins/infra/public/components/log_stream/log_stream.tsx b/x-pack/plugins/infra/public/components/log_stream/log_stream.tsx index b7410fda6f6fd..ab9bc0099f196 100644 --- a/x-pack/plugins/infra/public/components/log_stream/log_stream.tsx +++ b/x-pack/plugins/infra/public/components/log_stream/log_stream.tsx @@ -101,14 +101,14 @@ Read more at https://github.com/elastic/kibana/blob/master/src/plugins/kibana_re // Internal state const { - loadingState, - pageLoadingState, entries, - hasMoreBefore, - hasMoreAfter, fetchEntries, - fetchPreviousEntries, fetchNextEntries, + fetchPreviousEntries, + hasMoreAfter, + hasMoreBefore, + isLoadingMore, + isReloading, } = useLogStream({ sourceId, startTimestamp, @@ -118,12 +118,6 @@ Read more at https://github.com/elastic/kibana/blob/master/src/plugins/kibana_re columns: customColumns, }); - // Derived state - const isReloading = - isLoadingSourceConfiguration || loadingState === 'uninitialized' || loadingState === 'loading'; - - const isLoadingMore = pageLoadingState === 'loading'; - const columnConfigurations = useMemo(() => { return sourceConfiguration ? customColumns ?? sourceConfiguration.configuration.logColumns : []; }, [sourceConfiguration, customColumns]); @@ -177,7 +171,7 @@ Read more at https://github.com/elastic/kibana/blob/master/src/plugins/kibana_re items={streamItems} scale="medium" wrap={true} - isReloading={isReloading} + isReloading={isLoadingSourceConfiguration || isReloading} isLoadingMore={isLoadingMore} hasMoreBeforeStart={hasMoreBefore} hasMoreAfterEnd={hasMoreAfter} diff --git a/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.test.tsx b/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.test.tsx index f578292d6d6fc..447e6afbbf1fd 100644 --- a/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.test.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_actions_menu.test.tsx @@ -32,7 +32,7 @@ describe('LogEntryActionsMenu component', () => { fields: [{ field: 'host.ip', value: ['HOST_IP'] }], id: 'ITEM_ID', index: 'INDEX', - key: { + cursor: { time: 0, tiebreaker: 0, }, @@ -62,7 +62,7 @@ describe('LogEntryActionsMenu component', () => { fields: [{ field: 'container.id', value: ['CONTAINER_ID'] }], id: 'ITEM_ID', index: 'INDEX', - key: { + cursor: { time: 0, tiebreaker: 0, }, @@ -92,7 +92,7 @@ describe('LogEntryActionsMenu component', () => { fields: [{ field: 'kubernetes.pod.uid', value: ['POD_UID'] }], id: 'ITEM_ID', index: 'INDEX', - key: { + cursor: { time: 0, tiebreaker: 0, }, @@ -126,7 +126,7 @@ describe('LogEntryActionsMenu component', () => { ], id: 'ITEM_ID', index: 'INDEX', - key: { + cursor: { time: 0, tiebreaker: 0, }, @@ -158,7 +158,7 @@ describe('LogEntryActionsMenu component', () => { fields: [], id: 'ITEM_ID', index: 'INDEX', - key: { + cursor: { time: 0, tiebreaker: 0, }, @@ -192,7 +192,7 @@ describe('LogEntryActionsMenu component', () => { fields: [{ field: 'trace.id', value: ['1234567'] }], id: 'ITEM_ID', index: 'INDEX', - key: { + cursor: { time: 0, tiebreaker: 0, }, @@ -226,7 +226,7 @@ describe('LogEntryActionsMenu component', () => { ], id: 'ITEM_ID', index: 'INDEX', - key: { + cursor: { time: 0, tiebreaker: 0, }, @@ -256,7 +256,7 @@ describe('LogEntryActionsMenu component', () => { fields: [], id: 'ITEM_ID', index: 'INDEX', - key: { + cursor: { time: 0, tiebreaker: 0, }, diff --git a/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_fields_table.tsx b/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_fields_table.tsx index 44e9902e0413f..b3c80a3a4924a 100644 --- a/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_fields_table.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_entry_flyout/log_entry_fields_table.tsx @@ -7,10 +7,8 @@ import { EuiBasicTableColumn, EuiInMemoryTable } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useMemo } from 'react'; -import { - LogEntry, - LogEntryField, -} from '../../../../common/search_strategies/log_entries/log_entry'; +import { LogEntryField } from '../../../../common/log_entry'; +import { LogEntry } from '../../../../common/search_strategies/log_entries/log_entry'; import { TimeKey } from '../../../../common/time'; import { FieldValue } from '../log_text_stream/field_value'; @@ -22,7 +20,7 @@ export const LogEntryFieldsTable: React.FC<{ () => onSetFieldFilter ? (field: LogEntryField) => () => { - onSetFieldFilter?.(`${field.field}:"${field.value}"`, logEntry.id, logEntry.key); + onSetFieldFilter?.(`${field.field}:"${field.value}"`, logEntry.id, logEntry.cursor); } : undefined, [logEntry, onSetFieldFilter] diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/item.ts b/x-pack/plugins/infra/public/components/logging/log_text_stream/item.ts index b0ff36574bede..13d5b7b889465 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/item.ts +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/item.ts @@ -5,7 +5,6 @@ */ import { bisector } from 'd3-array'; - import { compareToTimeKey, TimeKey } from '../../../../common/time'; import { LogEntry } from '../../../../common/log_entry'; diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_row.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_row.tsx index 1a472df2b5c90..036818317011c 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_row.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_row.tsx @@ -7,7 +7,6 @@ import React, { memo, useState, useCallback, useMemo } from 'react'; import { i18n } from '@kbn/i18n'; import { isEmpty } from 'lodash'; - import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; import { useUiTracker } from '../../../../../observability/public'; import { isTimestampColumn } from '../../../utils/log_entry'; diff --git a/x-pack/plugins/infra/public/components/source_configuration/source_configuration_form_state.tsx b/x-pack/plugins/infra/public/components/source_configuration/source_configuration_form_state.tsx index 262649e20709b..4bda3b9572ce4 100644 --- a/x-pack/plugins/infra/public/components/source_configuration/source_configuration_form_state.tsx +++ b/x-pack/plugins/infra/public/components/source_configuration/source_configuration_form_state.tsx @@ -5,12 +5,12 @@ */ import { useCallback, useMemo } from 'react'; +import { InfraSourceConfiguration } from '../../../common/http_api/source_api'; import { useIndicesConfigurationFormState } from './indices_configuration_form_state'; import { useLogColumnsConfigurationFormState } from './log_columns_configuration_form_state'; -import { SourceConfiguration } from '../../utils/source_configuration'; -export const useSourceConfigurationFormState = (configuration?: SourceConfiguration) => { +export const useSourceConfigurationFormState = (configuration?: InfraSourceConfiguration) => { const indicesConfigurationFormState = useIndicesConfigurationFormState({ initialFormState: useMemo( () => diff --git a/x-pack/plugins/infra/public/containers/logs/log_entry.ts b/x-pack/plugins/infra/public/containers/logs/log_entry.ts index af8618b8be565..60000e0b8baba 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_entry.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_entry.ts @@ -11,7 +11,11 @@ import { logEntrySearchResponsePayloadRT, LOG_ENTRY_SEARCH_STRATEGY, } from '../../../common/search_strategies/log_entries/log_entry'; -import { useDataSearch, useLatestPartialDataSearchResponse } from '../../utils/data_search'; +import { + normalizeDataSearchResponses, + useDataSearch, + useLatestPartialDataSearchResponse, +} from '../../utils/data_search'; export const useLogEntry = ({ sourceId, @@ -31,6 +35,7 @@ export const useLogEntry = ({ } : null; }, [sourceId, logEntryId]), + parseResponses: parseLogEntrySearchResponses, }); const { @@ -41,11 +46,7 @@ export const useLogEntry = ({ latestResponseErrors, loaded, total, - } = useLatestPartialDataSearchResponse( - logEntrySearchRequests$, - null, - decodeLogEntrySearchResponse - ); + } = useLatestPartialDataSearchResponse(logEntrySearchRequests$); return { cancelRequest, @@ -59,4 +60,7 @@ export const useLogEntry = ({ }; }; -const decodeLogEntrySearchResponse = decodeOrThrow(logEntrySearchResponsePayloadRT); +const parseLogEntrySearchResponses = normalizeDataSearchResponses( + null, + decodeOrThrow(logEntrySearchResponsePayloadRT) +); diff --git a/x-pack/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.gql_query.ts b/x-pack/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.gql_query.ts deleted file mode 100644 index 9d9fab5875427..0000000000000 --- a/x-pack/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.gql_query.ts +++ /dev/null @@ -1,42 +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'; - -import { sharedFragments } from '../../../../common/graphql/shared'; - -export const logEntryHighlightsQuery = gql` - query LogEntryHighlightsQuery( - $sourceId: ID = "default" - $startKey: InfraTimeKeyInput! - $endKey: InfraTimeKeyInput! - $filterQuery: String - $highlights: [InfraLogEntryHighlightInput!]! - ) { - source(id: $sourceId) { - id - logEntryHighlights( - startKey: $startKey - endKey: $endKey - filterQuery: $filterQuery - highlights: $highlights - ) { - start { - ...InfraTimeKeyFields - } - end { - ...InfraTimeKeyFields - } - entries { - ...InfraLogEntryHighlightFields - } - } - } - } - - ${sharedFragments.InfraTimeKey} - ${sharedFragments.InfraLogEntryHighlightFields} -`; diff --git a/x-pack/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.tsx b/x-pack/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.tsx index fb72874df5409..caac28a0756a1 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.tsx +++ b/x-pack/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.tsx @@ -5,13 +5,12 @@ */ import { useEffect, useMemo, useState } from 'react'; - -import { TimeKey } from '../../../../common/time'; -import { useTrackedPromise } from '../../../utils/use_tracked_promise'; -import { fetchLogEntriesHighlights } from './api/fetch_log_entries_highlights'; import { LogEntriesHighlightsResponse } from '../../../../common/http_api'; import { LogEntry } from '../../../../common/log_entry'; +import { TimeKey } from '../../../../common/time'; import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; +import { useTrackedPromise } from '../../../utils/use_tracked_promise'; +import { fetchLogEntriesHighlights } from './api/fetch_log_entries_highlights'; export const useLogEntryHighlights = ( sourceId: string, diff --git a/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts b/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts index 1d9a7a1b1d777..8343525c82862 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts @@ -4,15 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useMemo, useEffect } from 'react'; -import useSetState from 'react-use/lib/useSetState'; +import { useCallback, useEffect, useMemo } from 'react'; import usePrevious from 'react-use/lib/usePrevious'; +import useSetState from 'react-use/lib/useSetState'; import { esKuery, esQuery, Query } from '../../../../../../../src/plugins/data/public'; -import { fetchLogEntries } from '../log_entries/api/fetch_log_entries'; -import { useTrackedPromise } from '../../../utils/use_tracked_promise'; -import { LogEntryCursor, LogEntry } from '../../../../common/log_entry'; -import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; +import { LogEntry, LogEntryCursor } from '../../../../common/log_entry'; +import { useSubscription } from '../../../utils/use_observable'; import { LogSourceConfigurationProperties } from '../log_source'; +import { useFetchLogEntriesAfter } from './use_fetch_log_entries_after'; +import { useFetchLogEntriesAround } from './use_fetch_log_entries_around'; +import { useFetchLogEntriesBefore } from './use_fetch_log_entries_before'; interface LogStreamProps { sourceId: string; @@ -31,16 +32,6 @@ interface LogStreamState { hasMoreAfter: boolean; } -type LoadingState = 'uninitialized' | 'loading' | 'success' | 'error'; - -interface LogStreamReturn extends LogStreamState { - fetchEntries: () => void; - fetchPreviousEntries: () => void; - fetchNextEntries: () => void; - loadingState: LoadingState; - pageLoadingState: LoadingState; -} - const INITIAL_STATE: LogStreamState = { entries: [], topCursor: null, @@ -50,11 +41,7 @@ const INITIAL_STATE: LogStreamState = { hasMoreAfter: true, }; -const EMPTY_DATA = { - entries: [], - topCursor: null, - bottomCursor: null, -}; +const LOG_ENTRIES_CHUNK_SIZE = 200; export function useLogStream({ sourceId, @@ -63,8 +50,7 @@ export function useLogStream({ query, center, columns, -}: LogStreamProps): LogStreamReturn { - const { services } = useKibanaContextForPlugin(); +}: LogStreamProps) { const [state, setState] = useSetState(INITIAL_STATE); // Ensure the pagination keeps working when the timerange gets extended @@ -85,175 +71,151 @@ export function useLogStream({ const parsedQuery = useMemo(() => { if (!query) { - return null; - } - - let q; - - if (typeof query === 'string') { - q = esKuery.toElasticsearchQuery(esKuery.fromKueryExpression(query)); + return undefined; + } else if (typeof query === 'string') { + return esKuery.toElasticsearchQuery(esKuery.fromKueryExpression(query)); } else if (query.language === 'kuery') { - q = esKuery.toElasticsearchQuery(esKuery.fromKueryExpression(query.query as string)); + return esKuery.toElasticsearchQuery(esKuery.fromKueryExpression(query.query as string)); } else if (query.language === 'lucene') { - q = esQuery.luceneStringToDsl(query.query as string); + return esQuery.luceneStringToDsl(query.query as string); + } else { + return undefined; } - - return JSON.stringify(q); }, [query]); - // Callbacks - const [entriesPromise, fetchEntries] = useTrackedPromise( - { - cancelPreviousOn: 'creation', - createPromise: () => { - setState(INITIAL_STATE); - const fetchPosition = center ? { center } : { before: 'last' }; + const commonFetchArguments = useMemo( + () => ({ + sourceId, + startTimestamp, + endTimestamp, + query: parsedQuery, + columnOverrides: columns, + }), + [columns, endTimestamp, parsedQuery, sourceId, startTimestamp] + ); - return fetchLogEntries( - { - sourceId, - startTimestamp, - endTimestamp, - query: parsedQuery, - columns, - ...fetchPosition, - }, - services.http.fetch - ); - }, - onResolve: ({ data }) => { + const { + fetchLogEntriesAround, + isRequestRunning: isLogEntriesAroundRequestRunning, + logEntriesAroundSearchResponses$, + } = useFetchLogEntriesAround(commonFetchArguments); + + useSubscription(logEntriesAroundSearchResponses$, { + next: ({ before, after, combined }) => { + if ((before.response.data != null || after?.response.data != null) && !combined.isPartial) { setState((prevState) => ({ - ...data, - hasMoreBefore: data.hasMoreBefore ?? prevState.hasMoreBefore, - hasMoreAfter: data.hasMoreAfter ?? prevState.hasMoreAfter, + ...prevState, + entries: combined.entries, + hasMoreAfter: combined.hasMoreAfter ?? prevState.hasMoreAfter, + hasMoreBefore: combined.hasMoreAfter ?? prevState.hasMoreAfter, + bottomCursor: combined.bottomCursor, + topCursor: combined.topCursor, })); - }, + } }, - [sourceId, startTimestamp, endTimestamp, query] - ); + }); - const [previousEntriesPromise, fetchPreviousEntries] = useTrackedPromise( - { - cancelPreviousOn: 'creation', - createPromise: () => { - if (state.topCursor === null) { - throw new Error( - 'useLogState: Cannot fetch previous entries. No cursor is set.\nEnsure you have called `fetchEntries` at least once.' - ); - } + const { + fetchLogEntriesBefore, + isRequestRunning: isLogEntriesBeforeRequestRunning, + logEntriesBeforeSearchResponse$, + } = useFetchLogEntriesBefore(commonFetchArguments); - if (!state.hasMoreBefore) { - return Promise.resolve({ data: EMPTY_DATA }); - } - - return fetchLogEntries( - { - sourceId, - startTimestamp, - endTimestamp, - query: parsedQuery, - before: state.topCursor, - }, - services.http.fetch - ); - }, - onResolve: ({ data }) => { - if (!data.entries.length) { - return; - } + useSubscription(logEntriesBeforeSearchResponse$, { + next: ({ response: { data, isPartial } }) => { + if (data != null && !isPartial) { setState((prevState) => ({ + ...prevState, entries: [...data.entries, ...prevState.entries], hasMoreBefore: data.hasMoreBefore ?? prevState.hasMoreBefore, topCursor: data.topCursor ?? prevState.topCursor, + bottomCursor: prevState.bottomCursor ?? data.bottomCursor, })); - }, + } }, - [sourceId, startTimestamp, endTimestamp, query, state.topCursor] - ); + }); - const [nextEntriesPromise, fetchNextEntries] = useTrackedPromise( - { - cancelPreviousOn: 'creation', - createPromise: () => { - if (state.bottomCursor === null) { - throw new Error( - 'useLogState: Cannot fetch next entries. No cursor is set.\nEnsure you have called `fetchEntries` at least once.' - ); - } + const fetchPreviousEntries = useCallback(() => { + if (state.topCursor === null) { + throw new Error( + 'useLogState: Cannot fetch previous entries. No cursor is set.\nEnsure you have called `fetchEntries` at least once.' + ); + } + + if (!state.hasMoreBefore) { + return; + } - if (!state.hasMoreAfter) { - return Promise.resolve({ data: EMPTY_DATA }); - } + fetchLogEntriesBefore(state.topCursor, LOG_ENTRIES_CHUNK_SIZE); + }, [fetchLogEntriesBefore, state.topCursor, state.hasMoreBefore]); - return fetchLogEntries( - { - sourceId, - startTimestamp, - endTimestamp, - query: parsedQuery, - after: state.bottomCursor, - }, - services.http.fetch - ); - }, - onResolve: ({ data }) => { - if (!data.entries.length) { - return; - } + const { + fetchLogEntriesAfter, + isRequestRunning: isLogEntriesAfterRequestRunning, + logEntriesAfterSearchResponse$, + } = useFetchLogEntriesAfter(commonFetchArguments); + + useSubscription(logEntriesAfterSearchResponse$, { + next: ({ response: { data, isPartial } }) => { + if (data != null && !isPartial) { setState((prevState) => ({ + ...prevState, entries: [...prevState.entries, ...data.entries], hasMoreAfter: data.hasMoreAfter ?? prevState.hasMoreAfter, + topCursor: prevState.topCursor ?? data.topCursor, bottomCursor: data.bottomCursor ?? prevState.bottomCursor, })); - }, + } }, - [sourceId, startTimestamp, endTimestamp, query, state.bottomCursor] - ); - - const loadingState = useMemo( - () => convertPromiseStateToLoadingState(entriesPromise.state), - [entriesPromise.state] - ); + }); - const pageLoadingState = useMemo(() => { - const states = [previousEntriesPromise.state, nextEntriesPromise.state]; - - if (states.includes('pending')) { - return 'loading'; + const fetchNextEntries = useCallback(() => { + if (state.bottomCursor === null) { + throw new Error( + 'useLogState: Cannot fetch next entries. No cursor is set.\nEnsure you have called `fetchEntries` at least once.' + ); } - if (states.includes('rejected')) { - return 'error'; + if (!state.hasMoreAfter) { + return; } - if (states.includes('resolved')) { - return 'success'; + fetchLogEntriesAfter(state.bottomCursor, LOG_ENTRIES_CHUNK_SIZE); + }, [fetchLogEntriesAfter, state.bottomCursor, state.hasMoreAfter]); + + const fetchEntries = useCallback(() => { + setState(INITIAL_STATE); + + if (center) { + fetchLogEntriesAround(center, LOG_ENTRIES_CHUNK_SIZE); + } else { + fetchLogEntriesBefore('last', LOG_ENTRIES_CHUNK_SIZE); } + }, [center, fetchLogEntriesAround, fetchLogEntriesBefore, setState]); + + const isReloading = useMemo( + () => + isLogEntriesAroundRequestRunning || + (state.bottomCursor == null && state.topCursor == null && isLogEntriesBeforeRequestRunning), + [ + isLogEntriesAroundRequestRunning, + isLogEntriesBeforeRequestRunning, + state.bottomCursor, + state.topCursor, + ] + ); - return 'uninitialized'; - }, [previousEntriesPromise.state, nextEntriesPromise.state]); + const isLoadingMore = useMemo( + () => isLogEntriesBeforeRequestRunning || isLogEntriesAfterRequestRunning, + [isLogEntriesAfterRequestRunning, isLogEntriesBeforeRequestRunning] + ); return { ...state, fetchEntries, - fetchPreviousEntries, fetchNextEntries, - loadingState, - pageLoadingState, + fetchPreviousEntries, + isLoadingMore, + isReloading, }; } - -function convertPromiseStateToLoadingState( - state: 'uninitialized' | 'pending' | 'resolved' | 'rejected' -): LoadingState { - switch (state) { - case 'uninitialized': - return 'uninitialized'; - case 'pending': - return 'loading'; - case 'resolved': - return 'success'; - case 'rejected': - return 'error'; - } -} diff --git a/x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_after.ts b/x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_after.ts new file mode 100644 index 0000000000000..c7076ec51db6a --- /dev/null +++ b/x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_after.ts @@ -0,0 +1,157 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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 { Observable } from 'rxjs'; +import { exhaustMap } from 'rxjs/operators'; +import { IKibanaSearchRequest } from '../../../../../../../src/plugins/data/public'; +import { LogSourceColumnConfiguration } from '../../../../common/http_api/log_sources'; +import { LogEntryAfterCursor } from '../../../../common/log_entry'; +import { decodeOrThrow } from '../../../../common/runtime_types'; +import { + logEntriesSearchRequestParamsRT, + LogEntriesSearchRequestQuery, + LogEntriesSearchResponsePayload, + logEntriesSearchResponsePayloadRT, + LOG_ENTRIES_SEARCH_STRATEGY, +} from '../../../../common/search_strategies/log_entries/log_entries'; +import { + flattenDataSearchResponseDescriptor, + normalizeDataSearchResponses, + ParsedDataSearchRequestDescriptor, + useDataSearch, + useDataSearchResponseState, +} from '../../../utils/data_search'; +import { useOperator } from '../../../utils/use_observable'; + +export const useLogEntriesAfterRequest = ({ + columnOverrides, + endTimestamp, + highlightPhrase, + query, + sourceId, + startTimestamp, +}: { + columnOverrides?: LogSourceColumnConfiguration[]; + endTimestamp: number; + highlightPhrase?: string; + query?: LogEntriesSearchRequestQuery; + sourceId: string; + startTimestamp: number; +}) => { + const { search: fetchLogEntriesAfter, requests$: logEntriesAfterSearchRequests$ } = useDataSearch( + { + getRequest: useCallback( + (cursor: LogEntryAfterCursor['after'], size: number) => { + return !!sourceId + ? { + request: { + params: logEntriesSearchRequestParamsRT.encode({ + after: cursor, + columns: columnOverrides, + endTimestamp, + highlightPhrase, + query, + size, + sourceId, + startTimestamp, + }), + }, + options: { strategy: LOG_ENTRIES_SEARCH_STRATEGY }, + } + : null; + }, + [columnOverrides, endTimestamp, highlightPhrase, query, sourceId, startTimestamp] + ), + parseResponses: parseLogEntriesAfterSearchResponses, + } + ); + + return { + fetchLogEntriesAfter, + logEntriesAfterSearchRequests$, + }; +}; + +export const useLogEntriesAfterResponse = ( + logEntriesAfterSearchRequests$: Observable< + ParsedDataSearchRequestDescriptor + > +) => { + const logEntriesAfterSearchResponse$ = useOperator( + logEntriesAfterSearchRequests$, + flattenLogEntriesAfterSearchResponse + ); + + const { + cancelRequest, + isRequestRunning, + isResponsePartial, + loaded, + total, + } = useDataSearchResponseState(logEntriesAfterSearchResponse$); + + return { + cancelRequest, + isRequestRunning, + isResponsePartial, + loaded, + logEntriesAfterSearchRequests$, + logEntriesAfterSearchResponse$, + total, + }; +}; + +export const useFetchLogEntriesAfter = ({ + columnOverrides, + endTimestamp, + highlightPhrase, + query, + sourceId, + startTimestamp, +}: { + columnOverrides?: LogSourceColumnConfiguration[]; + endTimestamp: number; + highlightPhrase?: string; + query?: LogEntriesSearchRequestQuery; + sourceId: string; + startTimestamp: number; +}) => { + const { fetchLogEntriesAfter, logEntriesAfterSearchRequests$ } = useLogEntriesAfterRequest({ + columnOverrides, + endTimestamp, + highlightPhrase, + query, + sourceId, + startTimestamp, + }); + + const { + cancelRequest, + isRequestRunning, + isResponsePartial, + loaded, + logEntriesAfterSearchResponse$, + total, + } = useLogEntriesAfterResponse(logEntriesAfterSearchRequests$); + + return { + cancelRequest, + fetchLogEntriesAfter, + isRequestRunning, + isResponsePartial, + loaded, + logEntriesAfterSearchResponse$, + total, + }; +}; + +export const parseLogEntriesAfterSearchResponses = normalizeDataSearchResponses( + null, + decodeOrThrow(logEntriesSearchResponsePayloadRT) +); + +const flattenLogEntriesAfterSearchResponse = exhaustMap(flattenDataSearchResponseDescriptor); diff --git a/x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_around.ts b/x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_around.ts new file mode 100644 index 0000000000000..01f6336e0d5c8 --- /dev/null +++ b/x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_around.ts @@ -0,0 +1,184 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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 { combineLatest, Observable, Subject } from 'rxjs'; +import { last, map, startWith, switchMap } from 'rxjs/operators'; +import { LogSourceColumnConfiguration } from '../../../../common/http_api/log_sources'; +import { LogEntryCursor } from '../../../../common/log_entry'; +import { LogEntriesSearchRequestQuery } from '../../../../common/search_strategies/log_entries/log_entries'; +import { flattenDataSearchResponseDescriptor } from '../../../utils/data_search'; +import { useObservable, useObservableState } from '../../../utils/use_observable'; +import { useLogEntriesAfterRequest } from './use_fetch_log_entries_after'; +import { useLogEntriesBeforeRequest } from './use_fetch_log_entries_before'; + +export const useFetchLogEntriesAround = ({ + columnOverrides, + endTimestamp, + highlightPhrase, + query, + sourceId, + startTimestamp, +}: { + columnOverrides?: LogSourceColumnConfiguration[]; + endTimestamp: number; + highlightPhrase?: string; + query?: LogEntriesSearchRequestQuery; + sourceId: string; + startTimestamp: number; +}) => { + const { fetchLogEntriesBefore } = useLogEntriesBeforeRequest({ + columnOverrides, + endTimestamp, + highlightPhrase, + query, + sourceId, + startTimestamp, + }); + + const { fetchLogEntriesAfter } = useLogEntriesAfterRequest({ + columnOverrides, + endTimestamp, + highlightPhrase, + query, + sourceId, + startTimestamp, + }); + + type LogEntriesBeforeRequest = NonNullable>; + type LogEntriesAfterRequest = NonNullable>; + + const logEntriesAroundSearchRequests$ = useObservable( + () => new Subject<[LogEntriesBeforeRequest, Observable]>(), + [] + ); + + const fetchLogEntriesAround = useCallback( + (cursor: LogEntryCursor, size: number) => { + const logEntriesBeforeSearchRequest = fetchLogEntriesBefore(cursor, Math.floor(size / 2)); + + if (logEntriesBeforeSearchRequest == null) { + return; + } + + const logEntriesAfterSearchRequest$ = flattenDataSearchResponseDescriptor( + logEntriesBeforeSearchRequest + ).pipe( + last(), // in the future we could start earlier if we receive partial results already + map((lastBeforeSearchResponse) => { + const cursorAfter = lastBeforeSearchResponse.response.data?.bottomCursor ?? { + time: cursor.time - 1, + tiebreaker: 0, + }; + + const logEntriesAfterSearchRequest = fetchLogEntriesAfter( + cursorAfter, + Math.ceil(size / 2) + ); + + if (logEntriesAfterSearchRequest == null) { + throw new Error('Failed to create request: no request args given'); + } + + return logEntriesAfterSearchRequest; + }) + ); + + logEntriesAroundSearchRequests$.next([ + logEntriesBeforeSearchRequest, + logEntriesAfterSearchRequest$, + ]); + }, + [fetchLogEntriesAfter, fetchLogEntriesBefore, logEntriesAroundSearchRequests$] + ); + + const logEntriesAroundSearchResponses$ = useObservable( + (inputs$) => + inputs$.pipe( + switchMap(([currentSearchRequests$]) => + currentSearchRequests$.pipe( + switchMap(([beforeRequest, afterRequest$]) => { + const beforeResponse$ = flattenDataSearchResponseDescriptor(beforeRequest); + const afterResponse$ = afterRequest$.pipe( + switchMap(flattenDataSearchResponseDescriptor), + startWith(undefined) // emit "before" response even if "after" hasn't started yet + ); + return combineLatest([beforeResponse$, afterResponse$]); + }), + map(([beforeResponse, afterResponse]) => { + const loadedBefore = beforeResponse.response.loaded; + const loadedAfter = afterResponse?.response.loaded; + const totalBefore = beforeResponse.response.total; + const totalAfter = afterResponse?.response.total; + + return { + before: beforeResponse, + after: afterResponse, + combined: { + isRunning: + (beforeResponse.response.isRunning || afterResponse?.response.isRunning) ?? + false, + isPartial: + (beforeResponse.response.isPartial || afterResponse?.response.isPartial) ?? + false, + loaded: + loadedBefore != null || loadedAfter != null + ? (loadedBefore ?? 0) + (loadedAfter ?? 0) + : undefined, + total: + totalBefore != null || totalAfter != null + ? (totalBefore ?? 0) + (totalAfter ?? 0) + : undefined, + entries: [ + ...(beforeResponse.response.data?.entries ?? []), + ...(afterResponse?.response.data?.entries ?? []), + ], + errors: [ + ...(beforeResponse.response.errors ?? []), + ...(afterResponse?.response.errors ?? []), + ], + hasMoreBefore: beforeResponse.response.data?.hasMoreBefore, + hasMoreAfter: afterResponse?.response.data?.hasMoreAfter, + topCursor: beforeResponse.response.data?.topCursor, + bottomCursor: afterResponse?.response.data?.bottomCursor, + }, + }; + }) + ) + ) + ), + [logEntriesAroundSearchRequests$] + ); + + const { + latestValue: { + before: latestBeforeResponse, + after: latestAfterResponse, + combined: latestCombinedResponse, + }, + } = useObservableState(logEntriesAroundSearchResponses$, initialCombinedResponse); + + const cancelRequest = useCallback(() => { + latestBeforeResponse?.abortController.abort(); + latestAfterResponse?.abortController.abort(); + }, [latestBeforeResponse, latestAfterResponse]); + + return { + cancelRequest, + fetchLogEntriesAround, + isRequestRunning: latestCombinedResponse?.isRunning ?? false, + isResponsePartial: latestCombinedResponse?.isPartial ?? false, + loaded: latestCombinedResponse?.loaded, + logEntriesAroundSearchResponses$, + total: latestCombinedResponse?.total, + }; +}; + +const initialCombinedResponse = { + before: undefined, + after: undefined, + combined: undefined, +} as const; diff --git a/x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_before.ts b/x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_before.ts new file mode 100644 index 0000000000000..5553be11b9fef --- /dev/null +++ b/x-pack/plugins/infra/public/containers/logs/log_stream/use_fetch_log_entries_before.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 { useCallback } from 'react'; +import { Observable } from 'rxjs'; +import { exhaustMap } from 'rxjs/operators'; +import { IKibanaSearchRequest } from '../../../../../../../src/plugins/data/public'; +import { LogSourceColumnConfiguration } from '../../../../common/http_api/log_sources'; +import { LogEntryBeforeCursor } from '../../../../common/log_entry'; +import { decodeOrThrow } from '../../../../common/runtime_types'; +import { + logEntriesSearchRequestParamsRT, + LogEntriesSearchRequestQuery, + LogEntriesSearchResponsePayload, + logEntriesSearchResponsePayloadRT, + LOG_ENTRIES_SEARCH_STRATEGY, +} from '../../../../common/search_strategies/log_entries/log_entries'; +import { + flattenDataSearchResponseDescriptor, + normalizeDataSearchResponses, + ParsedDataSearchRequestDescriptor, + useDataSearch, + useDataSearchResponseState, +} from '../../../utils/data_search'; +import { useOperator } from '../../../utils/use_observable'; + +export const useLogEntriesBeforeRequest = ({ + columnOverrides, + endTimestamp, + highlightPhrase, + query, + sourceId, + startTimestamp, +}: { + columnOverrides?: LogSourceColumnConfiguration[]; + endTimestamp: number; + highlightPhrase?: string; + query?: LogEntriesSearchRequestQuery; + sourceId: string; + startTimestamp: number; +}) => { + const { + search: fetchLogEntriesBefore, + requests$: logEntriesBeforeSearchRequests$, + } = useDataSearch({ + getRequest: useCallback( + (cursor: LogEntryBeforeCursor['before'], size: number) => { + return !!sourceId + ? { + request: { + params: logEntriesSearchRequestParamsRT.encode({ + before: cursor, + columns: columnOverrides, + endTimestamp, + highlightPhrase, + query, + size, + sourceId, + startTimestamp, + }), + }, + options: { strategy: LOG_ENTRIES_SEARCH_STRATEGY }, + } + : null; + }, + [columnOverrides, endTimestamp, highlightPhrase, query, sourceId, startTimestamp] + ), + parseResponses: parseLogEntriesBeforeSearchResponses, + }); + + return { + fetchLogEntriesBefore, + logEntriesBeforeSearchRequests$, + }; +}; + +export const useLogEntriesBeforeResponse = ( + logEntriesBeforeSearchRequests$: Observable< + ParsedDataSearchRequestDescriptor + > +) => { + const logEntriesBeforeSearchResponse$ = useOperator( + logEntriesBeforeSearchRequests$, + flattenLogEntriesBeforeSearchResponse + ); + + const { + cancelRequest, + isRequestRunning, + isResponsePartial, + loaded, + total, + } = useDataSearchResponseState(logEntriesBeforeSearchResponse$); + + return { + cancelRequest, + isRequestRunning, + isResponsePartial, + loaded, + logEntriesBeforeSearchRequests$, + logEntriesBeforeSearchResponse$, + total, + }; +}; + +export const useFetchLogEntriesBefore = ({ + columnOverrides, + endTimestamp, + highlightPhrase, + query, + sourceId, + startTimestamp, +}: { + columnOverrides?: LogSourceColumnConfiguration[]; + endTimestamp: number; + highlightPhrase?: string; + query?: LogEntriesSearchRequestQuery; + sourceId: string; + startTimestamp: number; +}) => { + const { fetchLogEntriesBefore, logEntriesBeforeSearchRequests$ } = useLogEntriesBeforeRequest({ + columnOverrides, + endTimestamp, + highlightPhrase, + query, + sourceId, + startTimestamp, + }); + + const { + cancelRequest, + isRequestRunning, + isResponsePartial, + loaded, + logEntriesBeforeSearchResponse$, + total, + } = useLogEntriesBeforeResponse(logEntriesBeforeSearchRequests$); + + return { + cancelRequest, + fetchLogEntriesBefore, + isRequestRunning, + isResponsePartial, + loaded, + logEntriesBeforeSearchResponse$, + total, + }; +}; + +export const parseLogEntriesBeforeSearchResponses = normalizeDataSearchResponses( + null, + decodeOrThrow(logEntriesSearchResponsePayloadRT) +); + +const flattenLogEntriesBeforeSearchResponse = exhaustMap(flattenDataSearchResponseDescriptor); diff --git a/x-pack/plugins/infra/public/containers/source/create_source.gql_query.ts b/x-pack/plugins/infra/public/containers/source/create_source.gql_query.ts deleted file mode 100644 index 6727dea712f3c..0000000000000 --- a/x-pack/plugins/infra/public/containers/source/create_source.gql_query.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 gql from 'graphql-tag'; - -import { sharedFragments } from '../../../common/graphql/shared'; -import { - sourceConfigurationFieldsFragment, - sourceStatusFieldsFragment, -} from './source_fields_fragment.gql_query'; - -export const createSourceMutation = gql` - mutation CreateSourceConfigurationMutation( - $sourceId: ID! - $sourceProperties: UpdateSourceInput! - ) { - createSource(id: $sourceId, sourceProperties: $sourceProperties) { - source { - ...InfraSourceFields - configuration { - ...SourceConfigurationFields - } - status { - ...SourceStatusFields - } - } - } - } - - ${sharedFragments.InfraSourceFields} - ${sourceConfigurationFieldsFragment} - ${sourceStatusFieldsFragment} -`; diff --git a/x-pack/plugins/infra/public/containers/source/query_source.gql_query.ts b/x-pack/plugins/infra/public/containers/source/query_source.gql_query.ts deleted file mode 100644 index 21b5192e57252..0000000000000 --- a/x-pack/plugins/infra/public/containers/source/query_source.gql_query.ts +++ /dev/null @@ -1,31 +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'; - -import { sharedFragments } from '../../../common/graphql/shared'; -import { - sourceConfigurationFieldsFragment, - sourceStatusFieldsFragment, -} from './source_fields_fragment.gql_query'; - -export const sourceQuery = gql` - query SourceQuery($sourceId: ID = "default") { - source(id: $sourceId) { - ...InfraSourceFields - configuration { - ...SourceConfigurationFields - } - status { - ...SourceStatusFields - } - } - } - - ${sharedFragments.InfraSourceFields} - ${sourceConfigurationFieldsFragment} - ${sourceStatusFieldsFragment} -`; diff --git a/x-pack/plugins/infra/public/containers/source/source.tsx b/x-pack/plugins/infra/public/containers/source/source.tsx index 96bbd858c3a4b..c84269d6b498a 100644 --- a/x-pack/plugins/infra/public/containers/source/source.tsx +++ b/x-pack/plugins/infra/public/containers/source/source.tsx @@ -8,20 +8,17 @@ import createContainer from 'constate'; import { useEffect, useMemo, useState } from 'react'; import { - CreateSourceConfigurationMutation, - SourceQuery, - UpdateSourceInput, - UpdateSourceMutation, -} from '../../graphql/types'; -import { DependencyError, useApolloClient } from '../../utils/apollo_context'; + InfraSavedSourceConfiguration, + InfraSource, + SourceResponse, +} from '../../../common/http_api/source_api'; import { useTrackedPromise } from '../../utils/use_tracked_promise'; -import { createSourceMutation } from './create_source.gql_query'; -import { sourceQuery } from './query_source.gql_query'; -import { updateSourceMutation } from './update_source.gql_query'; +import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; -type Source = SourceQuery.Query['source']; - -export const pickIndexPattern = (source: Source | undefined, type: 'logs' | 'metrics' | 'both') => { +export const pickIndexPattern = ( + source: InfraSource | undefined, + type: 'logs' | 'metrics' | 'both' +) => { if (!source) { return 'unknown-index'; } @@ -34,96 +31,79 @@ export const pickIndexPattern = (source: Source | undefined, type: 'logs' | 'met return `${source.configuration.logAlias},${source.configuration.metricAlias}`; }; +const DEPENDENCY_ERROR_MESSAGE = 'Failed to load source: No fetch client available.'; + export const useSource = ({ sourceId }: { sourceId: string }) => { - const apolloClient = useApolloClient(); - const [source, setSource] = useState(undefined); + const kibana = useKibana(); + const fetchService = kibana.services.http?.fetch; + const API_URL = `/api/metrics/source/${sourceId}`; + + const [source, setSource] = useState(undefined); const [loadSourceRequest, loadSource] = useTrackedPromise( { cancelPreviousOn: 'resolution', createPromise: async () => { - if (!apolloClient) { - throw new DependencyError('Failed to load source: No apollo client available.'); + if (!fetchService) { + throw new Error(DEPENDENCY_ERROR_MESSAGE); } - return await apolloClient.query({ - fetchPolicy: 'no-cache', - query: sourceQuery, - variables: { - sourceId, - }, + return await fetchService(`${API_URL}/metrics`, { + method: 'GET', }); }, onResolve: (response) => { - setSource(response.data.source); + setSource(response.source); }, }, - [apolloClient, sourceId] + [fetchService, sourceId] ); const [createSourceConfigurationRequest, createSourceConfiguration] = useTrackedPromise( { - createPromise: async (sourceProperties: UpdateSourceInput) => { - if (!apolloClient) { - throw new DependencyError( - 'Failed to create source configuration: No apollo client available.' - ); + createPromise: async (sourceProperties: InfraSavedSourceConfiguration) => { + if (!fetchService) { + throw new Error(DEPENDENCY_ERROR_MESSAGE); } - return await apolloClient.mutate< - CreateSourceConfigurationMutation.Mutation, - CreateSourceConfigurationMutation.Variables - >({ - mutation: createSourceMutation, - fetchPolicy: 'no-cache', - variables: { - sourceId, - sourceProperties, - }, + return await fetchService(API_URL, { + method: 'PATCH', + body: JSON.stringify(sourceProperties), }); }, onResolve: (response) => { - if (response.data) { - setSource(response.data.createSource.source); + if (response) { + setSource(response.source); } }, }, - [apolloClient, sourceId] + [fetchService, sourceId] ); const [updateSourceConfigurationRequest, updateSourceConfiguration] = useTrackedPromise( { - createPromise: async (sourceProperties: UpdateSourceInput) => { - if (!apolloClient) { - throw new DependencyError( - 'Failed to update source configuration: No apollo client available.' - ); + createPromise: async (sourceProperties: InfraSavedSourceConfiguration) => { + if (!fetchService) { + throw new Error(DEPENDENCY_ERROR_MESSAGE); } - return await apolloClient.mutate< - UpdateSourceMutation.Mutation, - UpdateSourceMutation.Variables - >({ - mutation: updateSourceMutation, - fetchPolicy: 'no-cache', - variables: { - sourceId, - sourceProperties, - }, + return await fetchService(API_URL, { + method: 'PATCH', + body: JSON.stringify(sourceProperties), }); }, onResolve: (response) => { - if (response.data) { - setSource(response.data.updateSource.source); + if (response) { + setSource(response.source); } }, }, - [apolloClient, sourceId] + [fetchService, sourceId] ); const createDerivedIndexPattern = (type: 'logs' | 'metrics' | 'both') => { return { - fields: source ? source.status.indexFields : [], + fields: source?.status ? source.status.indexFields : [], title: pickIndexPattern(source, type), }; }; diff --git a/x-pack/plugins/infra/public/containers/source/source_fields_fragment.gql_query.ts b/x-pack/plugins/infra/public/containers/source/source_fields_fragment.gql_query.ts deleted file mode 100644 index 61312a0f2890e..0000000000000 --- a/x-pack/plugins/infra/public/containers/source/source_fields_fragment.gql_query.ts +++ /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 gql from 'graphql-tag'; - -export const sourceConfigurationFieldsFragment = gql` - fragment SourceConfigurationFields on InfraSourceConfiguration { - name - description - logAlias - metricAlias - inventoryDefaultView - metricsExplorerDefaultView - fields { - container - host - message - pod - tiebreaker - timestamp - } - logColumns { - ... on InfraSourceTimestampLogColumn { - timestampColumn { - id - } - } - ... on InfraSourceMessageLogColumn { - messageColumn { - id - } - } - ... on InfraSourceFieldLogColumn { - fieldColumn { - id - field - } - } - } - } -`; - -export const sourceStatusFieldsFragment = gql` - fragment SourceStatusFields on InfraSourceStatus { - indexFields { - name - type - searchable - aggregatable - displayable - } - logIndicesExist - metricIndicesExist - } -`; diff --git a/x-pack/plugins/infra/public/containers/source/update_source.gql_query.ts b/x-pack/plugins/infra/public/containers/source/update_source.gql_query.ts deleted file mode 100644 index ed52780049f63..0000000000000 --- a/x-pack/plugins/infra/public/containers/source/update_source.gql_query.ts +++ /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 gql from 'graphql-tag'; - -import { sharedFragments } from '../../../common/graphql/shared'; -import { - sourceConfigurationFieldsFragment, - sourceStatusFieldsFragment, -} from './source_fields_fragment.gql_query'; - -export const updateSourceMutation = gql` - mutation UpdateSourceMutation($sourceId: ID = "default", $sourceProperties: UpdateSourceInput!) { - updateSource(id: $sourceId, sourceProperties: $sourceProperties) { - source { - ...InfraSourceFields - configuration { - ...SourceConfigurationFields - } - status { - ...SourceStatusFields - } - } - } - } - - ${sharedFragments.InfraSourceFields} - ${sourceConfigurationFieldsFragment} - ${sourceStatusFieldsFragment} -`; diff --git a/x-pack/plugins/infra/public/containers/source/use_source_via_http.ts b/x-pack/plugins/infra/public/containers/source/use_source_via_http.ts index 94e2537a67a2a..33f74173bee35 100644 --- a/x-pack/plugins/infra/public/containers/source/use_source_via_http.ts +++ b/x-pack/plugins/infra/public/containers/source/use_source_via_http.ts @@ -72,7 +72,7 @@ export const useSourceViaHttp = ({ const createDerivedIndexPattern = useCallback( (indexType: 'logs' | 'metrics' | 'both' = type) => { return { - fields: response?.source ? response.status.indexFields : [], + fields: response?.source.status ? response.source.status.indexFields : [], title: pickIndexPattern(response?.source, indexType), }; }, @@ -80,7 +80,7 @@ export const useSourceViaHttp = ({ ); const source = useMemo(() => { - return response ? { ...response.source, status: response.status } : null; + return response ? response.source : null; }, [response]); return { diff --git a/x-pack/plugins/infra/public/containers/with_source/with_source.tsx b/x-pack/plugins/infra/public/containers/with_source/with_source.tsx index e8c609ae0bad9..1681bf85f7f17 100644 --- a/x-pack/plugins/infra/public/containers/with_source/with_source.tsx +++ b/x-pack/plugins/infra/public/containers/with_source/with_source.tsx @@ -7,14 +7,17 @@ import React, { useContext } from 'react'; import { IIndexPattern } from 'src/plugins/data/public'; -import { SourceQuery, UpdateSourceInput } from '../../graphql/types'; +import { + InfraSavedSourceConfiguration, + InfraSourceConfiguration, +} from '../../../common/http_api/source_api'; import { RendererFunction } from '../../utils/typed_react'; import { Source } from '../source'; interface WithSourceProps { children: RendererFunction<{ - configuration?: SourceQuery.Query['source']['configuration']; - create: (sourceProperties: UpdateSourceInput) => Promise | undefined; + configuration?: InfraSourceConfiguration; + create: (sourceProperties: InfraSavedSourceConfiguration) => Promise | undefined; createDerivedIndexPattern: (type: 'logs' | 'metrics' | 'both') => IIndexPattern; exists?: boolean; hasFailed: boolean; @@ -25,7 +28,7 @@ interface WithSourceProps { metricAlias?: string; metricIndicesExist?: boolean; sourceId: string; - update: (sourceProperties: UpdateSourceInput) => Promise | undefined; + update: (sourceProperties: InfraSavedSourceConfiguration) => Promise | undefined; version?: string; }>; } diff --git a/x-pack/plugins/infra/public/graphql/introspection.json b/x-pack/plugins/infra/public/graphql/introspection.json deleted file mode 100644 index 5d351f3259ac5..0000000000000 --- a/x-pack/plugins/infra/public/graphql/introspection.json +++ /dev/null @@ -1,3373 +0,0 @@ -{ - "__schema": { - "queryType": { "name": "Query" }, - "mutationType": { "name": "Mutation" }, - "subscriptionType": null, - "types": [ - { - "kind": "OBJECT", - "name": "Query", - "description": "", - "fields": [ - { - "name": "source", - "description": "Get an infrastructure data source by id.\n\nThe resolution order for the source configuration attributes is as follows\nwith the first defined value winning:\n\n1. The attributes of the saved object with the given 'id'.\n2. The attributes defined in the static Kibana configuration key\n 'xpack.infra.sources.default'.\n3. The hard-coded default values.\n\nAs a consequence, querying a source that doesn't exist doesn't error out,\nbut returns the configured or hardcoded defaults.", - "args": [ - { - "name": "id", - "description": "The id of the source", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "InfraSource", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "allSources", - "description": "Get a list of all infrastructure data sources", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "InfraSource", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "SCALAR", - "name": "ID", - "description": "The `ID` scalar type represents a unique identifier, often used to refetch an object or as key for a cache. The ID type appears in a JSON response as a String; however, it is not intended to be human-readable. When expected as an input type, any string (such as `\"4\"`) or integer (such as `4`) input value will be accepted as an ID.", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "InfraSource", - "description": "A source of infrastructure data", - "fields": [ - { - "name": "id", - "description": "The id of the source", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "version", - "description": "The version number the source configuration was last persisted with", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "updatedAt", - "description": "The timestamp the source configuration was last persisted at", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "origin", - "description": "The origin of the source (one of 'fallback', 'internal', 'stored')", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "configuration", - "description": "The raw configuration of the source", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "InfraSourceConfiguration", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "status", - "description": "The status of the source", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "InfraSourceStatus", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "logEntriesAround", - "description": "A consecutive span of log entries surrounding a point in time", - "args": [ - { - "name": "key", - "description": "The sort key that corresponds to the point in time", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "InfraTimeKeyInput", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "countBefore", - "description": "The maximum number of preceding to return", - "type": { "kind": "SCALAR", "name": "Int", "ofType": null }, - "defaultValue": "0" - }, - { - "name": "countAfter", - "description": "The maximum number of following to return", - "type": { "kind": "SCALAR", "name": "Int", "ofType": null }, - "defaultValue": "0" - }, - { - "name": "filterQuery", - "description": "The query to filter the log entries by", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "InfraLogEntryInterval", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "logEntriesBetween", - "description": "A consecutive span of log entries within an interval", - "args": [ - { - "name": "startKey", - "description": "The sort key that corresponds to the start of the interval", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "InfraTimeKeyInput", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "endKey", - "description": "The sort key that corresponds to the end of the interval", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "InfraTimeKeyInput", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "filterQuery", - "description": "The query to filter the log entries by", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "InfraLogEntryInterval", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "logEntryHighlights", - "description": "Sequences of log entries matching sets of highlighting queries within an interval", - "args": [ - { - "name": "startKey", - "description": "The sort key that corresponds to the start of the interval", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "InfraTimeKeyInput", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "endKey", - "description": "The sort key that corresponds to the end of the interval", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "InfraTimeKeyInput", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "filterQuery", - "description": "The query to filter the log entries by", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "highlights", - "description": "The highlighting to apply to the log entries", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "InfraLogEntryHighlightInput", - "ofType": null - } - } - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "InfraLogEntryInterval", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "snapshot", - "description": "A snapshot of nodes", - "args": [ - { - "name": "timerange", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "InfraTimerangeInput", - "ofType": null - } - }, - "defaultValue": null - }, - { - "name": "filterQuery", - "description": "", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - } - ], - "type": { "kind": "OBJECT", "name": "InfraSnapshotResponse", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "metrics", - "description": "", - "args": [ - { - "name": "nodeIds", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "InfraNodeIdsInput", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "nodeType", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "InfraNodeType", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "timerange", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "InfraTimerangeInput", - "ofType": null - } - }, - "defaultValue": null - }, - { - "name": "metrics", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "InfraMetric", "ofType": null } - } - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "InfraMetricData", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "SCALAR", - "name": "String", - "description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "SCALAR", - "name": "Float", - "description": "The `Float` scalar type represents signed double-precision fractional values as specified by [IEEE 754](http://en.wikipedia.org/wiki/IEEE_floating_point). ", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "InfraSourceConfiguration", - "description": "A set of configuration options for an infrastructure data source", - "fields": [ - { - "name": "name", - "description": "The name of the data source", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "description", - "description": "A description of the data source", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "metricAlias", - "description": "The alias to read metric data from", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "logAlias", - "description": "The alias to read log data from", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "fields", - "description": "The field mapping to use for this source", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "InfraSourceFields", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "logColumns", - "description": "The columns to use for log display", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "UNION", "name": "InfraSourceLogColumn", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "InfraSourceFields", - "description": "A mapping of semantic fields to their document counterparts", - "fields": [ - { - "name": "container", - "description": "The field to identify a container by", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "host", - "description": "The fields to identify a host by", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "message", - "description": "The fields to use as the log message", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "pod", - "description": "The field to identify a pod by", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "tiebreaker", - "description": "The field to use as a tiebreaker for log events that have identical timestamps", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "timestamp", - "description": "The field to use as a timestamp for metrics and logs", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "UNION", - "name": "InfraSourceLogColumn", - "description": "All known log column types", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": [ - { "kind": "OBJECT", "name": "InfraSourceTimestampLogColumn", "ofType": null }, - { "kind": "OBJECT", "name": "InfraSourceMessageLogColumn", "ofType": null }, - { "kind": "OBJECT", "name": "InfraSourceFieldLogColumn", "ofType": null } - ] - }, - { - "kind": "OBJECT", - "name": "InfraSourceTimestampLogColumn", - "description": "The built-in timestamp log column", - "fields": [ - { - "name": "timestampColumn", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "InfraSourceTimestampLogColumnAttributes", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "InfraSourceTimestampLogColumnAttributes", - "description": "", - "fields": [ - { - "name": "id", - "description": "A unique id for the column", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "InfraSourceMessageLogColumn", - "description": "The built-in message log column", - "fields": [ - { - "name": "messageColumn", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "InfraSourceMessageLogColumnAttributes", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "InfraSourceMessageLogColumnAttributes", - "description": "", - "fields": [ - { - "name": "id", - "description": "A unique id for the column", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "InfraSourceFieldLogColumn", - "description": "A log column containing a field value", - "fields": [ - { - "name": "fieldColumn", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "OBJECT", - "name": "InfraSourceFieldLogColumnAttributes", - "ofType": null - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "InfraSourceFieldLogColumnAttributes", - "description": "", - "fields": [ - { - "name": "id", - "description": "A unique id for the column", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "field", - "description": "The field name this column refers to", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "InfraSourceStatus", - "description": "The status of an infrastructure data source", - "fields": [ - { - "name": "metricAliasExists", - "description": "Whether the configured metric alias exists", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "logAliasExists", - "description": "Whether the configured log alias exists", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "metricIndicesExist", - "description": "Whether the configured alias or wildcard pattern resolve to any metric indices", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "logIndicesExist", - "description": "Whether the configured alias or wildcard pattern resolve to any log indices", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "metricIndices", - "description": "The list of indices in the metric alias", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "logIndices", - "description": "The list of indices in the log alias", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "indexFields", - "description": "The list of fields defined in the index mappings", - "args": [ - { - "name": "indexType", - "description": "", - "type": { "kind": "ENUM", "name": "InfraIndexType", "ofType": null }, - "defaultValue": "ANY" - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "InfraIndexField", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "SCALAR", - "name": "Boolean", - "description": "The `Boolean` scalar type represents `true` or `false`.", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "ENUM", - "name": "InfraIndexType", - "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ - { "name": "ANY", "description": "", "isDeprecated": false, "deprecationReason": null }, - { "name": "LOGS", "description": "", "isDeprecated": false, "deprecationReason": null }, - { "name": "METRICS", "description": "", "isDeprecated": false, "deprecationReason": null } - ], - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "InfraIndexField", - "description": "A descriptor of a field in an index", - "fields": [ - { - "name": "name", - "description": "The name of the field", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "type", - "description": "The type of the field's values as recognized by Kibana", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "searchable", - "description": "Whether the field's values can be efficiently searched for", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "aggregatable", - "description": "Whether the field's values can be aggregated", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "displayable", - "description": "Whether the field should be displayed based on event.module and a ECS allowed list", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "InfraTimeKeyInput", - "description": "", - "fields": null, - "inputFields": [ - { - "name": "time", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "tiebreaker", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "SCALAR", - "name": "Int", - "description": "The `Int` scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1. ", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "InfraLogEntryInterval", - "description": "A consecutive sequence of log entries", - "fields": [ - { - "name": "start", - "description": "The key corresponding to the start of the interval covered by the entries", - "args": [], - "type": { "kind": "OBJECT", "name": "InfraTimeKey", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "end", - "description": "The key corresponding to the end of the interval covered by the entries", - "args": [], - "type": { "kind": "OBJECT", "name": "InfraTimeKey", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "hasMoreBefore", - "description": "Whether there are more log entries available before the start", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "hasMoreAfter", - "description": "Whether there are more log entries available after the end", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "filterQuery", - "description": "The query the log entries were filtered by", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "highlightQuery", - "description": "The query the log entries were highlighted with", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "entries", - "description": "A list of the log entries", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "InfraLogEntry", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "InfraTimeKey", - "description": "A representation of the log entry's position in the event stream", - "fields": [ - { - "name": "time", - "description": "The timestamp of the event that the log entry corresponds to", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "tiebreaker", - "description": "The tiebreaker that disambiguates events with the same timestamp", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "InfraLogEntry", - "description": "A log entry", - "fields": [ - { - "name": "key", - "description": "A unique representation of the log entry's position in the event stream", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "InfraTimeKey", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "gid", - "description": "The log entry's id", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "source", - "description": "The source id", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "columns", - "description": "The columns used for rendering the log entry", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "UNION", "name": "InfraLogEntryColumn", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "UNION", - "name": "InfraLogEntryColumn", - "description": "A column of a log entry", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": [ - { "kind": "OBJECT", "name": "InfraLogEntryTimestampColumn", "ofType": null }, - { "kind": "OBJECT", "name": "InfraLogEntryMessageColumn", "ofType": null }, - { "kind": "OBJECT", "name": "InfraLogEntryFieldColumn", "ofType": null } - ] - }, - { - "kind": "OBJECT", - "name": "InfraLogEntryTimestampColumn", - "description": "A special built-in column that contains the log entry's timestamp", - "fields": [ - { - "name": "columnId", - "description": "The id of the corresponding column configuration", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "timestamp", - "description": "The timestamp", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "InfraLogEntryMessageColumn", - "description": "A special built-in column that contains the log entry's constructed message", - "fields": [ - { - "name": "columnId", - "description": "The id of the corresponding column configuration", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "message", - "description": "A list of the formatted log entry segments", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "UNION", "name": "InfraLogMessageSegment", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "UNION", - "name": "InfraLogMessageSegment", - "description": "A segment of the log entry message", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": null, - "possibleTypes": [ - { "kind": "OBJECT", "name": "InfraLogMessageFieldSegment", "ofType": null }, - { "kind": "OBJECT", "name": "InfraLogMessageConstantSegment", "ofType": null } - ] - }, - { - "kind": "OBJECT", - "name": "InfraLogMessageFieldSegment", - "description": "A segment of the log entry message that was derived from a field", - "fields": [ - { - "name": "field", - "description": "The field the segment was derived from", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "value", - "description": "The segment's message", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "highlights", - "description": "A list of highlighted substrings of the value", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "InfraLogMessageConstantSegment", - "description": "A segment of the log entry message that was derived from a string literal", - "fields": [ - { - "name": "constant", - "description": "The segment's message", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "InfraLogEntryFieldColumn", - "description": "A column that contains the value of a field of the log entry", - "fields": [ - { - "name": "columnId", - "description": "The id of the corresponding column configuration", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "field", - "description": "The field name of the column", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "value", - "description": "The value of the field in the log entry", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "highlights", - "description": "A list of highlighted substrings of the value", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "InfraLogEntryHighlightInput", - "description": "A highlighting definition", - "fields": null, - "inputFields": [ - { - "name": "query", - "description": "The query to highlight by", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "countBefore", - "description": "The number of highlighted documents to include beyond the beginning of the interval", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Int", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "countAfter", - "description": "The number of highlighted documents to include beyond the end of the interval", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Int", "ofType": null } - }, - "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "InfraTimerangeInput", - "description": "", - "fields": null, - "inputFields": [ - { - "name": "interval", - "description": "The interval string to use for last bucket. The format is '{value}{unit}'. For example '5m' would return the metrics for the last 5 minutes of the timespan.", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "to", - "description": "The end of the timerange", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "from", - "description": "The beginning of the timerange", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "InfraSnapshotResponse", - "description": "", - "fields": [ - { - "name": "nodes", - "description": "Nodes of type host, container or pod grouped by 0, 1 or 2 terms", - "args": [ - { - "name": "type", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "InfraNodeType", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "groupBy", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "InfraSnapshotGroupbyInput", - "ofType": null - } - } - } - }, - "defaultValue": null - }, - { - "name": "metric", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "InfraSnapshotMetricInput", - "ofType": null - } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "InfraSnapshotNode", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "ENUM", - "name": "InfraNodeType", - "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ - { "name": "pod", "description": "", "isDeprecated": false, "deprecationReason": null }, - { - "name": "container", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { "name": "host", "description": "", "isDeprecated": false, "deprecationReason": null }, - { "name": "awsEC2", "description": "", "isDeprecated": false, "deprecationReason": null } - ], - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "InfraSnapshotGroupbyInput", - "description": "", - "fields": null, - "inputFields": [ - { - "name": "label", - "description": "The label to use in the results for the group by for the terms group by", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "field", - "description": "The field to group by from a terms aggregation, this is ignored by the filter type", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "InfraSnapshotMetricInput", - "description": "", - "fields": null, - "inputFields": [ - { - "name": "type", - "description": "The type of metric", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "InfraSnapshotMetricType", "ofType": null } - }, - "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "ENUM", - "name": "InfraSnapshotMetricType", - "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ - { "name": "count", "description": "", "isDeprecated": false, "deprecationReason": null }, - { "name": "cpu", "description": "", "isDeprecated": false, "deprecationReason": null }, - { "name": "load", "description": "", "isDeprecated": false, "deprecationReason": null }, - { "name": "memory", "description": "", "isDeprecated": false, "deprecationReason": null }, - { "name": "tx", "description": "", "isDeprecated": false, "deprecationReason": null }, - { "name": "rx", "description": "", "isDeprecated": false, "deprecationReason": null }, - { - "name": "logRate", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "diskIOReadBytes", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "diskIOWriteBytes", - "description": "", - "isDeprecated": false, - "deprecationReason": null - } - ], - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "InfraSnapshotNode", - "description": "", - "fields": [ - { - "name": "path", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "InfraSnapshotNodePath", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "metric", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "InfraSnapshotNodeMetric", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "InfraSnapshotNodePath", - "description": "", - "fields": [ - { - "name": "value", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "label", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "ip", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "InfraSnapshotNodeMetric", - "description": "", - "fields": [ - { - "name": "name", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "InfraSnapshotMetricType", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "value", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "avg", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "max", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "InfraNodeIdsInput", - "description": "", - "fields": null, - "inputFields": [ - { - "name": "nodeId", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "cloudId", - "description": "", - "type": { "kind": "SCALAR", "name": "ID", "ofType": null }, - "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "ENUM", - "name": "InfraMetric", - "description": "", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ - { - "name": "hostSystemOverview", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "hostCpuUsage", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "hostFilesystem", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "hostK8sOverview", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "hostK8sCpuCap", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "hostK8sDiskCap", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "hostK8sMemoryCap", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "hostK8sPodCap", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "hostLoad", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "hostMemoryUsage", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "hostNetworkTraffic", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "hostDockerOverview", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "hostDockerInfo", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "hostDockerTop5ByCpu", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "hostDockerTop5ByMemory", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "podOverview", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "podCpuUsage", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "podMemoryUsage", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "podLogUsage", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "podNetworkTraffic", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "containerOverview", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "containerCpuKernel", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "containerCpuUsage", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "containerDiskIOOps", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "containerDiskIOBytes", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "containerMemory", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "containerNetworkTraffic", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "nginxHits", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "nginxRequestRate", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "nginxActiveConnections", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "nginxRequestsPerConnection", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "awsOverview", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "awsCpuUtilization", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "awsNetworkBytes", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "awsNetworkPackets", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "awsDiskioBytes", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "awsDiskioOps", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "awsEC2CpuUtilization", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "awsEC2NetworkTraffic", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "awsEC2DiskIOBytes", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { "name": "custom", "description": "", "isDeprecated": false, "deprecationReason": null } - ], - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "InfraMetricData", - "description": "", - "fields": [ - { - "name": "id", - "description": "", - "args": [], - "type": { "kind": "ENUM", "name": "InfraMetric", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "series", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "InfraDataSeries", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "InfraDataSeries", - "description": "", - "fields": [ - { - "name": "id", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "label", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "data", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "InfraDataPoint", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "InfraDataPoint", - "description": "", - "fields": [ - { - "name": "timestamp", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Float", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "value", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "Float", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "Mutation", - "description": "", - "fields": [ - { - "name": "createSource", - "description": "Create a new source of infrastructure data", - "args": [ - { - "name": "id", - "description": "The id of the source", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "sourceProperties", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "UpdateSourceInput", "ofType": null } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "UpdateSourceResult", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "updateSource", - "description": "Modify an existing source", - "args": [ - { - "name": "id", - "description": "The id of the source", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "sourceProperties", - "description": "The properties to update the source with", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "INPUT_OBJECT", "name": "UpdateSourceInput", "ofType": null } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "UpdateSourceResult", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "deleteSource", - "description": "Delete a source of infrastructure data", - "args": [ - { - "name": "id", - "description": "The id of the source", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } - }, - "defaultValue": null - } - ], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "DeleteSourceResult", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "UpdateSourceInput", - "description": "The properties to update the source with", - "fields": null, - "inputFields": [ - { - "name": "name", - "description": "The name of the data source", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "description", - "description": "A description of the data source", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "metricAlias", - "description": "The alias to read metric data from", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "logAlias", - "description": "The alias to read log data from", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "fields", - "description": "The field mapping to use for this source", - "type": { "kind": "INPUT_OBJECT", "name": "UpdateSourceFieldsInput", "ofType": null }, - "defaultValue": null - }, - { - "name": "logColumns", - "description": "The log columns to display for this source", - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "INPUT_OBJECT", - "name": "UpdateSourceLogColumnInput", - "ofType": null - } - } - }, - "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "UpdateSourceFieldsInput", - "description": "The mapping of semantic fields of the source to be created", - "fields": null, - "inputFields": [ - { - "name": "container", - "description": "The field to identify a container by", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "host", - "description": "The fields to identify a host by", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "pod", - "description": "The field to identify a pod by", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "tiebreaker", - "description": "The field to use as a tiebreaker for log events that have identical timestamps", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - }, - { - "name": "timestamp", - "description": "The field to use as a timestamp for metrics and logs", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "UpdateSourceLogColumnInput", - "description": "One of the log column types to display for this source", - "fields": null, - "inputFields": [ - { - "name": "fieldColumn", - "description": "A custom field log column", - "type": { - "kind": "INPUT_OBJECT", - "name": "UpdateSourceFieldLogColumnInput", - "ofType": null - }, - "defaultValue": null - }, - { - "name": "messageColumn", - "description": "A built-in message log column", - "type": { - "kind": "INPUT_OBJECT", - "name": "UpdateSourceMessageLogColumnInput", - "ofType": null - }, - "defaultValue": null - }, - { - "name": "timestampColumn", - "description": "A built-in timestamp log column", - "type": { - "kind": "INPUT_OBJECT", - "name": "UpdateSourceTimestampLogColumnInput", - "ofType": null - }, - "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "UpdateSourceFieldLogColumnInput", - "description": "", - "fields": null, - "inputFields": [ - { - "name": "id", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } - }, - "defaultValue": null - }, - { - "name": "field", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "UpdateSourceMessageLogColumnInput", - "description": "", - "fields": null, - "inputFields": [ - { - "name": "id", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } - }, - "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "INPUT_OBJECT", - "name": "UpdateSourceTimestampLogColumnInput", - "description": "", - "fields": null, - "inputFields": [ - { - "name": "id", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } - }, - "defaultValue": null - } - ], - "interfaces": null, - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "UpdateSourceResult", - "description": "The result of a successful source update", - "fields": [ - { - "name": "source", - "description": "The source that was updated", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "InfraSource", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "DeleteSourceResult", - "description": "The result of a source deletion operations", - "fields": [ - { - "name": "id", - "description": "The id of the source that was deleted", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "ID", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "__Schema", - "description": "A GraphQL Schema defines the capabilities of a GraphQL server. It exposes all available types and directives on the server, as well as the entry points for query, mutation, and subscription operations.", - "fields": [ - { - "name": "types", - "description": "A list of all types supported by this server.", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "queryType", - "description": "The type that query operations will be rooted at.", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "mutationType", - "description": "If this server supports mutation, the type that mutation operations will be rooted at.", - "args": [], - "type": { "kind": "OBJECT", "name": "__Type", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "subscriptionType", - "description": "If this server support subscription, the type that subscription operations will be rooted at.", - "args": [], - "type": { "kind": "OBJECT", "name": "__Type", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "directives", - "description": "A list of all directives supported by this server.", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "__Directive", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "__Type", - "description": "The fundamental unit of any GraphQL Schema is the type. There are many kinds of types in GraphQL as represented by the `__TypeKind` enum.\n\nDepending on the kind of a type, certain fields describe information about that type. Scalar types provide no information beyond a name and description, while Enum types provide their values. Object and Interface types provide the fields they describe. Abstract types, Union and Interface, provide the Object types possible at runtime. List and NonNull types compose other types.", - "fields": [ - { - "name": "kind", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "__TypeKind", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "name", - "description": null, - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "description", - "description": null, - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "fields", - "description": null, - "args": [ - { - "name": "includeDeprecated", - "description": null, - "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, - "defaultValue": "false" - } - ], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "__Field", "ofType": null } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "interfaces", - "description": null, - "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "possibleTypes", - "description": null, - "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "enumValues", - "description": null, - "args": [ - { - "name": "includeDeprecated", - "description": null, - "type": { "kind": "SCALAR", "name": "Boolean", "ofType": null }, - "defaultValue": "false" - } - ], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "__EnumValue", "ofType": null } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "inputFields", - "description": null, - "args": [], - "type": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "__InputValue", "ofType": null } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "ofType", - "description": null, - "args": [], - "type": { "kind": "OBJECT", "name": "__Type", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "ENUM", - "name": "__TypeKind", - "description": "An enum describing what kind of type a given `__Type` is.", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ - { - "name": "SCALAR", - "description": "Indicates this type is a scalar.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "OBJECT", - "description": "Indicates this type is an object. `fields` and `interfaces` are valid fields.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "INTERFACE", - "description": "Indicates this type is an interface. `fields` and `possibleTypes` are valid fields.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "UNION", - "description": "Indicates this type is a union. `possibleTypes` is a valid field.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "ENUM", - "description": "Indicates this type is an enum. `enumValues` is a valid field.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "INPUT_OBJECT", - "description": "Indicates this type is an input object. `inputFields` is a valid field.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "LIST", - "description": "Indicates this type is a list. `ofType` is a valid field.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "NON_NULL", - "description": "Indicates this type is a non-null. `ofType` is a valid field.", - "isDeprecated": false, - "deprecationReason": null - } - ], - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "__Field", - "description": "Object and Interface types are described by a list of Fields, each of which has a name, potentially a list of arguments, and a return type.", - "fields": [ - { - "name": "name", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "description", - "description": null, - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "args", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "__InputValue", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "type", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "isDeprecated", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "deprecationReason", - "description": null, - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "__InputValue", - "description": "Arguments provided to Fields or Directives and the input fields of an InputObject are represented as Input Values which describe their type and optionally a default value.", - "fields": [ - { - "name": "name", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "description", - "description": null, - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "type", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "__Type", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "defaultValue", - "description": "A GraphQL-formatted string representing the default value for this input value.", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "__EnumValue", - "description": "One possible value for a given Enum. Enum values are unique values, not a placeholder for a string or numeric value. However an Enum value is returned in a JSON response as a string.", - "fields": [ - { - "name": "name", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "description", - "description": null, - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "isDeprecated", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "deprecationReason", - "description": null, - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "OBJECT", - "name": "__Directive", - "description": "A Directive provides a way to describe alternate runtime execution and type validation behavior in a GraphQL document.\n\nIn some cases, you need to provide options to alter GraphQL's execution behavior in ways field arguments will not suffice, such as conditionally including or skipping a field. Directives provide this by describing additional information to the executor.", - "fields": [ - { - "name": "name", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "description", - "description": null, - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "locations", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "ENUM", "name": "__DirectiveLocation", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "args", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "LIST", - "name": null, - "ofType": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "OBJECT", "name": "__InputValue", "ofType": null } - } - } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "onOperation", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, - "isDeprecated": true, - "deprecationReason": "Use `locations`." - }, - { - "name": "onFragment", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, - "isDeprecated": true, - "deprecationReason": "Use `locations`." - }, - { - "name": "onField", - "description": null, - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, - "isDeprecated": true, - "deprecationReason": "Use `locations`." - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, - { - "kind": "ENUM", - "name": "__DirectiveLocation", - "description": "A Directive can be adjacent to many parts of the GraphQL language, a __DirectiveLocation describes one such possible adjacencies.", - "fields": null, - "inputFields": null, - "interfaces": null, - "enumValues": [ - { - "name": "QUERY", - "description": "Location adjacent to a query operation.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "MUTATION", - "description": "Location adjacent to a mutation operation.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "SUBSCRIPTION", - "description": "Location adjacent to a subscription operation.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "FIELD", - "description": "Location adjacent to a field.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "FRAGMENT_DEFINITION", - "description": "Location adjacent to a fragment definition.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "FRAGMENT_SPREAD", - "description": "Location adjacent to a fragment spread.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "INLINE_FRAGMENT", - "description": "Location adjacent to an inline fragment.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "SCHEMA", - "description": "Location adjacent to a schema definition.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "SCALAR", - "description": "Location adjacent to a scalar definition.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "OBJECT", - "description": "Location adjacent to an object type definition.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "FIELD_DEFINITION", - "description": "Location adjacent to a field definition.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "ARGUMENT_DEFINITION", - "description": "Location adjacent to an argument definition.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "INTERFACE", - "description": "Location adjacent to an interface definition.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "UNION", - "description": "Location adjacent to a union definition.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "ENUM", - "description": "Location adjacent to an enum definition.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "ENUM_VALUE", - "description": "Location adjacent to an enum value definition.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "INPUT_OBJECT", - "description": "Location adjacent to an input object type definition.", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "INPUT_FIELD_DEFINITION", - "description": "Location adjacent to an input object field definition.", - "isDeprecated": false, - "deprecationReason": null - } - ], - "possibleTypes": null - } - ], - "directives": [ - { - "name": "skip", - "description": "Directs the executor to skip this field or fragment when the `if` argument is true.", - "locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"], - "args": [ - { - "name": "if", - "description": "Skipped when true.", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, - "defaultValue": null - } - ] - }, - { - "name": "include", - "description": "Directs the executor to include this field or fragment only when the `if` argument is true.", - "locations": ["FIELD", "FRAGMENT_SPREAD", "INLINE_FRAGMENT"], - "args": [ - { - "name": "if", - "description": "Included when true.", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Boolean", "ofType": null } - }, - "defaultValue": null - } - ] - }, - { - "name": "deprecated", - "description": "Marks an element of a GraphQL schema as no longer supported.", - "locations": ["FIELD_DEFINITION", "ENUM_VALUE"], - "args": [ - { - "name": "reason", - "description": "Explains why this element was deprecated, usually also including a suggestion for how to access supported similar data. Formatted in [Markdown](https://daringfireball.net/projects/markdown/).", - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "defaultValue": "\"No longer supported\"" - } - ] - } - ] - } -} diff --git a/x-pack/plugins/infra/public/graphql/types.ts b/x-pack/plugins/infra/public/graphql/types.ts deleted file mode 100644 index f0f74c34a19e6..0000000000000 --- a/x-pack/plugins/infra/public/graphql/types.ts +++ /dev/null @@ -1,1123 +0,0 @@ -import { SnapshotMetricType } from '../../common/inventory_models/types'; - -/* tslint:disable */ - -// ==================================================== -// START: Typescript template -// ==================================================== - -// ==================================================== -// Types -// ==================================================== - -export interface Query { - /** Get an infrastructure data source by id.The resolution order for the source configuration attributes is as followswith the first defined value winning:1. The attributes of the saved object with the given 'id'.2. The attributes defined in the static Kibana configuration key'xpack.infra.sources.default'.3. The hard-coded default values.As a consequence, querying a source that doesn't exist doesn't error out,but returns the configured or hardcoded defaults. */ - source: InfraSource; - /** Get a list of all infrastructure data sources */ - allSources: InfraSource[]; -} -/** A source of infrastructure data */ -export interface InfraSource { - /** The id of the source */ - id: string; - /** The version number the source configuration was last persisted with */ - version?: string | null; - /** The timestamp the source configuration was last persisted at */ - updatedAt?: number | null; - /** The origin of the source (one of 'fallback', 'internal', 'stored') */ - origin: string; - /** The raw configuration of the source */ - configuration: InfraSourceConfiguration; - /** The status of the source */ - status: InfraSourceStatus; - /** A consecutive span of log entries surrounding a point in time */ - logEntriesAround: InfraLogEntryInterval; - /** A consecutive span of log entries within an interval */ - logEntriesBetween: InfraLogEntryInterval; - /** Sequences of log entries matching sets of highlighting queries within an interval */ - logEntryHighlights: InfraLogEntryInterval[]; - - /** A snapshot of nodes */ - snapshot?: InfraSnapshotResponse | null; - - metrics: InfraMetricData[]; -} -/** A set of configuration options for an infrastructure data source */ -export interface InfraSourceConfiguration { - /** The name of the data source */ - name: string; - /** A description of the data source */ - description: string; - /** The alias to read metric data from */ - metricAlias: string; - /** The alias to read log data from */ - logAlias: string; - /** The field mapping to use for this source */ - fields: InfraSourceFields; - /** Default view for inventory */ - inventoryDefaultView: string; - /** Default view for Metrics Explorer */ - metricsExplorerDefaultView?: string | null; - /** The columns to use for log display */ - logColumns: InfraSourceLogColumn[]; -} -/** A mapping of semantic fields to their document counterparts */ -export interface InfraSourceFields { - /** The field to identify a container by */ - container: string; - /** The fields to identify a host by */ - host: string; - /** The fields to use as the log message */ - message: string[]; - /** The field to identify a pod by */ - pod: string; - /** The field to use as a tiebreaker for log events that have identical timestamps */ - tiebreaker: string; - /** The field to use as a timestamp for metrics and logs */ - timestamp: string; -} -/** The built-in timestamp log column */ -export interface InfraSourceTimestampLogColumn { - timestampColumn: InfraSourceTimestampLogColumnAttributes; -} - -export interface InfraSourceTimestampLogColumnAttributes { - /** A unique id for the column */ - id: string; -} -/** The built-in message log column */ -export interface InfraSourceMessageLogColumn { - messageColumn: InfraSourceMessageLogColumnAttributes; -} - -export interface InfraSourceMessageLogColumnAttributes { - /** A unique id for the column */ - id: string; -} -/** A log column containing a field value */ -export interface InfraSourceFieldLogColumn { - fieldColumn: InfraSourceFieldLogColumnAttributes; -} - -export interface InfraSourceFieldLogColumnAttributes { - /** A unique id for the column */ - id: string; - /** The field name this column refers to */ - field: string; -} -/** The status of an infrastructure data source */ -export interface InfraSourceStatus { - /** Whether the configured metric alias exists */ - metricAliasExists: boolean; - /** Whether the configured log alias exists */ - logAliasExists: boolean; - /** Whether the configured alias or wildcard pattern resolve to any metric indices */ - metricIndicesExist: boolean; - /** Whether the configured alias or wildcard pattern resolve to any log indices */ - logIndicesExist: boolean; - /** The list of indices in the metric alias */ - metricIndices: string[]; - /** The list of indices in the log alias */ - logIndices: string[]; - /** The list of fields defined in the index mappings */ - indexFields: InfraIndexField[]; -} -/** A descriptor of a field in an index */ -export interface InfraIndexField { - /** The name of the field */ - name: string; - /** The type of the field's values as recognized by Kibana */ - type: string; - /** Whether the field's values can be efficiently searched for */ - searchable: boolean; - /** Whether the field's values can be aggregated */ - aggregatable: boolean; - /** Whether the field should be displayed based on event.module and a ECS allowed list */ - displayable: boolean; -} -/** A consecutive sequence of log entries */ -export interface InfraLogEntryInterval { - /** The key corresponding to the start of the interval covered by the entries */ - start?: InfraTimeKey | null; - /** The key corresponding to the end of the interval covered by the entries */ - end?: InfraTimeKey | null; - /** Whether there are more log entries available before the start */ - hasMoreBefore: boolean; - /** Whether there are more log entries available after the end */ - hasMoreAfter: boolean; - /** The query the log entries were filtered by */ - filterQuery?: string | null; - /** The query the log entries were highlighted with */ - highlightQuery?: string | null; - /** A list of the log entries */ - entries: InfraLogEntry[]; -} -/** A representation of the log entry's position in the event stream */ -export interface InfraTimeKey { - /** The timestamp of the event that the log entry corresponds to */ - time: number; - /** The tiebreaker that disambiguates events with the same timestamp */ - tiebreaker: number; -} -/** A log entry */ -export interface InfraLogEntry { - /** A unique representation of the log entry's position in the event stream */ - key: InfraTimeKey; - /** The log entry's id */ - gid: string; - /** The source id */ - source: string; - /** The columns used for rendering the log entry */ - columns: InfraLogEntryColumn[]; -} -/** A special built-in column that contains the log entry's timestamp */ -export interface InfraLogEntryTimestampColumn { - /** The id of the corresponding column configuration */ - columnId: string; - /** The timestamp */ - timestamp: number; -} -/** A special built-in column that contains the log entry's constructed message */ -export interface InfraLogEntryMessageColumn { - /** The id of the corresponding column configuration */ - columnId: string; - /** A list of the formatted log entry segments */ - message: InfraLogMessageSegment[]; -} -/** A segment of the log entry message that was derived from a field */ -export interface InfraLogMessageFieldSegment { - /** The field the segment was derived from */ - field: string; - /** The segment's message */ - value: string; - /** A list of highlighted substrings of the value */ - highlights: string[]; -} -/** A segment of the log entry message that was derived from a string literal */ -export interface InfraLogMessageConstantSegment { - /** The segment's message */ - constant: string; -} -/** A column that contains the value of a field of the log entry */ -export interface InfraLogEntryFieldColumn { - /** The id of the corresponding column configuration */ - columnId: string; - /** The field name of the column */ - field: string; - /** The value of the field in the log entry */ - value: string; - /** A list of highlighted substrings of the value */ - highlights: string[]; -} - -export interface InfraSnapshotResponse { - /** Nodes of type host, container or pod grouped by 0, 1 or 2 terms */ - nodes: InfraSnapshotNode[]; -} - -export interface InfraSnapshotNode { - path: InfraSnapshotNodePath[]; - - metric: InfraSnapshotNodeMetric; -} - -export interface InfraSnapshotNodePath { - value: string; - - label: string; - - ip?: string | null; -} - -export interface InfraSnapshotNodeMetric { - name: SnapshotMetricType; - - value?: number | null; - - avg?: number | null; - - max?: number | null; -} - -export interface InfraMetricData { - id?: InfraMetric | null; - - series: InfraDataSeries[]; -} - -export interface InfraDataSeries { - id: string; - - label: string; - - data: InfraDataPoint[]; -} - -export interface InfraDataPoint { - timestamp: number; - - value?: number | null; -} - -export interface Mutation { - /** Create a new source of infrastructure data */ - createSource: UpdateSourceResult; - /** Modify an existing source */ - updateSource: UpdateSourceResult; - /** Delete a source of infrastructure data */ - deleteSource: DeleteSourceResult; -} -/** The result of a successful source update */ -export interface UpdateSourceResult { - /** The source that was updated */ - source: InfraSource; -} -/** The result of a source deletion operations */ -export interface DeleteSourceResult { - /** The id of the source that was deleted */ - id: string; -} - -// ==================================================== -// InputTypes -// ==================================================== - -export interface InfraTimeKeyInput { - time: number; - - tiebreaker: number; -} -/** A highlighting definition */ -export interface InfraLogEntryHighlightInput { - /** The query to highlight by */ - query: string; - /** The number of highlighted documents to include beyond the beginning of the interval */ - countBefore: number; - /** The number of highlighted documents to include beyond the end of the interval */ - countAfter: number; -} - -export interface InfraTimerangeInput { - /** The interval string to use for last bucket. The format is '{value}{unit}'. For example '5m' would return the metrics for the last 5 minutes of the timespan. */ - interval: string; - /** The end of the timerange */ - to: number; - /** The beginning of the timerange */ - from: number; -} - -export interface InfraSnapshotGroupbyInput { - /** The label to use in the results for the group by for the terms group by */ - label?: string | null; - /** The field to group by from a terms aggregation, this is ignored by the filter type */ - field?: string | null; -} - -export interface InfraSnapshotMetricInput { - /** The type of metric */ - type: InfraSnapshotMetricType; -} - -export interface InfraNodeIdsInput { - nodeId: string; - - cloudId?: string | null; -} -/** The properties to update the source with */ -export interface UpdateSourceInput { - /** The name of the data source */ - name?: string | null; - /** A description of the data source */ - description?: string | null; - /** The alias to read metric data from */ - metricAlias?: string | null; - /** The alias to read log data from */ - logAlias?: string | null; - /** The field mapping to use for this source */ - fields?: UpdateSourceFieldsInput | null; - /** Name of default inventory view */ - inventoryDefaultView?: string | null; - /** Default view for Metrics Explorer */ - metricsExplorerDefaultView?: string | null; - /** The log columns to display for this source */ - logColumns?: UpdateSourceLogColumnInput[] | null; -} -/** The mapping of semantic fields of the source to be created */ -export interface UpdateSourceFieldsInput { - /** The field to identify a container by */ - container?: string | null; - /** The fields to identify a host by */ - host?: string | null; - /** The field to identify a pod by */ - pod?: string | null; - /** The field to use as a tiebreaker for log events that have identical timestamps */ - tiebreaker?: string | null; - /** The field to use as a timestamp for metrics and logs */ - timestamp?: string | null; -} -/** One of the log column types to display for this source */ -export interface UpdateSourceLogColumnInput { - /** A custom field log column */ - fieldColumn?: UpdateSourceFieldLogColumnInput | null; - /** A built-in message log column */ - messageColumn?: UpdateSourceMessageLogColumnInput | null; - /** A built-in timestamp log column */ - timestampColumn?: UpdateSourceTimestampLogColumnInput | null; -} - -export interface UpdateSourceFieldLogColumnInput { - id: string; - - field: string; -} - -export interface UpdateSourceMessageLogColumnInput { - id: string; -} - -export interface UpdateSourceTimestampLogColumnInput { - id: string; -} - -// ==================================================== -// Arguments -// ==================================================== - -export interface SourceQueryArgs { - /** The id of the source */ - id: string; -} -export interface LogEntriesAroundInfraSourceArgs { - /** The sort key that corresponds to the point in time */ - key: InfraTimeKeyInput; - /** The maximum number of preceding to return */ - countBefore?: number | null; - /** The maximum number of following to return */ - countAfter?: number | null; - /** The query to filter the log entries by */ - filterQuery?: string | null; -} -export interface LogEntriesBetweenInfraSourceArgs { - /** The sort key that corresponds to the start of the interval */ - startKey: InfraTimeKeyInput; - /** The sort key that corresponds to the end of the interval */ - endKey: InfraTimeKeyInput; - /** The query to filter the log entries by */ - filterQuery?: string | null; -} -export interface LogEntryHighlightsInfraSourceArgs { - /** The sort key that corresponds to the start of the interval */ - startKey: InfraTimeKeyInput; - /** The sort key that corresponds to the end of the interval */ - endKey: InfraTimeKeyInput; - /** The query to filter the log entries by */ - filterQuery?: string | null; - /** The highlighting to apply to the log entries */ - highlights: InfraLogEntryHighlightInput[]; -} -export interface SnapshotInfraSourceArgs { - timerange: InfraTimerangeInput; - - filterQuery?: string | null; -} -export interface MetricsInfraSourceArgs { - nodeIds: InfraNodeIdsInput; - - nodeType: InfraNodeType; - - timerange: InfraTimerangeInput; - - metrics: InfraMetric[]; -} -export interface IndexFieldsInfraSourceStatusArgs { - indexType?: InfraIndexType | null; -} -export interface NodesInfraSnapshotResponseArgs { - type: InfraNodeType; - - groupBy: InfraSnapshotGroupbyInput[]; - - metric: InfraSnapshotMetricInput; -} -export interface CreateSourceMutationArgs { - /** The id of the source */ - id: string; - - sourceProperties: UpdateSourceInput; -} -export interface UpdateSourceMutationArgs { - /** The id of the source */ - id: string; - /** The properties to update the source with */ - sourceProperties: UpdateSourceInput; -} -export interface DeleteSourceMutationArgs { - /** The id of the source */ - id: string; -} - -// ==================================================== -// Enums -// ==================================================== - -export enum InfraIndexType { - ANY = 'ANY', - LOGS = 'LOGS', - METRICS = 'METRICS', -} - -export enum InfraNodeType { - pod = 'pod', - container = 'container', - host = 'host', - awsEC2 = 'awsEC2', - awsS3 = 'awsS3', - awsRDS = 'awsRDS', - awsSQS = 'awsSQS', -} - -export enum InfraSnapshotMetricType { - count = 'count', - cpu = 'cpu', - load = 'load', - memory = 'memory', - tx = 'tx', - rx = 'rx', - logRate = 'logRate', - diskIOReadBytes = 'diskIOReadBytes', - diskIOWriteBytes = 'diskIOWriteBytes', - s3TotalRequests = 's3TotalRequests', - s3NumberOfObjects = 's3NumberOfObjects', - s3BucketSize = 's3BucketSize', - s3DownloadBytes = 's3DownloadBytes', - s3UploadBytes = 's3UploadBytes', - rdsConnections = 'rdsConnections', - rdsQueriesExecuted = 'rdsQueriesExecuted', - rdsActiveTransactions = 'rdsActiveTransactions', - rdsLatency = 'rdsLatency', - sqsMessagesVisible = 'sqsMessagesVisible', - sqsMessagesDelayed = 'sqsMessagesDelayed', - sqsMessagesSent = 'sqsMessagesSent', - sqsMessagesEmpty = 'sqsMessagesEmpty', - sqsOldestMessage = 'sqsOldestMessage', -} - -export enum InfraMetric { - hostSystemOverview = 'hostSystemOverview', - hostCpuUsage = 'hostCpuUsage', - hostFilesystem = 'hostFilesystem', - hostK8sOverview = 'hostK8sOverview', - hostK8sCpuCap = 'hostK8sCpuCap', - hostK8sDiskCap = 'hostK8sDiskCap', - hostK8sMemoryCap = 'hostK8sMemoryCap', - hostK8sPodCap = 'hostK8sPodCap', - hostLoad = 'hostLoad', - hostMemoryUsage = 'hostMemoryUsage', - hostNetworkTraffic = 'hostNetworkTraffic', - hostDockerOverview = 'hostDockerOverview', - hostDockerInfo = 'hostDockerInfo', - hostDockerTop5ByCpu = 'hostDockerTop5ByCpu', - hostDockerTop5ByMemory = 'hostDockerTop5ByMemory', - podOverview = 'podOverview', - podCpuUsage = 'podCpuUsage', - podMemoryUsage = 'podMemoryUsage', - podLogUsage = 'podLogUsage', - podNetworkTraffic = 'podNetworkTraffic', - containerOverview = 'containerOverview', - containerCpuKernel = 'containerCpuKernel', - containerCpuUsage = 'containerCpuUsage', - containerDiskIOOps = 'containerDiskIOOps', - containerDiskIOBytes = 'containerDiskIOBytes', - containerMemory = 'containerMemory', - containerNetworkTraffic = 'containerNetworkTraffic', - nginxHits = 'nginxHits', - nginxRequestRate = 'nginxRequestRate', - nginxActiveConnections = 'nginxActiveConnections', - nginxRequestsPerConnection = 'nginxRequestsPerConnection', - awsOverview = 'awsOverview', - awsCpuUtilization = 'awsCpuUtilization', - awsNetworkBytes = 'awsNetworkBytes', - awsNetworkPackets = 'awsNetworkPackets', - awsDiskioBytes = 'awsDiskioBytes', - awsDiskioOps = 'awsDiskioOps', - awsEC2CpuUtilization = 'awsEC2CpuUtilization', - awsEC2DiskIOBytes = 'awsEC2DiskIOBytes', - awsEC2NetworkTraffic = 'awsEC2NetworkTraffic', - awsS3TotalRequests = 'awsS3TotalRequests', - awsS3NumberOfObjects = 'awsS3NumberOfObjects', - awsS3BucketSize = 'awsS3BucketSize', - awsS3DownloadBytes = 'awsS3DownloadBytes', - awsS3UploadBytes = 'awsS3UploadBytes', - awsRDSCpuTotal = 'awsRDSCpuTotal', - awsRDSConnections = 'awsRDSConnections', - awsRDSQueriesExecuted = 'awsRDSQueriesExecuted', - awsRDSActiveTransactions = 'awsRDSActiveTransactions', - awsRDSLatency = 'awsRDSLatency', - awsSQSMessagesVisible = 'awsSQSMessagesVisible', - awsSQSMessagesDelayed = 'awsSQSMessagesDelayed', - awsSQSMessagesSent = 'awsSQSMessagesSent', - awsSQSMessagesEmpty = 'awsSQSMessagesEmpty', - awsSQSOldestMessage = 'awsSQSOldestMessage', - custom = 'custom', -} - -// ==================================================== -// Unions -// ==================================================== - -/** All known log column types */ -export type InfraSourceLogColumn = - | InfraSourceTimestampLogColumn - | InfraSourceMessageLogColumn - | InfraSourceFieldLogColumn; - -/** A column of a log entry */ -export type InfraLogEntryColumn = - | InfraLogEntryTimestampColumn - | InfraLogEntryMessageColumn - | InfraLogEntryFieldColumn; - -/** A segment of the log entry message */ -export type InfraLogMessageSegment = InfraLogMessageFieldSegment | InfraLogMessageConstantSegment; - -// ==================================================== -// END: Typescript template -// ==================================================== - -// ==================================================== -// Documents -// ==================================================== - -export namespace LogEntryHighlightsQuery { - export type Variables = { - sourceId?: string | null; - startKey: InfraTimeKeyInput; - endKey: InfraTimeKeyInput; - filterQuery?: string | null; - highlights: InfraLogEntryHighlightInput[]; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'InfraSource'; - - id: string; - - logEntryHighlights: LogEntryHighlights[]; - }; - - export type LogEntryHighlights = { - __typename?: 'InfraLogEntryInterval'; - - start?: Start | null; - - end?: End | null; - - entries: Entries[]; - }; - - export type Start = InfraTimeKeyFields.Fragment; - - export type End = InfraTimeKeyFields.Fragment; - - export type Entries = InfraLogEntryHighlightFields.Fragment; -} - -export namespace MetricsQuery { - export type Variables = { - sourceId: string; - timerange: InfraTimerangeInput; - metrics: InfraMetric[]; - nodeId: string; - cloudId?: string | null; - nodeType: InfraNodeType; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'InfraSource'; - - id: string; - - metrics: Metrics[]; - }; - - export type Metrics = { - __typename?: 'InfraMetricData'; - - id?: InfraMetric | null; - - series: Series[]; - }; - - export type Series = { - __typename?: 'InfraDataSeries'; - - id: string; - - label: string; - - data: Data[]; - }; - - export type Data = { - __typename?: 'InfraDataPoint'; - - timestamp: number; - - value?: number | null; - }; -} - -export namespace CreateSourceConfigurationMutation { - export type Variables = { - sourceId: string; - sourceProperties: UpdateSourceInput; - }; - - export type Mutation = { - __typename?: 'Mutation'; - - createSource: CreateSource; - }; - - export type CreateSource = { - __typename?: 'UpdateSourceResult'; - - source: Source; - }; - - export type Source = { - __typename?: 'InfraSource'; - - configuration: Configuration; - - status: Status; - } & InfraSourceFields.Fragment; - - export type Configuration = SourceConfigurationFields.Fragment; - - export type Status = SourceStatusFields.Fragment; -} - -export namespace SourceQuery { - export type Variables = { - sourceId?: string | null; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'InfraSource'; - - configuration: Configuration; - - status: Status; - } & InfraSourceFields.Fragment; - - export type Configuration = SourceConfigurationFields.Fragment; - - export type Status = SourceStatusFields.Fragment; -} - -export namespace UpdateSourceMutation { - export type Variables = { - sourceId?: string | null; - sourceProperties: UpdateSourceInput; - }; - - export type Mutation = { - __typename?: 'Mutation'; - - updateSource: UpdateSource; - }; - - export type UpdateSource = { - __typename?: 'UpdateSourceResult'; - - source: Source; - }; - - export type Source = { - __typename?: 'InfraSource'; - - configuration: Configuration; - - status: Status; - } & InfraSourceFields.Fragment; - - export type Configuration = SourceConfigurationFields.Fragment; - - export type Status = SourceStatusFields.Fragment; -} - -export namespace WaffleNodesQuery { - export type Variables = { - sourceId: string; - timerange: InfraTimerangeInput; - filterQuery?: string | null; - metric: InfraSnapshotMetricInput; - groupBy: InfraSnapshotGroupbyInput[]; - type: InfraNodeType; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'InfraSource'; - - id: string; - - snapshot?: Snapshot | null; - }; - - export type Snapshot = { - __typename?: 'InfraSnapshotResponse'; - - nodes: Nodes[]; - }; - - export type Nodes = { - __typename?: 'InfraSnapshotNode'; - - path: Path[]; - - metric: Metric; - }; - - export type Path = { - __typename?: 'InfraSnapshotNodePath'; - - value: string; - - label: string; - - ip?: string | null; - }; - - export type Metric = { - __typename?: 'InfraSnapshotNodeMetric'; - - name: InfraSnapshotMetricType; - - value?: number | null; - - avg?: number | null; - - max?: number | null; - }; -} - -export namespace LogEntries { - export type Variables = { - sourceId?: string | null; - timeKey: InfraTimeKeyInput; - countBefore?: number | null; - countAfter?: number | null; - filterQuery?: string | null; - }; - - export type Query = { - __typename?: 'Query'; - - source: Source; - }; - - export type Source = { - __typename?: 'InfraSource'; - - id: string; - - logEntriesAround: LogEntriesAround; - }; - - export type LogEntriesAround = { - __typename?: 'InfraLogEntryInterval'; - - start?: Start | null; - - end?: End | null; - - hasMoreBefore: boolean; - - hasMoreAfter: boolean; - - entries: Entries[]; - }; - - export type Start = InfraTimeKeyFields.Fragment; - - export type End = InfraTimeKeyFields.Fragment; - - export type Entries = InfraLogEntryFields.Fragment; -} - -export namespace SourceConfigurationFields { - export type Fragment = { - __typename?: 'InfraSourceConfiguration'; - - name: string; - - description: string; - - logAlias: string; - - metricAlias: string; - - fields: Fields; - - inventoryDefaultView: string; - - metricsExplorerDefaultView: string; - - logColumns: LogColumns[]; - }; - - export type Fields = { - __typename?: 'InfraSourceFields'; - - container: string; - - host: string; - - message: string[]; - - pod: string; - - tiebreaker: string; - - timestamp: string; - }; - - export type LogColumns = - | InfraSourceTimestampLogColumnInlineFragment - | InfraSourceMessageLogColumnInlineFragment - | InfraSourceFieldLogColumnInlineFragment; - - export type InfraSourceTimestampLogColumnInlineFragment = { - __typename?: 'InfraSourceTimestampLogColumn'; - - timestampColumn: TimestampColumn; - }; - - export type TimestampColumn = { - __typename?: 'InfraSourceTimestampLogColumnAttributes'; - - id: string; - }; - - export type InfraSourceMessageLogColumnInlineFragment = { - __typename?: 'InfraSourceMessageLogColumn'; - - messageColumn: MessageColumn; - }; - - export type MessageColumn = { - __typename?: 'InfraSourceMessageLogColumnAttributes'; - - id: string; - }; - - export type InfraSourceFieldLogColumnInlineFragment = { - __typename?: 'InfraSourceFieldLogColumn'; - - fieldColumn: FieldColumn; - }; - - export type FieldColumn = { - __typename?: 'InfraSourceFieldLogColumnAttributes'; - - id: string; - - field: string; - }; -} - -export namespace SourceStatusFields { - export type Fragment = { - __typename?: 'InfraSourceStatus'; - - indexFields: IndexFields[]; - - logIndicesExist: boolean; - - metricIndicesExist: boolean; - }; - - export type IndexFields = { - __typename?: 'InfraIndexField'; - - name: string; - - type: string; - - searchable: boolean; - - aggregatable: boolean; - - displayable: boolean; - }; -} - -export namespace InfraTimeKeyFields { - export type Fragment = { - __typename?: 'InfraTimeKey'; - - time: number; - - tiebreaker: number; - }; -} - -export namespace InfraSourceFields { - export type Fragment = { - __typename?: 'InfraSource'; - - id: string; - - version?: string | null; - - updatedAt?: number | null; - - origin: string; - }; -} - -export namespace InfraLogEntryFields { - export type Fragment = { - __typename?: 'InfraLogEntry'; - - gid: string; - - key: Key; - - columns: Columns[]; - }; - - export type Key = { - __typename?: 'InfraTimeKey'; - - time: number; - - tiebreaker: number; - }; - - export type Columns = - | InfraLogEntryTimestampColumnInlineFragment - | InfraLogEntryMessageColumnInlineFragment - | InfraLogEntryFieldColumnInlineFragment; - - export type InfraLogEntryTimestampColumnInlineFragment = { - __typename?: 'InfraLogEntryTimestampColumn'; - - columnId: string; - - timestamp: number; - }; - - export type InfraLogEntryMessageColumnInlineFragment = { - __typename?: 'InfraLogEntryMessageColumn'; - - columnId: string; - - message: Message[]; - }; - - export type Message = - | InfraLogMessageFieldSegmentInlineFragment - | InfraLogMessageConstantSegmentInlineFragment; - - export type InfraLogMessageFieldSegmentInlineFragment = { - __typename?: 'InfraLogMessageFieldSegment'; - - field: string; - - value: string; - }; - - export type InfraLogMessageConstantSegmentInlineFragment = { - __typename?: 'InfraLogMessageConstantSegment'; - - constant: string; - }; - - export type InfraLogEntryFieldColumnInlineFragment = { - __typename?: 'InfraLogEntryFieldColumn'; - - columnId: string; - - field: string; - - value: string; - }; -} - -export namespace InfraLogEntryHighlightFields { - export type Fragment = { - __typename?: 'InfraLogEntry'; - - gid: string; - - key: Key; - - columns: Columns[]; - }; - - export type Key = { - __typename?: 'InfraTimeKey'; - - time: number; - - tiebreaker: number; - }; - - export type Columns = - | InfraLogEntryMessageColumnInlineFragment - | InfraLogEntryFieldColumnInlineFragment; - - export type InfraLogEntryMessageColumnInlineFragment = { - __typename?: 'InfraLogEntryMessageColumn'; - - columnId: string; - - message: Message[]; - }; - - export type Message = InfraLogMessageFieldSegmentInlineFragment; - - export type InfraLogMessageFieldSegmentInlineFragment = { - __typename?: 'InfraLogMessageFieldSegment'; - - field: string; - - highlights: string[]; - }; - - export type InfraLogEntryFieldColumnInlineFragment = { - __typename?: 'InfraLogEntryFieldColumn'; - - columnId: string; - - field: string; - - highlights: string[]; - }; -} diff --git a/x-pack/plugins/infra/public/lib/lib.ts b/x-pack/plugins/infra/public/lib/lib.ts index 782f6ce5e0eb5..60e1cf0b8bc27 100644 --- a/x-pack/plugins/infra/public/lib/lib.ts +++ b/x-pack/plugins/infra/public/lib/lib.ts @@ -13,7 +13,7 @@ import { SnapshotNodeMetric, SnapshotNodePath, } from '../../common/http_api/snapshot_api'; -import { SourceQuery } from '../graphql/types'; +import { InfraSourceConfigurationFields } from '../../common/http_api/source_api'; import { WaffleSortOption } from '../pages/metrics/inventory_view/hooks/use_waffle_options'; export interface InfraWaffleMapNode { @@ -123,7 +123,7 @@ export enum InfraWaffleMapRuleOperator { } export interface InfraWaffleMapOptions { - fields?: SourceQuery.Query['source']['configuration']['fields'] | null; + fields?: InfraSourceConfigurationFields | null; formatter: InfraFormatterType; formatTemplate: string; metric: SnapshotMetricInput; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/category_example_message.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/category_example_message.tsx index e24fdd06bc6d9..83659ace3ce54 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/category_example_message.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/category_example_message.tsx @@ -127,6 +127,7 @@ export const CategoryExampleMessage: React.FunctionComponent<{ onClick: () => { const logEntry: LogEntry = { id, + index: '', // TODO: use real index when loading via async search context, cursor: { time: timestamp, tiebreaker }, columns: [], diff --git a/x-pack/plugins/infra/public/pages/metrics/index.tsx b/x-pack/plugins/infra/public/pages/metrics/index.tsx index 24f9598484d71..1b2ea9d551a8f 100644 --- a/x-pack/plugins/infra/public/pages/metrics/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/index.tsx @@ -11,6 +11,7 @@ import { Route, RouteComponentProps, Switch } from 'react-router-dom'; import { EuiErrorBoundary, EuiFlexItem, EuiFlexGroup, EuiButtonEmpty } from '@elastic/eui'; import { IIndexPattern } from 'src/plugins/data/common'; +import { InfraSourceConfiguration } from '../../../common/http_api/source_api'; import { DocumentTitle } from '../../components/document_title'; import { HelpCenterContent } from '../../components/help_center_content'; import { RoutedTabs } from '../../components/navigation/routed_tabs'; @@ -36,7 +37,6 @@ import { WaffleFiltersProvider } from './inventory_view/hooks/use_waffle_filters import { InventoryAlertDropdown } from '../../alerting/inventory/components/alert_dropdown'; import { MetricsAlertDropdown } from '../../alerting/metric_threshold/components/alert_dropdown'; import { SavedView } from '../../containers/saved_view/saved_view'; -import { SourceConfigurationFields } from '../../graphql/types'; import { AlertPrefillProvider } from '../../alerting/use_alert_prefill'; import { InfraMLCapabilitiesProvider } from '../../containers/ml/infra_ml_capabilities'; import { AnomalyDetectionFlyout } from './inventory_view/components/ml/anomaly_detection/anomoly_detection_flyout'; @@ -189,7 +189,7 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => { }; const PageContent = (props: { - configuration: SourceConfigurationFields.Fragment; + configuration: InfraSourceConfiguration; createDerivedIndexPattern: (type: 'logs' | 'metrics' | 'both') => IIndexPattern; }) => { const { createDerivedIndexPattern, configuration } = props; diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/bottom_drawer.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/bottom_drawer.tsx index 6055b60719a68..91f5963b134ff 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/bottom_drawer.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/bottom_drawer.tsx @@ -25,7 +25,8 @@ export const BottomDrawer: React.FC<{ measureRef: (instance: HTMLElement | null) => void; interval: string; formatter: InfraFormatter; -}> = ({ measureRef, interval, formatter, children }) => { + width: number; +}> = ({ measureRef, width, interval, formatter, children }) => { const [isOpen, setIsOpen] = useState(false); const trackDrawerOpen = useUiTracker({ app: 'infra_metrics' }); @@ -35,7 +36,7 @@ export const BottomDrawer: React.FC<{ }, [isOpen, trackDrawerOpen]); return ( - + ` +const BottomActionContainer = euiStyled.div<{ isOpen: boolean; outerWidth: number }>` padding: ${(props) => props.theme.eui.paddingSizes.m} 0; position: fixed; - left: 0; bottom: 0; right: 0; transition: transform ${TRANSITION_MS}ms; - transform: translateY(${(props) => (props.isOpen ? 0 : '224px')}) + transform: translateY(${(props) => (props.isOpen ? 0 : '224px')}); + width: ${(props) => props.outerWidth}px; `; const BottomActionTopBar = euiStyled(EuiFlexGroup).attrs({ diff --git a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx index f8b0bbf62d2b5..b3f3efaa9c702 100644 --- a/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/inventory_view/components/layout.tsx @@ -129,65 +129,74 @@ export const Layout = () => { return ( <> - - - {({ measureRef: topActionMeasureRef, bounds: { height: topActionHeight = 0 } }) => ( - <> - - - - - - - - - - - - - - - - - {({ measureRef, bounds: { height = 0 } }) => ( - <> - - {view === 'map' && ( - - + {({ measureRef: pageMeasureRef, bounds: { width = 0 } }) => ( + + + {({ measureRef: topActionMeasureRef, bounds: { height: topActionHeight = 0 } }) => ( + <> + + + + + + + + + + + + + + + + + {({ measureRef, bounds: { height = 0 } }) => ( + <> + - + {view === 'map' && ( + + + + )} + )} - - )} - - - )} - - + + + )} + + + )} + ); diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/helpers.ts b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/helpers.ts index c2cde7eb15e95..3bcf24793b6c9 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/helpers.ts +++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/helpers.ts @@ -8,14 +8,16 @@ import { ReactText } from 'react'; import Color from 'color'; import { get, first, last, min, max } from 'lodash'; import { createFormatter } from '../../../../../common/formatters'; -import { InfraDataSeries } from '../../../../graphql/types'; import { InventoryVisTypeRT, InventoryFormatterType, InventoryVisType, } from '../../../../../common/inventory_models/types'; import { SeriesOverrides } from '../types'; -import { NodeDetailsMetricData } from '../../../../../common/http_api/node_details_api'; +import { + NodeDetailsDataSeries, + NodeDetailsMetricData, +} from '../../../../../common/http_api/node_details_api'; /** * Returns a formatter @@ -28,7 +30,7 @@ export const getFormatter = ( /** * Does a series have more then two points? */ -export const seriesHasLessThen2DataPoints = (series: InfraDataSeries): boolean => { +export const seriesHasLessThen2DataPoints = (series: NodeDetailsDataSeries): boolean => { return series.data.length < 2; }; diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/page_error.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/page_error.tsx index bda2a5941e023..9eb7df26565e8 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/page_error.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/page_error.tsx @@ -4,12 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -// import { GraphQLFormattedError } from 'graphql'; import React from 'react'; import { i18n } from '@kbn/i18n'; import { IHttpFetchError } from 'src/core/public'; import { InvalidNodeError } from './invalid_node'; -// import { InfraMetricsErrorCodes } from '../../../../common/errors'; import { DocumentTitle } from '../../../../components/document_title'; import { ErrorPageBody } from '../../../error'; diff --git a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/series_chart.tsx b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/series_chart.tsx index 0d7716ad3cc66..91b1bb9b0c1cf 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/series_chart.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metric_detail/components/series_chart.tsx @@ -13,14 +13,14 @@ import { BarSeriesStyle, AreaSeriesStyle, } from '@elastic/charts'; -import { InfraDataSeries } from '../../../../graphql/types'; +import { NodeDetailsDataSeries } from '../../../../../common/http_api/node_details_api'; import { InventoryVisType } from '../../../../../common/inventory_models/types'; interface Props { id: string; name: string; color: string | null; - series: InfraDataSeries; + series: NodeDetailsDataSeries; type: InventoryVisType; stack: boolean | undefined; } @@ -59,7 +59,7 @@ export const AreaChart = ({ id, color, series, name, type, stack }: Props) => { ); }; -export const BarChart = ({ id, color, series, name, type, stack }: Props) => { +export const BarChart = ({ id, color, series, name, stack }: Props) => { const style: RecursivePartial = { rectBorder: { stroke: color || void 0, diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart.tsx index c228f09cbb645..256fabdb792fd 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart.tsx @@ -18,6 +18,7 @@ import { } from '@elastic/charts'; import { first, last } from 'lodash'; import moment from 'moment'; +import { InfraSourceConfiguration } from '../../../../../common/http_api/source_api'; import { MetricsExplorerSeries } from '../../../../../common/http_api/metrics_explorer'; import { MetricsExplorerOptions, @@ -29,7 +30,6 @@ import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/comm import { createFormatterForMetric } from './helpers/create_formatter_for_metric'; import { MetricExplorerSeriesChart } from './series_chart'; import { MetricsExplorerChartContextMenu } from './chart_context_menu'; -import { SourceQuery } from '../../../../graphql/types'; import { MetricsExplorerEmptyChart } from './empty_chart'; import { MetricsExplorerNoMetrics } from './no_metrics'; import { getChartTheme } from './helpers/get_chart_theme'; @@ -46,7 +46,7 @@ interface Props { options: MetricsExplorerOptions; chartOptions: MetricsExplorerChartOptions; series: MetricsExplorerSeries; - source: SourceQuery.Query['source']['configuration'] | undefined; + source: InfraSourceConfiguration | undefined; timeRange: MetricsExplorerTimeOptions; onTimeChange: (start: string, end: string) => void; } diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart_context_menu.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart_context_menu.tsx index 5d38fd436fcc5..4fb13f72217b0 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart_context_menu.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/chart_context_menu.tsx @@ -14,6 +14,7 @@ import { } from '@elastic/eui'; import DateMath from '@elastic/datemath'; import { Capabilities } from 'src/core/public'; +import { InfraSourceConfiguration } from '../../../../../common/http_api/source_api'; import { AlertFlyout } from '../../../../alerting/metric_threshold/components/alert_flyout'; import { MetricsExplorerSeries } from '../../../../../common/http_api/metrics_explorer'; import { @@ -23,7 +24,6 @@ import { } from '../hooks/use_metrics_explorer_options'; import { createTSVBLink } from './helpers/create_tsvb_link'; import { getNodeDetailUrl } from '../../../link_to/redirect_to_node_detail'; -import { SourceConfiguration } from '../../../../utils/source_configuration'; import { InventoryItemType } from '../../../../../common/inventory_models/types'; import { useLinkProps } from '../../../../hooks/use_link_props'; @@ -31,14 +31,14 @@ export interface Props { options: MetricsExplorerOptions; onFilter?: (query: string) => void; series: MetricsExplorerSeries; - source?: SourceConfiguration; + source?: InfraSourceConfiguration; timeRange: MetricsExplorerTimeOptions; uiCapabilities?: Capabilities; chartOptions: MetricsExplorerChartOptions; } const fieldToNodeType = ( - source: SourceConfiguration, + source: InfraSourceConfiguration, groupBy: string | string[] ): InventoryItemType | undefined => { const fields = Array.isArray(groupBy) ? groupBy : [groupBy]; diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/charts.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/charts.tsx index 270ccac000637..b9c00101d7b3b 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/charts.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/charts.tsx @@ -8,6 +8,7 @@ import { EuiButton, EuiFlexGrid, EuiFlexItem, EuiText, EuiHorizontalRule } from import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; +import { InfraSourceConfiguration } from '../../../../../common/http_api/source_api'; import { MetricsExplorerResponse } from '../../../../../common/http_api/metrics_explorer'; import { MetricsExplorerOptions, @@ -17,7 +18,6 @@ import { import { InfraLoadingPanel } from '../../../../components/loading'; import { NoData } from '../../../../components/empty_states/no_data'; import { MetricsExplorerChart } from './chart'; -import { SourceQuery } from '../../../../graphql/types'; type StringOrNull = string | null; @@ -30,7 +30,7 @@ interface Props { onFilter: (filter: string) => void; onTimeChange: (start: string, end: string) => void; data: MetricsExplorerResponse | null; - source: SourceQuery.Query['source']['configuration'] | undefined; + source: InfraSourceConfiguration | undefined; timeRange: MetricsExplorerTimeOptions; } export const MetricsExplorerCharts = ({ diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_tsvb_link.ts b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_tsvb_link.ts index 15ed28c095199..90338d714488e 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_tsvb_link.ts +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/components/helpers/create_tsvb_link.ts @@ -7,6 +7,7 @@ import { encode } from 'rison-node'; import uuid from 'uuid'; import { set } from '@elastic/safer-lodash-set'; +import { InfraSourceConfiguration } from '../../../../../../common/http_api/source_api'; import { colorTransformer, Color } from '../../../../../../common/color_palette'; import { MetricsExplorerSeries } from '../../../../../../common/http_api/metrics_explorer'; import { @@ -19,15 +20,14 @@ import { } from '../../hooks/use_metrics_explorer_options'; import { metricToFormat } from './metric_to_format'; import { InfraFormatterType } from '../../../../../lib/lib'; -import { SourceQuery } from '../../../../../graphql/types'; import { createMetricLabel } from './create_metric_label'; import { LinkDescriptor } from '../../../../../hooks/use_link_props'; /* - We've recently changed the default index pattern in Metrics UI from `metricbeat-*` to + We've recently changed the default index pattern in Metrics UI from `metricbeat-*` to `metrics-*,metricbeat-*`. There is a bug in TSVB when there is an empty index in the pattern the field dropdowns are not populated correctly. This index pattern is a temporary fix. - See: https://github.com/elastic/kibana/issues/73987 + See: https://github.com/elastic/kibana/issues/73987 */ const TSVB_WORKAROUND_INDEX_PATTERN = 'metric*'; @@ -142,7 +142,7 @@ const createTSVBIndexPattern = (alias: string) => { }; export const createTSVBLink = ( - source: SourceQuery.Query['source']['configuration'] | undefined, + source: InfraSourceConfiguration | undefined, options: MetricsExplorerOptions, series: MetricsExplorerSeries, timeRange: MetricsExplorerTimeOptions, diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metric_explorer_state.ts b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metric_explorer_state.ts index 169bc9bcbcdb2..7a235b9da64cc 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metric_explorer_state.ts +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metric_explorer_state.ts @@ -6,6 +6,7 @@ import { useState, useCallback, useContext } from 'react'; import { IIndexPattern } from 'src/plugins/data/public'; +import { InfraSourceConfiguration } from '../../../../../common/http_api/source_api'; import { MetricsExplorerMetric, MetricsExplorerAggregation, @@ -17,7 +18,6 @@ import { MetricsExplorerTimeOptions, MetricsExplorerOptions, } from './use_metrics_explorer_options'; -import { SourceQuery } from '../../../../graphql/types'; export interface MetricExplorerViewState { chartOptions: MetricsExplorerChartOptions; @@ -26,7 +26,7 @@ export interface MetricExplorerViewState { } export const useMetricsExplorerState = ( - source: SourceQuery.Query['source']['configuration'], + source: InfraSourceConfiguration, derivedIndexPattern: IIndexPattern, shouldLoadImmediately = true ) => { diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.test.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.test.tsx index f566e5253c615..689a8146afa07 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.test.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.test.tsx @@ -19,9 +19,9 @@ import { createSeries, } from '../../../../utils/fixtures/metrics_explorer'; import { MetricsExplorerOptions, MetricsExplorerTimeOptions } from './use_metrics_explorer_options'; -import { SourceQuery } from '../../../../../common/graphql/types'; import { IIndexPattern } from '../../../../../../../../src/plugins/data/public'; import { HttpHandler } from 'kibana/public'; +import { InfraSourceConfiguration } from '../../../../../common/http_api/source_api'; const mockedFetch = jest.fn(); @@ -37,7 +37,7 @@ const renderUseMetricsExplorerDataHook = () => { return renderHook( (props: { options: MetricsExplorerOptions; - source: SourceQuery.Query['source']['configuration'] | undefined; + source: InfraSourceConfiguration | undefined; derivedIndexPattern: IIndexPattern; timeRange: MetricsExplorerTimeOptions; afterKey: string | null | Record; diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.ts b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.ts index 924f59eb0d1da..1315b5917c979 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.ts +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/hooks/use_metrics_explorer_data.ts @@ -8,7 +8,7 @@ import DateMath from '@elastic/datemath'; import { isEqual } from 'lodash'; import { useEffect, useState, useCallback } from 'react'; import { IIndexPattern } from 'src/plugins/data/public'; -import { SourceQuery } from '../../../../../common/graphql/types'; +import { InfraSourceConfiguration } from '../../../../../common/http_api/source_api'; import { MetricsExplorerResponse, metricsExplorerResponseRT, @@ -24,7 +24,7 @@ function isSameOptions(current: MetricsExplorerOptions, next: MetricsExplorerOpt export function useMetricsExplorerData( options: MetricsExplorerOptions, - source: SourceQuery.Query['source']['configuration'] | undefined, + source: InfraSourceConfiguration | undefined, derivedIndexPattern: IIndexPattern, timerange: MetricsExplorerTimeOptions, afterKey: string | null | Record, diff --git a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx index 20efca79650a1..8ca921f4f2660 100644 --- a/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx +++ b/x-pack/plugins/infra/public/pages/metrics/metrics_explorer/index.tsx @@ -8,8 +8,8 @@ import { EuiErrorBoundary } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useEffect } from 'react'; import { IIndexPattern } from 'src/plugins/data/public'; +import { InfraSourceConfiguration } from '../../../../common/http_api/source_api'; import { useTrackPageview } from '../../../../../observability/public'; -import { SourceQuery } from '../../../../common/graphql/types'; import { DocumentTitle } from '../../../components/document_title'; import { NoData } from '../../../components/empty_states'; import { MetricsExplorerCharts } from './components/charts'; @@ -18,7 +18,7 @@ import { useMetricsExplorerState } from './hooks/use_metric_explorer_state'; import { useSavedViewContext } from '../../../containers/saved_view/saved_view'; interface MetricsExplorerPageProps { - source: SourceQuery.Query['source']['configuration']; + source: InfraSourceConfiguration; derivedIndexPattern: IIndexPattern; } diff --git a/x-pack/plugins/infra/public/test_utils/entries.ts b/x-pack/plugins/infra/public/test_utils/entries.ts index 96737fb175365..1633b9d8dc076 100644 --- a/x-pack/plugins/infra/public/test_utils/entries.ts +++ b/x-pack/plugins/infra/public/test_utils/entries.ts @@ -28,6 +28,7 @@ export function generateFakeEntries( const timestamp = i === count - 1 ? endTimestamp : startTimestamp + timestampStep * i; entries.push({ id: `entry-${i}`, + index: 'logs-fake', context: {}, cursor: { time: timestamp, tiebreaker: i }, columns: columns.map((column) => { diff --git a/x-pack/plugins/infra/public/utils/apollo_client.ts b/x-pack/plugins/infra/public/utils/apollo_client.ts deleted file mode 100644 index 41831a03cabbb..0000000000000 --- a/x-pack/plugins/infra/public/utils/apollo_client.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. - */ - -import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'; -import ApolloClient from 'apollo-client'; -import { ApolloLink } from 'apollo-link'; -import { createHttpLink } from 'apollo-link-http'; -import { withClientState } from 'apollo-link-state'; -import { HttpFetchOptions, HttpHandler } from 'src/core/public'; -import introspectionQueryResultData from '../graphql/introspection.json'; - -export const createApolloClient = (fetch: HttpHandler) => { - const cache = new InMemoryCache({ - addTypename: false, - fragmentMatcher: new IntrospectionFragmentMatcher({ - // @ts-expect-error apollo-cache-inmemory types don't match actual introspection data - introspectionQueryResultData, - }), - }); - - const wrappedFetch = (path: string, options: HttpFetchOptions) => { - return new Promise(async (resolve, reject) => { - // core.http.fetch isn't 100% compatible with the Fetch API and will - // throw Errors on 401s. This top level try / catch handles those scenarios. - try { - fetch(path, { - ...options, - // Set headers to undefined due to this bug: https://github.com/apollographql/apollo-link/issues/249, - // Apollo will try to set a "content-type" header which will conflict with the "Content-Type" header that - // core.http.fetch correctly sets. - headers: undefined, - asResponse: true, - }).then((res) => { - if (!res.response) { - return reject(); - } - // core.http.fetch will parse the Response and set a body before handing it back. As such .text() / .json() - // will have already been called on the Response instance. However, Apollo will also want to call - // .text() / .json() on the instance, as it expects the raw Response instance, rather than core's wrapper. - // .text() / .json() can only be called once, and an Error will be thrown if those methods are accessed again. - // This hacks around that by setting up a new .text() method that will restringify the JSON response we already have. - // This does result in an extra stringify / parse cycle, which isn't ideal, but as we only have a few endpoints left using - // GraphQL this shouldn't create excessive overhead. - // Ref: https://github.com/apollographql/apollo-link/blob/master/packages/apollo-link-http/src/httpLink.ts#L134 - // and - // https://github.com/apollographql/apollo-link/blob/master/packages/apollo-link-http-common/src/index.ts#L125 - return resolve({ - ...res.response, - text: () => { - return new Promise(async (resolveText, rejectText) => { - if (res.body) { - return resolveText(JSON.stringify(res.body)); - } else { - return rejectText(); - } - }); - }, - }); - }); - } catch (error) { - reject(error); - } - }); - }; - - const HttpLink = createHttpLink({ - fetch: wrappedFetch, - uri: `/api/infra/graphql`, - }); - - const graphQLOptions = { - cache, - link: ApolloLink.from([ - withClientState({ - cache, - resolvers: {}, - }), - HttpLink, - ]), - }; - - return new ApolloClient(graphQLOptions); -}; diff --git a/x-pack/plugins/infra/public/utils/apollo_context.ts b/x-pack/plugins/infra/public/utils/apollo_context.ts deleted file mode 100644 index a33a1964c4d8a..0000000000000 --- a/x-pack/plugins/infra/public/utils/apollo_context.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 { ApolloClient } from 'apollo-client'; -import { createContext, useContext } from 'react'; - -/** - * This is a temporary provider and hook for use with hooks until react-apollo - * has upgraded to the new-style `createContext` api. - */ - -export const ApolloClientContext = createContext | undefined>(undefined); - -export const useApolloClient = () => { - return useContext(ApolloClientContext); -}; - -export class DependencyError extends Error { - constructor(message?: string) { - super(message); - Object.setPrototypeOf(this, new.target.prototype); - } -} diff --git a/x-pack/plugins/infra/public/utils/data_search/data_search.stories.mdx b/x-pack/plugins/infra/public/utils/data_search/data_search.stories.mdx index a698b806b4cd7..a8854692caa36 100644 --- a/x-pack/plugins/infra/public/utils/data_search/data_search.stories.mdx +++ b/x-pack/plugins/infra/public/utils/data_search/data_search.stories.mdx @@ -43,15 +43,35 @@ be issued by calling the returned `search()` function. For each new request the hook emits an object describing the request and its state in the `requests$` `Observable`. +Since the specific response shape depends on the data strategy used, the hook +takes a projection function, that is responsible for decoding the response in +an appropriate way. Because most response projections follow a similar pattern +there's a helper `normalizeDataSearchResponses(initialResponse, +parseRawResponse)`, which generates an RxJS operator, that... + +- emits an initial response containing the given `initialResponse` value +- applies `parseRawResponse` to the `rawResponse` property of each emitted response +- transforms transport layer errors as well as parsing errors into + `SearchStrategyError`s + ```typescript +const parseMyCustomSearchResponse = normalizeDataSearchResponses( + 'initial value', + decodeOrThrow(myCustomSearchResponsePayloadRT) +); + const { search, requests$ } = useDataSearch({ getRequest: useCallback((searchTerm: string) => ({ request: { params: { searchTerm - } - } - }), []); + }, + options: { + strategy: 'my-custom-search-strategy', + }, + }, + }), []), + parseResponses: parseMyCustomSearchResponse, }); ``` @@ -68,10 +88,6 @@ observables are unsubscribed from for proper cancellation if a new request has been created. This uses RxJS's `switchMap()` operator under the hood. The hook also makes sure that all observables are unsubscribed from on unmount. -Since the specific response shape depends on the data strategy used, the hook -takes a projection function, that is responsible for decoding the response in -an appropriate way. - A request can fail due to various reasons that include servers-side errors, Elasticsearch shard failures and network failures. The intention is to map all of them to a common `SearchStrategyError` interface. While the @@ -94,11 +110,7 @@ const { latestResponseErrors, loaded, total, -} = useLatestPartialDataSearchResponse( - requests$, - 'initialValue', - useMemo(() => decodeOrThrow(mySearchStrategyResponsePayloadRT), []), -); +} = useLatestPartialDataSearchResponse(requests$); ``` ## Representing the request state to the user diff --git a/x-pack/plugins/infra/public/utils/data_search/flatten_data_search_response.ts b/x-pack/plugins/infra/public/utils/data_search/flatten_data_search_response.ts new file mode 100644 index 0000000000000..98df6d441bd80 --- /dev/null +++ b/x-pack/plugins/infra/public/utils/data_search/flatten_data_search_response.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 { map } from 'rxjs/operators'; +import { IKibanaSearchRequest } from '../../../../../../src/plugins/data/public'; +import { ParsedDataSearchRequestDescriptor } from './types'; + +export const flattenDataSearchResponseDescriptor = < + Request extends IKibanaSearchRequest, + Response +>({ + abortController, + options, + request, + response$, +}: ParsedDataSearchRequestDescriptor) => + response$.pipe( + map((response) => { + return { + abortController, + options, + request, + response, + }; + }) + ); diff --git a/x-pack/plugins/infra/public/utils/data_search/index.ts b/x-pack/plugins/infra/public/utils/data_search/index.ts index c08ab0727fd90..10beba4aa4fdc 100644 --- a/x-pack/plugins/infra/public/utils/data_search/index.ts +++ b/x-pack/plugins/infra/public/utils/data_search/index.ts @@ -4,6 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +export * from './flatten_data_search_response'; +export * from './normalize_data_search_responses'; export * from './types'; export * from './use_data_search_request'; +export * from './use_data_search_response_state'; export * from './use_latest_partial_data_search_response'; diff --git a/x-pack/plugins/infra/public/utils/data_search/normalize_data_search_responses.ts b/x-pack/plugins/infra/public/utils/data_search/normalize_data_search_responses.ts new file mode 100644 index 0000000000000..5046cc128a835 --- /dev/null +++ b/x-pack/plugins/infra/public/utils/data_search/normalize_data_search_responses.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 { Observable, of } from 'rxjs'; +import { catchError, map, startWith } from 'rxjs/operators'; +import { IKibanaSearchResponse } from '../../../../../../src/plugins/data/public'; +import { AbortError } from '../../../../../../src/plugins/kibana_utils/public'; +import { SearchStrategyError } from '../../../common/search_strategies/common/errors'; +import { ParsedKibanaSearchResponse } from './types'; + +export type RawResponseParser = ( + rawResponse: RawResponse +) => { data: Response; errors?: SearchStrategyError[] }; + +/** + * An operator factory that normalizes each {@link IKibanaSearchResponse} by + * parsing it into a {@link ParsedKibanaSearchResponse} and adding initial + * responses and error handling. + * + * @param initialResponse - The initial value to emit when a new request is + * handled. + * @param projectResponse - The projection function to apply to each response + * payload. It should validate that the response payload is of the type {@link + * RawResponse} and decode it to a {@link Response}. + * + * @return An operator that adds parsing and error handling transformations to + * each response payload using the arguments given above. + */ +export const normalizeDataSearchResponses = ( + initialResponse: InitialResponse, + parseRawResponse: RawResponseParser +) => ( + response$: Observable> +): Observable> => + response$.pipe( + map((response) => { + const { data, errors = [] } = parseRawResponse(response.rawResponse); + return { + data, + errors, + isPartial: response.isPartial ?? false, + isRunning: response.isRunning ?? false, + loaded: response.loaded, + total: response.total, + }; + }), + startWith({ + data: initialResponse, + errors: [], + isPartial: true, + isRunning: true, + loaded: 0, + total: undefined, + }), + catchError((error) => + of({ + data: initialResponse, + errors: [ + error instanceof AbortError + ? { + type: 'aborted' as const, + } + : { + type: 'generic' as const, + message: `${error.message ?? error}`, + }, + ], + isPartial: true, + isRunning: false, + loaded: 0, + total: undefined, + }) + ) + ); diff --git a/x-pack/plugins/infra/public/utils/data_search/types.ts b/x-pack/plugins/infra/public/utils/data_search/types.ts index ba0a4c639dae4..4fcb5898ea5bd 100644 --- a/x-pack/plugins/infra/public/utils/data_search/types.ts +++ b/x-pack/plugins/infra/public/utils/data_search/types.ts @@ -19,7 +19,17 @@ export interface DataSearchRequestDescriptor { +export interface ParsedDataSearchRequestDescriptor< + Request extends IKibanaSearchRequest, + ResponseData +> { + request: Request; + options: ISearchOptions; + response$: Observable>; + abortController: AbortController; +} + +export interface ParsedKibanaSearchResponse { total?: number; loaded?: number; isRunning: boolean; @@ -28,9 +38,12 @@ export interface NormalizedKibanaSearchResponse { errors: SearchStrategyError[]; } -export interface DataSearchResponseDescriptor { +export interface ParsedDataSearchResponseDescriptor< + Request extends IKibanaSearchRequest, + Response +> { request: Request; options: ISearchOptions; - response: NormalizedKibanaSearchResponse; + response: ParsedKibanaSearchResponse; abortController: AbortController; } diff --git a/x-pack/plugins/infra/public/utils/data_search/use_data_search_request.test.tsx b/x-pack/plugins/infra/public/utils/data_search/use_data_search_request.test.tsx index 87c091f12ad90..780476abb7b1b 100644 --- a/x-pack/plugins/infra/public/utils/data_search/use_data_search_request.test.tsx +++ b/x-pack/plugins/infra/public/utils/data_search/use_data_search_request.test.tsx @@ -17,6 +17,7 @@ import { import { dataPluginMock } from '../../../../../../src/plugins/data/public/mocks'; import { createKibanaReactContext } from '../../../../../../src/plugins/kibana_react/public'; import { PluginKibanaContextValue } from '../../hooks/use_kibana'; +import { normalizeDataSearchResponses } from './normalize_data_search_responses'; import { useDataSearch } from './use_data_search_request'; describe('useDataSearch hook', () => { @@ -34,6 +35,7 @@ describe('useDataSearch hook', () => { () => useDataSearch({ getRequest, + parseResponses: noopParseResponse, }), { wrapper: ({ children }) => {children}, @@ -48,7 +50,7 @@ describe('useDataSearch hook', () => { expect(dataMock.search.search).not.toHaveBeenCalled(); }); - it('creates search requests with the given params and options', async () => { + it('creates search requests with the given params and options and parses the responses', async () => { const dataMock = createDataPluginMock(); const searchResponseMock$ = of({ rawResponse: { @@ -78,6 +80,7 @@ describe('useDataSearch hook', () => { () => useDataSearch({ getRequest, + parseResponses: noopParseResponse, }), { wrapper: ({ children }) => {children}, @@ -112,10 +115,11 @@ describe('useDataSearch hook', () => { }); expect(firstRequest).toHaveProperty('options.strategy', 'test-search-strategy'); expect(firstRequest).toHaveProperty('response$', expect.any(Observable)); - await expect(firstRequest.response$.toPromise()).resolves.toEqual({ - rawResponse: { - firstKey: 'firstValue', + await expect(firstRequest.response$.toPromise()).resolves.toMatchObject({ + data: { + firstKey: 'firstValue', // because this specific response parser just copies the raw response }, + errors: [], }); }); @@ -145,6 +149,7 @@ describe('useDataSearch hook', () => { () => useDataSearch({ getRequest, + parseResponses: noopParseResponse, }), { wrapper: ({ children }) => {children}, @@ -186,3 +191,8 @@ const createDataPluginMock = () => { }; return dataMock; }; + +const noopParseResponse = normalizeDataSearchResponses( + null, + (response: Response) => ({ data: response }) +); diff --git a/x-pack/plugins/infra/public/utils/data_search/use_data_search_request.ts b/x-pack/plugins/infra/public/utils/data_search/use_data_search_request.ts index a23f06adc0353..0f1686a93be82 100644 --- a/x-pack/plugins/infra/public/utils/data_search/use_data_search_request.ts +++ b/x-pack/plugins/infra/public/utils/data_search/use_data_search_request.ts @@ -5,8 +5,8 @@ */ import { useCallback } from 'react'; -import { Subject } from 'rxjs'; -import { map, share, switchMap, tap } from 'rxjs/operators'; +import { OperatorFunction, Subject } from 'rxjs'; +import { share, tap } from 'rxjs/operators'; import { IKibanaSearchRequest, IKibanaSearchResponse, @@ -14,6 +14,7 @@ import { } from '../../../../../../src/plugins/data/public'; import { useKibanaContextForPlugin } from '../../hooks/use_kibana'; import { tapUnsubscribe, useObservable } from '../use_observable'; +import { ParsedDataSearchRequestDescriptor, ParsedKibanaSearchResponse } from './types'; export type DataSearchRequestFactory = ( ...args: Args @@ -25,69 +26,74 @@ export type DataSearchRequestFactory = OperatorFunction< + IKibanaSearchResponse, + ParsedKibanaSearchResponse +>; + export const useDataSearch = < RequestFactoryArgs extends any[], - Request extends IKibanaSearchRequest, - RawResponse + RequestParams, + Request extends IKibanaSearchRequest, + RawResponse, + Response >({ getRequest, + parseResponses, }: { getRequest: DataSearchRequestFactory; + parseResponses: ParseResponsesOperator; }) => { const { services } = useKibanaContextForPlugin(); - const request$ = useObservable( - () => new Subject<{ request: Request; options: ISearchOptions }>(), - [] - ); const requests$ = useObservable( - (inputs$) => - inputs$.pipe( - switchMap(([currentRequest$]) => currentRequest$), - map(({ request, options }) => { - const abortController = new AbortController(); - let isAbortable = true; - - return { - abortController, - request, - options, - response$: services.data.search - .search>(request, { - abortSignal: abortController.signal, - ...options, - }) - .pipe( - // avoid aborting failed or completed requests - tap({ - error: () => { - isAbortable = false; - }, - complete: () => { - isAbortable = false; - }, - }), - tapUnsubscribe(() => { - if (isAbortable) { - abortController.abort(); - } - }), - share() - ), - }; - }) - ), - [request$] + () => new Subject>(), + [] ); const search = useCallback( (...args: RequestFactoryArgs) => { - const request = getRequest(...args); + const requestArgs = getRequest(...args); - if (request) { - request$.next(request); + if (requestArgs == null) { + return; } + + const abortController = new AbortController(); + let isAbortable = true; + + const newRequestDescriptor = { + ...requestArgs, + abortController, + response$: services.data.search + .search>(requestArgs.request, { + abortSignal: abortController.signal, + ...requestArgs.options, + }) + .pipe( + // avoid aborting failed or completed requests + tap({ + error: () => { + isAbortable = false; + }, + complete: () => { + isAbortable = false; + }, + }), + tapUnsubscribe(() => { + if (isAbortable) { + abortController.abort(); + } + }), + parseResponses, + share() + ), + }; + + requests$.next(newRequestDescriptor); + + return newRequestDescriptor; }, - [getRequest, request$] + [getRequest, services.data.search, parseResponses, requests$] ); return { diff --git a/x-pack/plugins/infra/public/utils/data_search/use_data_search_response_state.ts b/x-pack/plugins/infra/public/utils/data_search/use_data_search_response_state.ts new file mode 100644 index 0000000000000..3b37b80f26cdc --- /dev/null +++ b/x-pack/plugins/infra/public/utils/data_search/use_data_search_response_state.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 { useCallback } from 'react'; +import { Observable } from 'rxjs'; +import { IKibanaSearchRequest } from '../../../../../../src/plugins/data/public'; +import { useObservableState } from '../use_observable'; +import { ParsedDataSearchResponseDescriptor } from './types'; + +export const useDataSearchResponseState = < + Request extends IKibanaSearchRequest, + Response, + InitialResponse +>( + response$: Observable> +) => { + const { latestValue } = useObservableState(response$, undefined); + + const cancelRequest = useCallback(() => { + latestValue?.abortController.abort(); + }, [latestValue]); + + return { + cancelRequest, + isRequestRunning: latestValue?.response.isRunning ?? false, + isResponsePartial: latestValue?.response.isPartial ?? false, + latestResponseData: latestValue?.response.data, + latestResponseErrors: latestValue?.response.errors, + loaded: latestValue?.response.loaded, + total: latestValue?.response.total, + }; +}; diff --git a/x-pack/plugins/infra/public/utils/data_search/use_latest_partial_data_search_response.test.tsx b/x-pack/plugins/infra/public/utils/data_search/use_latest_partial_data_search_response.test.tsx index 4c336aa1107a2..864d92f43bc17 100644 --- a/x-pack/plugins/infra/public/utils/data_search/use_latest_partial_data_search_response.test.tsx +++ b/x-pack/plugins/infra/public/utils/data_search/use_latest_partial_data_search_response.test.tsx @@ -5,12 +5,9 @@ */ import { act, renderHook } from '@testing-library/react-hooks'; -import { Observable, of, Subject } from 'rxjs'; -import { - IKibanaSearchRequest, - IKibanaSearchResponse, -} from '../../../../../../src/plugins/data/public'; -import { DataSearchRequestDescriptor } from './types'; +import { BehaviorSubject, Observable, of, Subject } from 'rxjs'; +import { IKibanaSearchRequest } from '../../../../../../src/plugins/data/public'; +import { ParsedDataSearchRequestDescriptor, ParsedKibanaSearchResponse } from './types'; import { useLatestPartialDataSearchResponse } from './use_latest_partial_data_search_response'; describe('useLatestPartialDataSearchResponse hook', () => { @@ -19,25 +16,31 @@ describe('useLatestPartialDataSearchResponse hook', () => { abortController: new AbortController(), options: {}, request: { params: 'firstRequestParam' }, - response$: new Subject>(), + response$: new BehaviorSubject>({ + data: 'initial', + isRunning: true, + isPartial: true, + errors: [], + }), }; const secondRequest = { abortController: new AbortController(), options: {}, request: { params: 'secondRequestParam' }, - response$: new Subject>(), + response$: new BehaviorSubject>({ + data: 'initial', + isRunning: true, + isPartial: true, + errors: [], + }), }; const requests$ = new Subject< - DataSearchRequestDescriptor, string> + ParsedDataSearchRequestDescriptor, string> >(); - const { result } = renderHook(() => - useLatestPartialDataSearchResponse(requests$, 'initial', (response) => ({ - data: `projection of ${response}`, - })) - ); + const { result } = renderHook(() => useLatestPartialDataSearchResponse(requests$)); expect(result).toHaveProperty('current.isRequestRunning', false); expect(result).toHaveProperty('current.latestResponseData', undefined); @@ -52,37 +55,43 @@ describe('useLatestPartialDataSearchResponse hook', () => { // first response of the first request arrives act(() => { - firstRequest.response$.next({ rawResponse: 'request-1-response-1', isRunning: true }); + firstRequest.response$.next({ + data: 'request-1-response-1', + isRunning: true, + isPartial: true, + errors: [], + }); }); expect(result).toHaveProperty('current.isRequestRunning', true); - expect(result).toHaveProperty( - 'current.latestResponseData', - 'projection of request-1-response-1' - ); + expect(result).toHaveProperty('current.latestResponseData', 'request-1-response-1'); // second request is started before the second response of the first request arrives act(() => { requests$.next(secondRequest); - secondRequest.response$.next({ rawResponse: 'request-2-response-1', isRunning: true }); + secondRequest.response$.next({ + data: 'request-2-response-1', + isRunning: true, + isPartial: true, + errors: [], + }); }); expect(result).toHaveProperty('current.isRequestRunning', true); - expect(result).toHaveProperty( - 'current.latestResponseData', - 'projection of request-2-response-1' - ); + expect(result).toHaveProperty('current.latestResponseData', 'request-2-response-1'); // second response of the second request arrives act(() => { - secondRequest.response$.next({ rawResponse: 'request-2-response-2', isRunning: false }); + secondRequest.response$.next({ + data: 'request-2-response-2', + isRunning: false, + isPartial: false, + errors: [], + }); }); expect(result).toHaveProperty('current.isRequestRunning', false); - expect(result).toHaveProperty( - 'current.latestResponseData', - 'projection of request-2-response-2' - ); + expect(result).toHaveProperty('current.latestResponseData', 'request-2-response-2'); }); it("unsubscribes from the latest request's response observable on unmount", () => { @@ -92,20 +101,16 @@ describe('useLatestPartialDataSearchResponse hook', () => { abortController: new AbortController(), options: {}, request: { params: 'firstRequestParam' }, - response$: new Observable>(() => { + response$: new Observable>(() => { return onUnsubscribe; }), }; - const requests$ = of, string>>( + const requests$ = of, string>>( firstRequest ); - const { unmount } = renderHook(() => - useLatestPartialDataSearchResponse(requests$, 'initial', (response) => ({ - data: `projection of ${response}`, - })) - ); + const { unmount } = renderHook(() => useLatestPartialDataSearchResponse(requests$)); expect(onUnsubscribe).not.toHaveBeenCalled(); diff --git a/x-pack/plugins/infra/public/utils/data_search/use_latest_partial_data_search_response.ts b/x-pack/plugins/infra/public/utils/data_search/use_latest_partial_data_search_response.ts index 71fd96283d0ef..9366df8adbaf7 100644 --- a/x-pack/plugins/infra/public/utils/data_search/use_latest_partial_data_search_response.ts +++ b/x-pack/plugins/infra/public/utils/data_search/use_latest_partial_data_search_response.ts @@ -4,111 +4,40 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useCallback } from 'react'; -import { Observable, of } from 'rxjs'; -import { catchError, map, startWith, switchMap } from 'rxjs/operators'; +import { Observable } from 'rxjs'; +import { switchMap } from 'rxjs/operators'; import { IKibanaSearchRequest } from '../../../../../../src/plugins/data/public'; -import { AbortError } from '../../../../../../src/plugins/kibana_utils/public'; -import { SearchStrategyError } from '../../../common/search_strategies/common/errors'; -import { useLatest, useObservable, useObservableState } from '../use_observable'; -import { DataSearchRequestDescriptor, DataSearchResponseDescriptor } from './types'; +import { useOperator } from '../use_observable'; +import { flattenDataSearchResponseDescriptor } from './flatten_data_search_response'; +import { ParsedDataSearchRequestDescriptor, ParsedDataSearchResponseDescriptor } from './types'; +import { useDataSearchResponseState } from './use_data_search_response_state'; -export const useLatestPartialDataSearchResponse = < - Request extends IKibanaSearchRequest, - RawResponse, - Response, - InitialResponse ->( - requests$: Observable>, - initialResponse: InitialResponse, - projectResponse: (rawResponse: RawResponse) => { data: Response; errors?: SearchStrategyError[] } +export const useLatestPartialDataSearchResponse = ( + requests$: Observable> ) => { - const latestInitialResponse = useLatest(initialResponse); - const latestProjectResponse = useLatest(projectResponse); - const latestResponse$: Observable< - DataSearchResponseDescriptor - > = useObservable( - (inputs$) => - inputs$.pipe( - switchMap(([currentRequests$]) => - currentRequests$.pipe( - switchMap(({ abortController, options, request, response$ }) => - response$.pipe( - map((response) => { - const { data, errors = [] } = latestProjectResponse.current(response.rawResponse); - return { - abortController, - options, - request, - response: { - data, - errors, - isPartial: response.isPartial ?? false, - isRunning: response.isRunning ?? false, - loaded: response.loaded, - total: response.total, - }, - }; - }), - startWith({ - abortController, - options, - request, - response: { - data: latestInitialResponse.current, - errors: [], - isPartial: true, - isRunning: true, - loaded: 0, - total: undefined, - }, - }), - catchError((error) => - of({ - abortController, - options, - request, - response: { - data: latestInitialResponse.current, - errors: [ - error instanceof AbortError - ? { - type: 'aborted' as const, - } - : { - type: 'generic' as const, - message: `${error.message ?? error}`, - }, - ], - isPartial: true, - isRunning: false, - loaded: 0, - total: undefined, - }, - }) - ) - ) - ) - ) - ) - ), - [requests$] as const - ); - - const { latestValue } = useObservableState(latestResponse$, undefined); + ParsedDataSearchResponseDescriptor + > = useOperator(requests$, flattenLatestDataSearchResponse); - const cancelRequest = useCallback(() => { - latestValue?.abortController.abort(); - }, [latestValue]); + const { + cancelRequest, + isRequestRunning, + isResponsePartial, + latestResponseData, + latestResponseErrors, + loaded, + total, + } = useDataSearchResponseState(latestResponse$); return { cancelRequest, - isRequestRunning: latestValue?.response.isRunning ?? false, - isResponsePartial: latestValue?.response.isPartial ?? false, - latestResponseData: latestValue?.response.data, - latestResponseErrors: latestValue?.response.errors, - loaded: latestValue?.response.loaded, - total: latestValue?.response.total, + isRequestRunning, + isResponsePartial, + latestResponseData, + latestResponseErrors, + loaded, + total, }; }; + +const flattenLatestDataSearchResponse = switchMap(flattenDataSearchResponseDescriptor); diff --git a/x-pack/plugins/infra/public/utils/log_entry/log_entry.ts b/x-pack/plugins/infra/public/utils/log_entry/log_entry.ts index c69104ad6177e..60034aea6be63 100644 --- a/x-pack/plugins/infra/public/utils/log_entry/log_entry.ts +++ b/x-pack/plugins/infra/public/utils/log_entry/log_entry.ts @@ -5,9 +5,7 @@ */ import { bisector } from 'd3-array'; - import { compareToTimeKey, getIndexAtTimeKey, TimeKey, UniqueTimeKey } from '../../../common/time'; -import { InfraLogEntryFields } from '../../graphql/types'; import { LogEntry, LogColumn, @@ -19,10 +17,6 @@ import { LogMessageConstantPart, } from '../../../common/log_entry'; -export type LogEntryMessageSegment = InfraLogEntryFields.Message; -export type LogEntryConstantMessageSegment = InfraLogEntryFields.InfraLogMessageConstantSegmentInlineFragment; -export type LogEntryFieldMessageSegment = InfraLogEntryFields.InfraLogMessageFieldSegmentInlineFragment; - export const getLogEntryKey = (entry: { cursor: TimeKey }) => entry.cursor; export const getUniqueLogEntryKey = (entry: { id: string; cursor: TimeKey }): UniqueTimeKey => ({ diff --git a/x-pack/plugins/infra/public/utils/log_entry/log_entry_highlight.ts b/x-pack/plugins/infra/public/utils/log_entry/log_entry_highlight.ts index 208316c693d4d..e14d938c426f9 100644 --- a/x-pack/plugins/infra/public/utils/log_entry/log_entry_highlight.ts +++ b/x-pack/plugins/infra/public/utils/log_entry/log_entry_highlight.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { InfraLogEntryHighlightFields } from '../../graphql/types'; import { LogEntry, LogColumn, @@ -14,13 +13,6 @@ import { LogMessageFieldPart, } from '../../../common/log_entry'; -export type LogEntryHighlightColumn = InfraLogEntryHighlightFields.Columns; -export type LogEntryHighlightMessageColumn = InfraLogEntryHighlightFields.InfraLogEntryMessageColumnInlineFragment; -export type LogEntryHighlightFieldColumn = InfraLogEntryHighlightFields.InfraLogEntryFieldColumnInlineFragment; - -export type LogEntryHighlightMessageSegment = InfraLogEntryHighlightFields.Message | {}; -export type LogEntryHighlightFieldMessageSegment = InfraLogEntryHighlightFields.InfraLogMessageFieldSegmentInlineFragment; - export interface LogEntryHighlightsMap { [entryId: string]: LogEntry[]; } diff --git a/x-pack/plugins/infra/public/utils/source_configuration.ts b/x-pack/plugins/infra/public/utils/source_configuration.ts index 5104ed46000fa..cbffe7826f81b 100644 --- a/x-pack/plugins/infra/public/utils/source_configuration.ts +++ b/x-pack/plugins/infra/public/utils/source_configuration.ts @@ -4,14 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SourceConfigurationFields } from '../graphql/types'; +import { + InfraSavedSourceConfigurationColumn, + InfraSavedSourceConfigurationFields, + InfraSourceConfigurationMessageColumn, + InfraSourceConfigurationTimestampColumn, +} from '../../common/http_api/source_api'; -export type SourceConfiguration = SourceConfigurationFields.Fragment; - -export type LogColumnConfiguration = SourceConfigurationFields.LogColumns; -export type FieldLogColumnConfiguration = SourceConfigurationFields.InfraSourceFieldLogColumnInlineFragment; -export type MessageLogColumnConfiguration = SourceConfigurationFields.InfraSourceMessageLogColumnInlineFragment; -export type TimestampLogColumnConfiguration = SourceConfigurationFields.InfraSourceTimestampLogColumnInlineFragment; +export type LogColumnConfiguration = InfraSavedSourceConfigurationColumn; +export type FieldLogColumnConfiguration = InfraSavedSourceConfigurationFields; +export type MessageLogColumnConfiguration = InfraSourceConfigurationMessageColumn; +export type TimestampLogColumnConfiguration = InfraSourceConfigurationTimestampColumn; export const isFieldLogColumnConfiguration = ( logColumnConfiguration: LogColumnConfiguration diff --git a/x-pack/plugins/infra/public/utils/use_observable.ts b/x-pack/plugins/infra/public/utils/use_observable.ts index 342aa5aa797b1..508684f8d7268 100644 --- a/x-pack/plugins/infra/public/utils/use_observable.ts +++ b/x-pack/plugins/infra/public/utils/use_observable.ts @@ -5,7 +5,8 @@ */ import { useEffect, useRef, useState } from 'react'; -import { BehaviorSubject, Observable, PartialObserver, Subscription } from 'rxjs'; +import { BehaviorSubject, Observable, OperatorFunction, PartialObserver, Subscription } from 'rxjs'; +import { switchMap } from 'rxjs/operators'; export const useLatest = (value: Value) => { const valueRef = useRef(value); @@ -62,7 +63,9 @@ export const useSubscription = ( const fixedUnsubscribe = latestUnsubscribe.current; const subscription = input$.subscribe({ - next: (value) => latestNext.current?.(value), + next: (value) => { + return latestNext.current?.(value); + }, error: (value) => latestError.current?.(value), complete: () => latestComplete.current?.(), }); @@ -78,6 +81,19 @@ export const useSubscription = ( return latestSubscription.current; }; +export const useOperator = ( + input$: Observable, + operator: OperatorFunction +) => { + const latestOperator = useLatest(operator); + + return useObservable( + (inputs$) => + inputs$.pipe(switchMap(([currentInput$]) => latestOperator.current(currentInput$))), + [input$] as const + ); +}; + export const tapUnsubscribe = (onUnsubscribe: () => void) => (source$: Observable) => { return new Observable((subscriber) => { const subscription = source$.subscribe({ diff --git a/x-pack/plugins/infra/scripts/combined_schema.ts b/x-pack/plugins/infra/scripts/combined_schema.ts deleted file mode 100644 index 4813a3b674b31..0000000000000 --- a/x-pack/plugins/infra/scripts/combined_schema.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 { buildSchemaFromTypeDefinitions } from 'graphql-tools'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { schemas as serverSchemas } from '../server/graphql'; - -export const schemas = [...serverSchemas]; - -// this default export is used to feed the combined types to the gql-gen tool -// which generates the corresponding typescript types -// eslint-disable-next-line import/no-default-export -export default buildSchemaFromTypeDefinitions(schemas); diff --git a/x-pack/plugins/infra/scripts/generate_types_from_graphql.js b/x-pack/plugins/infra/scripts/generate_types_from_graphql.js deleted file mode 100644 index aec5ff6da99ce..0000000000000 --- a/x-pack/plugins/infra/scripts/generate_types_from_graphql.js +++ /dev/null @@ -1,75 +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. - */ - -require('../../../../src/setup_node_env'); - -const { join, resolve } = require('path'); -// eslint-disable-next-line import/no-extraneous-dependencies, import/no-unresolved -const { generate } = require('graphql-code-generator'); - -const GRAPHQL_GLOBS = [ - join('public', 'containers', '**', '*.gql_query.ts{,x}'), - join('public', 'store', '**', '*.gql_query.ts{,x}'), - join('common', 'graphql', '**', '*.gql_query.ts{,x}'), -]; -const CLIENT_CONFIG_PATH = resolve(__dirname, 'gql_gen_client.json'); -const SERVER_CONFIG_PATH = resolve(__dirname, 'gql_gen_server.json'); -const OUTPUT_INTROSPECTION_PATH = resolve('public', 'graphql', 'introspection.json'); -const OUTPUT_CLIENT_TYPES_PATH = resolve('public', 'graphql', 'types.ts'); -const OUTPUT_COMMON_TYPES_PATH = resolve('common', 'graphql', 'types.ts'); -const OUTPUT_SERVER_TYPES_PATH = resolve('server', 'graphql', 'types.ts'); -const SCHEMA_PATH = resolve(__dirname, 'combined_schema.ts'); - -async function main() { - await generate( - { - args: GRAPHQL_GLOBS, - config: SERVER_CONFIG_PATH, - out: OUTPUT_INTROSPECTION_PATH, - overwrite: true, - schema: SCHEMA_PATH, - template: 'graphql-codegen-introspection-template', - }, - true - ); - await generate( - { - args: GRAPHQL_GLOBS, - config: CLIENT_CONFIG_PATH, - out: OUTPUT_CLIENT_TYPES_PATH, - overwrite: true, - schema: SCHEMA_PATH, - template: 'graphql-codegen-typescript-template', - }, - true - ); - await generate( - { - args: GRAPHQL_GLOBS, - config: CLIENT_CONFIG_PATH, - out: OUTPUT_COMMON_TYPES_PATH, - overwrite: true, - schema: SCHEMA_PATH, - template: 'graphql-codegen-typescript-template', - }, - true - ); - await generate( - { - args: [], - config: SERVER_CONFIG_PATH, - out: OUTPUT_SERVER_TYPES_PATH, - overwrite: true, - schema: SCHEMA_PATH, - template: 'graphql-codegen-typescript-resolvers-template', - }, - true - ); -} - -if (require.main === module) { - main(); -} diff --git a/x-pack/plugins/infra/server/graphql/index.ts b/x-pack/plugins/infra/server/graphql/index.ts deleted file mode 100644 index f5150972a3a65..0000000000000 --- a/x-pack/plugins/infra/server/graphql/index.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 { rootSchema } from '../../common/graphql/root/schema.gql'; -import { sharedSchema } from '../../common/graphql/shared/schema.gql'; -import { sourceStatusSchema } from './source_status/schema.gql'; -import { sourcesSchema } from './sources/schema.gql'; - -export const schemas = [rootSchema, sharedSchema, sourcesSchema, sourceStatusSchema]; diff --git a/x-pack/plugins/infra/server/graphql/source_status/resolvers.ts b/x-pack/plugins/infra/server/graphql/source_status/resolvers.ts deleted file mode 100644 index bd92dd0f7da8b..0000000000000 --- a/x-pack/plugins/infra/server/graphql/source_status/resolvers.ts +++ /dev/null @@ -1,90 +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 { InfraIndexType, InfraSourceStatusResolvers } from '../../graphql/types'; -import { InfraFieldsDomain } from '../../lib/domains/fields_domain'; -import { InfraSourceStatus } from '../../lib/source_status'; -import { ChildResolverOf, InfraResolverOf } from '../../utils/typed_resolvers'; -import { QuerySourceResolver } from '../sources/resolvers'; - -export type InfraSourceStatusMetricAliasExistsResolver = ChildResolverOf< - InfraResolverOf, - QuerySourceResolver ->; - -export type InfraSourceStatusMetricIndicesExistResolver = ChildResolverOf< - InfraResolverOf, - QuerySourceResolver ->; - -export type InfraSourceStatusMetricIndicesResolver = ChildResolverOf< - InfraResolverOf, - QuerySourceResolver ->; - -export type InfraSourceStatusLogAliasExistsResolver = ChildResolverOf< - InfraResolverOf, - QuerySourceResolver ->; - -export type InfraSourceStatusLogIndicesExistResolver = ChildResolverOf< - InfraResolverOf, - QuerySourceResolver ->; - -export type InfraSourceStatusLogIndicesResolver = ChildResolverOf< - InfraResolverOf, - QuerySourceResolver ->; - -export type InfraSourceStatusIndexFieldsResolver = ChildResolverOf< - InfraResolverOf, - QuerySourceResolver ->; - -export const createSourceStatusResolvers = (libs: { - sourceStatus: InfraSourceStatus; - fields: InfraFieldsDomain; -}): { - InfraSourceStatus: { - metricAliasExists: InfraSourceStatusMetricAliasExistsResolver; - metricIndicesExist: InfraSourceStatusMetricIndicesExistResolver; - metricIndices: InfraSourceStatusMetricIndicesResolver; - logAliasExists: InfraSourceStatusLogAliasExistsResolver; - logIndicesExist: InfraSourceStatusLogIndicesExistResolver; - logIndices: InfraSourceStatusLogIndicesResolver; - indexFields: InfraSourceStatusIndexFieldsResolver; - }; -} => ({ - InfraSourceStatus: { - async metricAliasExists(source, args, { req }) { - return await libs.sourceStatus.hasMetricAlias(req, source.id); - }, - async metricIndicesExist(source, args, { req }) { - return await libs.sourceStatus.hasMetricIndices(req, source.id); - }, - async metricIndices(source, args, { req }) { - return await libs.sourceStatus.getMetricIndexNames(req, source.id); - }, - async logAliasExists(source, args, { req }) { - return await libs.sourceStatus.hasLogAlias(req, source.id); - }, - async logIndicesExist(source, args, { req }) { - return (await libs.sourceStatus.getLogIndexStatus(req, source.id)) !== 'missing'; - }, - async logIndices(source, args, { req }) { - return await libs.sourceStatus.getLogIndexNames(req, source.id); - }, - async indexFields(source, args, { req }) { - const fields = await libs.fields.getFields( - req, - source.id, - args.indexType || InfraIndexType.ANY - ); - return fields; - }, - }, -}); diff --git a/x-pack/plugins/infra/server/graphql/source_status/schema.gql.ts b/x-pack/plugins/infra/server/graphql/source_status/schema.gql.ts deleted file mode 100644 index e0482382c6d6a..0000000000000 --- a/x-pack/plugins/infra/server/graphql/source_status/schema.gql.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 gql from 'graphql-tag'; - -export const sourceStatusSchema = gql` - "A descriptor of a field in an index" - type InfraIndexField { - "The name of the field" - name: String! - "The type of the field's values as recognized by Kibana" - type: String! - "Whether the field's values can be efficiently searched for" - searchable: Boolean! - "Whether the field's values can be aggregated" - aggregatable: Boolean! - "Whether the field should be displayed based on event.module and a ECS allowed list" - displayable: Boolean! - } - - extend type InfraSourceStatus { - "Whether the configured metric alias exists" - metricAliasExists: Boolean! - "Whether the configured log alias exists" - logAliasExists: Boolean! - "Whether the configured alias or wildcard pattern resolve to any metric indices" - metricIndicesExist: Boolean! - "Whether the configured alias or wildcard pattern resolve to any log indices" - logIndicesExist: Boolean! - "The list of indices in the metric alias" - metricIndices: [String!]! - "The list of indices in the log alias" - logIndices: [String!]! - "The list of fields defined in the index mappings" - indexFields(indexType: InfraIndexType = ANY): [InfraIndexField!]! - } -`; diff --git a/x-pack/plugins/infra/server/graphql/sources/resolvers.ts b/x-pack/plugins/infra/server/graphql/sources/resolvers.ts deleted file mode 100644 index 15c4a6677946d..0000000000000 --- a/x-pack/plugins/infra/server/graphql/sources/resolvers.ts +++ /dev/null @@ -1,202 +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 { UserInputError } from 'apollo-server-errors'; -import { failure } from 'io-ts/lib/PathReporter'; - -import { identity } from 'fp-ts/lib/function'; -import { pipe } from 'fp-ts/lib/pipeable'; -import { fold } from 'fp-ts/lib/Either'; -import { - InfraSourceLogColumn, - InfraSourceResolvers, - MutationResolvers, - QueryResolvers, - UpdateSourceLogColumnInput, -} from '../../graphql/types'; -import { InfraSourceStatus } from '../../lib/source_status'; -import { - InfraSources, - SavedSourceConfigurationFieldColumnRuntimeType, - SavedSourceConfigurationMessageColumnRuntimeType, - SavedSourceConfigurationTimestampColumnRuntimeType, - SavedSourceConfigurationColumnRuntimeType, -} from '../../lib/sources'; -import { - ChildResolverOf, - InfraResolverOf, - InfraResolverWithFields, - ResultOf, -} from '../../utils/typed_resolvers'; - -export type QuerySourceResolver = InfraResolverWithFields< - QueryResolvers.SourceResolver, - 'id' | 'version' | 'updatedAt' | 'configuration' ->; - -export type QueryAllSourcesResolver = InfraResolverWithFields< - QueryResolvers.AllSourcesResolver, - 'id' | 'version' | 'updatedAt' | 'configuration' ->; - -export type InfraSourceStatusResolver = ChildResolverOf< - InfraResolverOf>>, - QuerySourceResolver ->; - -export type MutationCreateSourceResolver = InfraResolverOf< - MutationResolvers.CreateSourceResolver<{ - source: ResultOf; - }> ->; - -export type MutationDeleteSourceResolver = InfraResolverOf; - -export type MutationUpdateSourceResolver = InfraResolverOf< - MutationResolvers.UpdateSourceResolver<{ - source: ResultOf; - }> ->; - -interface SourcesResolversDeps { - sources: InfraSources; - sourceStatus: InfraSourceStatus; -} - -export const createSourcesResolvers = ( - libs: SourcesResolversDeps -): { - Query: { - source: QuerySourceResolver; - allSources: QueryAllSourcesResolver; - }; - InfraSource: { - status: InfraSourceStatusResolver; - }; - InfraSourceLogColumn: { - __resolveType( - logColumn: InfraSourceLogColumn - ): - | 'InfraSourceTimestampLogColumn' - | 'InfraSourceMessageLogColumn' - | 'InfraSourceFieldLogColumn' - | null; - }; - Mutation: { - createSource: MutationCreateSourceResolver; - deleteSource: MutationDeleteSourceResolver; - updateSource: MutationUpdateSourceResolver; - }; -} => ({ - Query: { - async source(root, args, { req }) { - const requestedSourceConfiguration = await libs.sources.getSourceConfiguration( - req.core.savedObjects.client, - args.id - ); - - return requestedSourceConfiguration; - }, - async allSources(root, args, { req }) { - const sourceConfigurations = await libs.sources.getAllSourceConfigurations( - req.core.savedObjects.client - ); - - return sourceConfigurations; - }, - }, - InfraSource: { - async status(source) { - return source; - }, - }, - InfraSourceLogColumn: { - __resolveType(logColumn) { - if (SavedSourceConfigurationTimestampColumnRuntimeType.is(logColumn)) { - return 'InfraSourceTimestampLogColumn'; - } - - if (SavedSourceConfigurationMessageColumnRuntimeType.is(logColumn)) { - return 'InfraSourceMessageLogColumn'; - } - - if (SavedSourceConfigurationFieldColumnRuntimeType.is(logColumn)) { - return 'InfraSourceFieldLogColumn'; - } - - return null; - }, - }, - Mutation: { - async createSource(root, args, { req }) { - const sourceConfiguration = await libs.sources.createSourceConfiguration( - req.core.savedObjects.client, - args.id, - compactObject({ - ...args.sourceProperties, - fields: args.sourceProperties.fields - ? compactObject(args.sourceProperties.fields) - : undefined, - logColumns: decodeLogColumns(args.sourceProperties.logColumns), - }) - ); - - return { - source: sourceConfiguration, - }; - }, - async deleteSource(root, args, { req }) { - await libs.sources.deleteSourceConfiguration(req.core.savedObjects.client, args.id); - - return { - id: args.id, - }; - }, - async updateSource(root, args, { req }) { - const updatedSourceConfiguration = await libs.sources.updateSourceConfiguration( - req.core.savedObjects.client, - args.id, - compactObject({ - ...args.sourceProperties, - fields: args.sourceProperties.fields - ? compactObject(args.sourceProperties.fields) - : undefined, - logColumns: decodeLogColumns(args.sourceProperties.logColumns), - }) - ); - - return { - source: updatedSourceConfiguration, - }; - }, - }, -}); - -type CompactObject = { [K in keyof T]: NonNullable }; - -const compactObject = (obj: T): CompactObject => - Object.entries(obj).reduce>( - (accumulatedObj, [key, value]) => - typeof value === 'undefined' || value === null - ? accumulatedObj - : { - ...(accumulatedObj as any), - [key]: value, - }, - {} as CompactObject - ); - -const decodeLogColumns = (logColumns?: UpdateSourceLogColumnInput[] | null) => - logColumns - ? logColumns.map((logColumn) => - pipe( - SavedSourceConfigurationColumnRuntimeType.decode(logColumn), - fold((errors) => { - throw new UserInputError(failure(errors).join('\n')); - }, identity) - ) - ) - : undefined; diff --git a/x-pack/plugins/infra/server/graphql/sources/schema.gql.ts b/x-pack/plugins/infra/server/graphql/sources/schema.gql.ts deleted file mode 100644 index dbd0696fe3e03..0000000000000 --- a/x-pack/plugins/infra/server/graphql/sources/schema.gql.ts +++ /dev/null @@ -1,209 +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 sourcesSchema = gql` - "A source of infrastructure data" - type InfraSource { - "The id of the source" - id: ID! - "The version number the source configuration was last persisted with" - version: String - "The timestamp the source configuration was last persisted at" - updatedAt: Float - "The origin of the source (one of 'fallback', 'internal', 'stored')" - origin: String! - "The raw configuration of the source" - configuration: InfraSourceConfiguration! - "The status of the source" - status: InfraSourceStatus! - } - - "The status of an infrastructure data source" - type InfraSourceStatus - - "A set of configuration options for an infrastructure data source" - type InfraSourceConfiguration { - "The name of the data source" - name: String! - "A description of the data source" - description: String! - "The alias to read metric data from" - metricAlias: String! - "The alias to read log data from" - logAlias: String! - "Default view for inventory" - inventoryDefaultView: String! - "Default view for Metrics Explorer" - metricsExplorerDefaultView: String! - "The field mapping to use for this source" - fields: InfraSourceFields! - "The columns to use for log display" - logColumns: [InfraSourceLogColumn!]! - } - - "A mapping of semantic fields to their document counterparts" - type InfraSourceFields { - "The field to identify a container by" - container: String! - "The fields to identify a host by" - host: String! - "The fields to use as the log message" - message: [String!]! - "The field to identify a pod by" - pod: String! - "The field to use as a tiebreaker for log events that have identical timestamps" - tiebreaker: String! - "The field to use as a timestamp for metrics and logs" - timestamp: String! - } - - "The built-in timestamp log column" - type InfraSourceTimestampLogColumn { - timestampColumn: InfraSourceTimestampLogColumnAttributes! - } - - type InfraSourceTimestampLogColumnAttributes { - "A unique id for the column" - id: ID! - } - - "The built-in message log column" - type InfraSourceMessageLogColumn { - messageColumn: InfraSourceMessageLogColumnAttributes! - } - - type InfraSourceMessageLogColumnAttributes { - "A unique id for the column" - id: ID! - } - - "A log column containing a field value" - type InfraSourceFieldLogColumn { - fieldColumn: InfraSourceFieldLogColumnAttributes! - } - - type InfraSourceFieldLogColumnAttributes { - "A unique id for the column" - id: ID! - "The field name this column refers to" - field: String! - } - - "All known log column types" - union InfraSourceLogColumn = - InfraSourceTimestampLogColumn - | InfraSourceMessageLogColumn - | InfraSourceFieldLogColumn - - extend type Query { - """ - Get an infrastructure data source by id. - - The resolution order for the source configuration attributes is as follows - with the first defined value winning: - - 1. The attributes of the saved object with the given 'id'. - 2. The attributes defined in the static Kibana configuration key - 'xpack.infra.sources.default'. - 3. The hard-coded default values. - - As a consequence, querying a source that doesn't exist doesn't error out, - but returns the configured or hardcoded defaults. - """ - source("The id of the source" id: ID!): InfraSource! - "Get a list of all infrastructure data sources" - allSources: [InfraSource!]! - } - - "The properties to update the source with" - input UpdateSourceInput { - "The name of the data source" - name: String - "A description of the data source" - description: String - "The alias to read metric data from" - metricAlias: String - "The alias to read log data from" - logAlias: String - "The field mapping to use for this source" - fields: UpdateSourceFieldsInput - "Name of default inventory view" - inventoryDefaultView: String - "Default view for Metrics Explorer" - metricsExplorerDefaultView: String - "The log columns to display for this source" - logColumns: [UpdateSourceLogColumnInput!] - } - - "The mapping of semantic fields of the source to be created" - input UpdateSourceFieldsInput { - "The field to identify a container by" - container: String - "The fields to identify a host by" - host: String - "The field to identify a pod by" - pod: String - "The field to use as a tiebreaker for log events that have identical timestamps" - tiebreaker: String - "The field to use as a timestamp for metrics and logs" - timestamp: String - } - - "One of the log column types to display for this source" - input UpdateSourceLogColumnInput { - "A custom field log column" - fieldColumn: UpdateSourceFieldLogColumnInput - "A built-in message log column" - messageColumn: UpdateSourceMessageLogColumnInput - "A built-in timestamp log column" - timestampColumn: UpdateSourceTimestampLogColumnInput - } - - input UpdateSourceFieldLogColumnInput { - id: ID! - field: String! - } - - input UpdateSourceMessageLogColumnInput { - id: ID! - } - - input UpdateSourceTimestampLogColumnInput { - id: ID! - } - - "The result of a successful source update" - type UpdateSourceResult { - "The source that was updated" - source: InfraSource! - } - - "The result of a source deletion operations" - type DeleteSourceResult { - "The id of the source that was deleted" - id: ID! - } - - extend type Mutation { - "Create a new source of infrastructure data" - createSource( - "The id of the source" - id: ID! - sourceProperties: UpdateSourceInput! - ): UpdateSourceResult! - "Modify an existing source" - updateSource( - "The id of the source" - id: ID! - "The properties to update the source with" - sourceProperties: UpdateSourceInput! - ): UpdateSourceResult! - "Delete a source of infrastructure data" - deleteSource("The id of the source" id: ID!): DeleteSourceResult! - } -`; diff --git a/x-pack/plugins/infra/server/graphql/types.ts b/x-pack/plugins/infra/server/graphql/types.ts deleted file mode 100644 index 02dcd76e8b34c..0000000000000 --- a/x-pack/plugins/infra/server/graphql/types.ts +++ /dev/null @@ -1,1517 +0,0 @@ -/* tslint:disable */ -import { InfraContext } from '../lib/infra_types'; -import { GraphQLResolveInfo } from 'graphql'; - -export type Resolver = ( - parent: Parent, - args: Args, - context: Context, - info: GraphQLResolveInfo -) => Promise | Result; - -export interface ISubscriptionResolverObject { - subscribe( - parent: P, - args: Args, - context: Context, - info: GraphQLResolveInfo - ): AsyncIterator; - resolve?( - parent: P, - args: Args, - context: Context, - info: GraphQLResolveInfo - ): R | Result | Promise; -} - -export type SubscriptionResolver = - | ((...args: any[]) => ISubscriptionResolverObject) - | ISubscriptionResolverObject; - -// ==================================================== -// START: Typescript template -// ==================================================== - -// ==================================================== -// Types -// ==================================================== - -export interface Query { - /** Get an infrastructure data source by id.The resolution order for the source configuration attributes is as followswith the first defined value winning:1. The attributes of the saved object with the given 'id'.2. The attributes defined in the static Kibana configuration key'xpack.infra.sources.default'.3. The hard-coded default values.As a consequence, querying a source that doesn't exist doesn't error out,but returns the configured or hardcoded defaults. */ - source: InfraSource; - /** Get a list of all infrastructure data sources */ - allSources: InfraSource[]; -} -/** A source of infrastructure data */ -export interface InfraSource { - /** The id of the source */ - id: string; - /** The version number the source configuration was last persisted with */ - version?: string | null; - /** The timestamp the source configuration was last persisted at */ - updatedAt?: number | null; - /** The origin of the source (one of 'fallback', 'internal', 'stored') */ - origin: string; - /** The raw configuration of the source */ - configuration: InfraSourceConfiguration; - /** The status of the source */ - status: InfraSourceStatus; - /** A consecutive span of log entries surrounding a point in time */ - logEntriesAround: InfraLogEntryInterval; - /** A consecutive span of log entries within an interval */ - logEntriesBetween: InfraLogEntryInterval; - /** Sequences of log entries matching sets of highlighting queries within an interval */ - logEntryHighlights: InfraLogEntryInterval[]; - - /** A snapshot of nodes */ - snapshot?: InfraSnapshotResponse | null; - - metrics: InfraMetricData[]; -} -/** A set of configuration options for an infrastructure data source */ -export interface InfraSourceConfiguration { - /** The name of the data source */ - name: string; - /** A description of the data source */ - description: string; - /** The alias to read metric data from */ - metricAlias: string; - /** The alias to read log data from */ - logAlias: string; - /** The field mapping to use for this source */ - fields: InfraSourceFields; - /** The columns to use for log display */ - logColumns: InfraSourceLogColumn[]; -} -/** A mapping of semantic fields to their document counterparts */ -export interface InfraSourceFields { - /** The field to identify a container by */ - container: string; - /** The fields to identify a host by */ - host: string; - /** The fields to use as the log message */ - message: string[]; - /** The field to identify a pod by */ - pod: string; - /** The field to use as a tiebreaker for log events that have identical timestamps */ - tiebreaker: string; - /** The field to use as a timestamp for metrics and logs */ - timestamp: string; -} -/** The built-in timestamp log column */ -export interface InfraSourceTimestampLogColumn { - timestampColumn: InfraSourceTimestampLogColumnAttributes; -} - -export interface InfraSourceTimestampLogColumnAttributes { - /** A unique id for the column */ - id: string; -} -/** The built-in message log column */ -export interface InfraSourceMessageLogColumn { - messageColumn: InfraSourceMessageLogColumnAttributes; -} - -export interface InfraSourceMessageLogColumnAttributes { - /** A unique id for the column */ - id: string; -} -/** A log column containing a field value */ -export interface InfraSourceFieldLogColumn { - fieldColumn: InfraSourceFieldLogColumnAttributes; -} - -export interface InfraSourceFieldLogColumnAttributes { - /** A unique id for the column */ - id: string; - /** The field name this column refers to */ - field: string; -} -/** The status of an infrastructure data source */ -export interface InfraSourceStatus { - /** Whether the configured metric alias exists */ - metricAliasExists: boolean; - /** Whether the configured log alias exists */ - logAliasExists: boolean; - /** Whether the configured alias or wildcard pattern resolve to any metric indices */ - metricIndicesExist: boolean; - /** Whether the configured alias or wildcard pattern resolve to any log indices */ - logIndicesExist: boolean; - /** The list of indices in the metric alias */ - metricIndices: string[]; - /** The list of indices in the log alias */ - logIndices: string[]; - /** The list of fields defined in the index mappings */ - indexFields: InfraIndexField[]; -} -/** A descriptor of a field in an index */ -export interface InfraIndexField { - /** The name of the field */ - name: string; - /** The type of the field's values as recognized by Kibana */ - type: string; - /** Whether the field's values can be efficiently searched for */ - searchable: boolean; - /** Whether the field's values can be aggregated */ - aggregatable: boolean; - /** Whether the field should be displayed based on event.module and a ECS allowed list */ - displayable: boolean; -} -/** A consecutive sequence of log entries */ -export interface InfraLogEntryInterval { - /** The key corresponding to the start of the interval covered by the entries */ - start?: InfraTimeKey | null; - /** The key corresponding to the end of the interval covered by the entries */ - end?: InfraTimeKey | null; - /** Whether there are more log entries available before the start */ - hasMoreBefore: boolean; - /** Whether there are more log entries available after the end */ - hasMoreAfter: boolean; - /** The query the log entries were filtered by */ - filterQuery?: string | null; - /** The query the log entries were highlighted with */ - highlightQuery?: string | null; - /** A list of the log entries */ - entries: InfraLogEntry[]; -} -/** A representation of the log entry's position in the event stream */ -export interface InfraTimeKey { - /** The timestamp of the event that the log entry corresponds to */ - time: number; - /** The tiebreaker that disambiguates events with the same timestamp */ - tiebreaker: number; -} -/** A log entry */ -export interface InfraLogEntry { - /** A unique representation of the log entry's position in the event stream */ - key: InfraTimeKey; - /** The log entry's id */ - gid: string; - /** The source id */ - source: string; - /** The columns used for rendering the log entry */ - columns: InfraLogEntryColumn[]; -} -/** A special built-in column that contains the log entry's timestamp */ -export interface InfraLogEntryTimestampColumn { - /** The id of the corresponding column configuration */ - columnId: string; - /** The timestamp */ - timestamp: number; -} -/** A special built-in column that contains the log entry's constructed message */ -export interface InfraLogEntryMessageColumn { - /** The id of the corresponding column configuration */ - columnId: string; - /** A list of the formatted log entry segments */ - message: InfraLogMessageSegment[]; -} -/** A segment of the log entry message that was derived from a field */ -export interface InfraLogMessageFieldSegment { - /** The field the segment was derived from */ - field: string; - /** The segment's message */ - value: string; - /** A list of highlighted substrings of the value */ - highlights: string[]; -} -/** A segment of the log entry message that was derived from a string literal */ -export interface InfraLogMessageConstantSegment { - /** The segment's message */ - constant: string; -} -/** A column that contains the value of a field of the log entry */ -export interface InfraLogEntryFieldColumn { - /** The id of the corresponding column configuration */ - columnId: string; - /** The field name of the column */ - field: string; - /** The value of the field in the log entry */ - value: string; - /** A list of highlighted substrings of the value */ - highlights: string[]; -} - -export interface InfraSnapshotResponse { - /** Nodes of type host, container or pod grouped by 0, 1 or 2 terms */ - nodes: InfraSnapshotNode[]; -} - -export interface InfraSnapshotNode { - path: InfraSnapshotNodePath[]; - - metric: InfraSnapshotNodeMetric; -} - -export interface InfraSnapshotNodePath { - value: string; - - label: string; - - ip?: string | null; -} - -export interface InfraSnapshotNodeMetric { - name: InfraSnapshotMetricType; - - value?: number | null; - - avg?: number | null; - - max?: number | null; -} - -export interface InfraMetricData { - id?: InfraMetric | null; - - series: InfraDataSeries[]; -} - -export interface InfraDataSeries { - id: string; - - label: string; - - data: InfraDataPoint[]; -} - -export interface InfraDataPoint { - timestamp: number; - - value?: number | null; -} - -export interface Mutation { - /** Create a new source of infrastructure data */ - createSource: UpdateSourceResult; - /** Modify an existing source */ - updateSource: UpdateSourceResult; - /** Delete a source of infrastructure data */ - deleteSource: DeleteSourceResult; -} -/** The result of a successful source update */ -export interface UpdateSourceResult { - /** The source that was updated */ - source: InfraSource; -} -/** The result of a source deletion operations */ -export interface DeleteSourceResult { - /** The id of the source that was deleted */ - id: string; -} - -// ==================================================== -// InputTypes -// ==================================================== - -export interface InfraTimeKeyInput { - time: number; - - tiebreaker: number; -} -/** A highlighting definition */ -export interface InfraLogEntryHighlightInput { - /** The query to highlight by */ - query: string; - /** The number of highlighted documents to include beyond the beginning of the interval */ - countBefore: number; - /** The number of highlighted documents to include beyond the end of the interval */ - countAfter: number; -} - -export interface InfraTimerangeInput { - /** The interval string to use for last bucket. The format is '{value}{unit}'. For example '5m' would return the metrics for the last 5 minutes of the timespan. */ - interval: string; - /** The end of the timerange */ - to: number; - /** The beginning of the timerange */ - from: number; -} - -export interface InfraSnapshotGroupbyInput { - /** The label to use in the results for the group by for the terms group by */ - label?: string | null; - /** The field to group by from a terms aggregation, this is ignored by the filter type */ - field?: string | null; -} - -export interface InfraSnapshotMetricInput { - /** The type of metric */ - type: InfraSnapshotMetricType; -} - -export interface InfraNodeIdsInput { - nodeId: string; - - cloudId?: string | null; -} -/** The properties to update the source with */ -export interface UpdateSourceInput { - /** The name of the data source */ - name?: string | null; - /** A description of the data source */ - description?: string | null; - /** The alias to read metric data from */ - metricAlias?: string | null; - /** The alias to read log data from */ - logAlias?: string | null; - /** The field mapping to use for this source */ - fields?: UpdateSourceFieldsInput | null; - /** Name of default inventory view */ - inventoryDefaultView?: string | null; - /** Default view for Metrics Explorer */ - metricsExplorerDefaultView?: string | null; - /** The log columns to display for this source */ - logColumns?: UpdateSourceLogColumnInput[] | null; -} -/** The mapping of semantic fields of the source to be created */ -export interface UpdateSourceFieldsInput { - /** The field to identify a container by */ - container?: string | null; - /** The fields to identify a host by */ - host?: string | null; - /** The field to identify a pod by */ - pod?: string | null; - /** The field to use as a tiebreaker for log events that have identical timestamps */ - tiebreaker?: string | null; - /** The field to use as a timestamp for metrics and logs */ - timestamp?: string | null; -} -/** One of the log column types to display for this source */ -export interface UpdateSourceLogColumnInput { - /** A custom field log column */ - fieldColumn?: UpdateSourceFieldLogColumnInput | null; - /** A built-in message log column */ - messageColumn?: UpdateSourceMessageLogColumnInput | null; - /** A built-in timestamp log column */ - timestampColumn?: UpdateSourceTimestampLogColumnInput | null; -} - -export interface UpdateSourceFieldLogColumnInput { - id: string; - - field: string; -} - -export interface UpdateSourceMessageLogColumnInput { - id: string; -} - -export interface UpdateSourceTimestampLogColumnInput { - id: string; -} - -// ==================================================== -// Arguments -// ==================================================== - -export interface SourceQueryArgs { - /** The id of the source */ - id: string; -} -export interface LogEntriesAroundInfraSourceArgs { - /** The sort key that corresponds to the point in time */ - key: InfraTimeKeyInput; - /** The maximum number of preceding to return */ - countBefore?: number | null; - /** The maximum number of following to return */ - countAfter?: number | null; - /** The query to filter the log entries by */ - filterQuery?: string | null; -} -export interface LogEntriesBetweenInfraSourceArgs { - /** The sort key that corresponds to the start of the interval */ - startKey: InfraTimeKeyInput; - /** The sort key that corresponds to the end of the interval */ - endKey: InfraTimeKeyInput; - /** The query to filter the log entries by */ - filterQuery?: string | null; -} -export interface LogEntryHighlightsInfraSourceArgs { - /** The sort key that corresponds to the start of the interval */ - startKey: InfraTimeKeyInput; - /** The sort key that corresponds to the end of the interval */ - endKey: InfraTimeKeyInput; - /** The query to filter the log entries by */ - filterQuery?: string | null; - /** The highlighting to apply to the log entries */ - highlights: InfraLogEntryHighlightInput[]; -} -export interface SnapshotInfraSourceArgs { - timerange: InfraTimerangeInput; - - filterQuery?: string | null; -} -export interface MetricsInfraSourceArgs { - nodeIds: InfraNodeIdsInput; - - nodeType: InfraNodeType; - - timerange: InfraTimerangeInput; - - metrics: InfraMetric[]; -} -export interface IndexFieldsInfraSourceStatusArgs { - indexType?: InfraIndexType | null; -} -export interface NodesInfraSnapshotResponseArgs { - type: InfraNodeType; - - groupBy: InfraSnapshotGroupbyInput[]; - - metric: InfraSnapshotMetricInput; -} -export interface CreateSourceMutationArgs { - /** The id of the source */ - id: string; - - sourceProperties: UpdateSourceInput; -} -export interface UpdateSourceMutationArgs { - /** The id of the source */ - id: string; - /** The properties to update the source with */ - sourceProperties: UpdateSourceInput; -} -export interface DeleteSourceMutationArgs { - /** The id of the source */ - id: string; -} - -// ==================================================== -// Enums -// ==================================================== - -export enum InfraIndexType { - ANY = 'ANY', - LOGS = 'LOGS', - METRICS = 'METRICS', -} - -export enum InfraNodeType { - pod = 'pod', - container = 'container', - host = 'host', - awsEC2 = 'awsEC2', - awsS3 = 'awsS3', - awsRDS = 'awsRDS', - awsSQS = 'awsSQS', -} - -export enum InfraSnapshotMetricType { - count = 'count', - cpu = 'cpu', - load = 'load', - memory = 'memory', - tx = 'tx', - rx = 'rx', - logRate = 'logRate', - diskIOReadBytes = 'diskIOReadBytes', - diskIOWriteBytes = 'diskIOWriteBytes', - s3TotalRequests = 's3TotalRequests', - s3NumberOfObjects = 's3NumberOfObjects', - s3BucketSize = 's3BucketSize', - s3DownloadBytes = 's3DownloadBytes', - s3UploadBytes = 's3UploadBytes', - rdsConnections = 'rdsConnections', - rdsQueriesExecuted = 'rdsQueriesExecuted', - rdsActiveTransactions = 'rdsActiveTransactions', - rdsLatency = 'rdsLatency', - sqsMessagesVisible = 'sqsMessagesVisible', - sqsMessagesDelayed = 'sqsMessagesDelayed', - sqsMessagesSent = 'sqsMessagesSent', - sqsMessagesEmpty = 'sqsMessagesEmpty', - sqsOldestMessage = 'sqsOldestMessage', -} - -export enum InfraMetric { - hostSystemOverview = 'hostSystemOverview', - hostCpuUsage = 'hostCpuUsage', - hostFilesystem = 'hostFilesystem', - hostK8sOverview = 'hostK8sOverview', - hostK8sCpuCap = 'hostK8sCpuCap', - hostK8sDiskCap = 'hostK8sDiskCap', - hostK8sMemoryCap = 'hostK8sMemoryCap', - hostK8sPodCap = 'hostK8sPodCap', - hostLoad = 'hostLoad', - hostMemoryUsage = 'hostMemoryUsage', - hostNetworkTraffic = 'hostNetworkTraffic', - hostDockerOverview = 'hostDockerOverview', - hostDockerInfo = 'hostDockerInfo', - hostDockerTop5ByCpu = 'hostDockerTop5ByCpu', - hostDockerTop5ByMemory = 'hostDockerTop5ByMemory', - podOverview = 'podOverview', - podCpuUsage = 'podCpuUsage', - podMemoryUsage = 'podMemoryUsage', - podLogUsage = 'podLogUsage', - podNetworkTraffic = 'podNetworkTraffic', - containerOverview = 'containerOverview', - containerCpuKernel = 'containerCpuKernel', - containerCpuUsage = 'containerCpuUsage', - containerDiskIOOps = 'containerDiskIOOps', - containerDiskIOBytes = 'containerDiskIOBytes', - containerMemory = 'containerMemory', - containerNetworkTraffic = 'containerNetworkTraffic', - nginxHits = 'nginxHits', - nginxRequestRate = 'nginxRequestRate', - nginxActiveConnections = 'nginxActiveConnections', - nginxRequestsPerConnection = 'nginxRequestsPerConnection', - awsOverview = 'awsOverview', - awsCpuUtilization = 'awsCpuUtilization', - awsNetworkBytes = 'awsNetworkBytes', - awsNetworkPackets = 'awsNetworkPackets', - awsDiskioBytes = 'awsDiskioBytes', - awsDiskioOps = 'awsDiskioOps', - awsEC2CpuUtilization = 'awsEC2CpuUtilization', - awsEC2DiskIOBytes = 'awsEC2DiskIOBytes', - awsEC2NetworkTraffic = 'awsEC2NetworkTraffic', - awsS3TotalRequests = 'awsS3TotalRequests', - awsS3NumberOfObjects = 'awsS3NumberOfObjects', - awsS3BucketSize = 'awsS3BucketSize', - awsS3DownloadBytes = 'awsS3DownloadBytes', - awsS3UploadBytes = 'awsS3UploadBytes', - awsRDSCpuTotal = 'awsRDSCpuTotal', - awsRDSConnections = 'awsRDSConnections', - awsRDSQueriesExecuted = 'awsRDSQueriesExecuted', - awsRDSActiveTransactions = 'awsRDSActiveTransactions', - awsRDSLatency = 'awsRDSLatency', - awsSQSMessagesVisible = 'awsSQSMessagesVisible', - awsSQSMessagesDelayed = 'awsSQSMessagesDelayed', - awsSQSMessagesSent = 'awsSQSMessagesSent', - awsSQSMessagesEmpty = 'awsSQSMessagesEmpty', - awsSQSOldestMessage = 'awsSQSOldestMessage', - custom = 'custom', -} - -// ==================================================== -// Unions -// ==================================================== - -/** All known log column types */ -export type InfraSourceLogColumn = - | InfraSourceTimestampLogColumn - | InfraSourceMessageLogColumn - | InfraSourceFieldLogColumn; - -/** A column of a log entry */ -export type InfraLogEntryColumn = - | InfraLogEntryTimestampColumn - | InfraLogEntryMessageColumn - | InfraLogEntryFieldColumn; - -/** A segment of the log entry message */ -export type InfraLogMessageSegment = InfraLogMessageFieldSegment | InfraLogMessageConstantSegment; - -// ==================================================== -// END: Typescript template -// ==================================================== - -// ==================================================== -// Resolvers -// ==================================================== - -export namespace QueryResolvers { - export interface Resolvers { - /** Get an infrastructure data source by id.The resolution order for the source configuration attributes is as followswith the first defined value winning:1. The attributes of the saved object with the given 'id'.2. The attributes defined in the static Kibana configuration key'xpack.infra.sources.default'.3. The hard-coded default values.As a consequence, querying a source that doesn't exist doesn't error out,but returns the configured or hardcoded defaults. */ - source?: SourceResolver; - /** Get a list of all infrastructure data sources */ - allSources?: AllSourcesResolver; - } - - export type SourceResolver = Resolver< - R, - Parent, - Context, - SourceArgs - >; - export interface SourceArgs { - /** The id of the source */ - id: string; - } - - export type AllSourcesResolver< - R = InfraSource[], - Parent = never, - Context = InfraContext - > = Resolver; -} -/** A source of infrastructure data */ -export namespace InfraSourceResolvers { - export interface Resolvers { - /** The id of the source */ - id?: IdResolver; - /** The version number the source configuration was last persisted with */ - version?: VersionResolver; - /** The timestamp the source configuration was last persisted at */ - updatedAt?: UpdatedAtResolver; - /** The origin of the source (one of 'fallback', 'internal', 'stored') */ - origin?: OriginResolver; - /** The raw configuration of the source */ - configuration?: ConfigurationResolver; - /** The status of the source */ - status?: StatusResolver; - /** A consecutive span of log entries surrounding a point in time */ - logEntriesAround?: LogEntriesAroundResolver; - /** A consecutive span of log entries within an interval */ - logEntriesBetween?: LogEntriesBetweenResolver; - /** Sequences of log entries matching sets of highlighting queries within an interval */ - logEntryHighlights?: LogEntryHighlightsResolver; - - /** A snapshot of nodes */ - snapshot?: SnapshotResolver; - - metrics?: MetricsResolver; - } - - export type IdResolver = Resolver< - R, - Parent, - Context - >; - export type VersionResolver< - R = string | null, - Parent = InfraSource, - Context = InfraContext - > = Resolver; - export type UpdatedAtResolver< - R = number | null, - Parent = InfraSource, - Context = InfraContext - > = Resolver; - export type OriginResolver = Resolver< - R, - Parent, - Context - >; - export type ConfigurationResolver< - R = InfraSourceConfiguration, - Parent = InfraSource, - Context = InfraContext - > = Resolver; - export type StatusResolver< - R = InfraSourceStatus, - Parent = InfraSource, - Context = InfraContext - > = Resolver; - export type LogEntriesAroundResolver< - R = InfraLogEntryInterval, - Parent = InfraSource, - Context = InfraContext - > = Resolver; - export interface LogEntriesAroundArgs { - /** The sort key that corresponds to the point in time */ - key: InfraTimeKeyInput; - /** The maximum number of preceding to return */ - countBefore?: number | null; - /** The maximum number of following to return */ - countAfter?: number | null; - /** The query to filter the log entries by */ - filterQuery?: string | null; - } - - export type LogEntriesBetweenResolver< - R = InfraLogEntryInterval, - Parent = InfraSource, - Context = InfraContext - > = Resolver; - export interface LogEntriesBetweenArgs { - /** The sort key that corresponds to the start of the interval */ - startKey: InfraTimeKeyInput; - /** The sort key that corresponds to the end of the interval */ - endKey: InfraTimeKeyInput; - /** The query to filter the log entries by */ - filterQuery?: string | null; - } - - export type LogEntryHighlightsResolver< - R = InfraLogEntryInterval[], - Parent = InfraSource, - Context = InfraContext - > = Resolver; - export interface LogEntryHighlightsArgs { - /** The sort key that corresponds to the start of the interval */ - startKey: InfraTimeKeyInput; - /** The sort key that corresponds to the end of the interval */ - endKey: InfraTimeKeyInput; - /** The query to filter the log entries by */ - filterQuery?: string | null; - /** The highlighting to apply to the log entries */ - highlights: InfraLogEntryHighlightInput[]; - } - - export type SnapshotResolver< - R = InfraSnapshotResponse | null, - Parent = InfraSource, - Context = InfraContext - > = Resolver; - export interface SnapshotArgs { - timerange: InfraTimerangeInput; - - filterQuery?: string | null; - } - - export type MetricsResolver< - R = InfraMetricData[], - Parent = InfraSource, - Context = InfraContext - > = Resolver; - export interface MetricsArgs { - nodeIds: InfraNodeIdsInput; - - nodeType: InfraNodeType; - - timerange: InfraTimerangeInput; - - metrics: InfraMetric[]; - } -} -/** A set of configuration options for an infrastructure data source */ -export namespace InfraSourceConfigurationResolvers { - export interface Resolvers { - /** The name of the data source */ - name?: NameResolver; - /** A description of the data source */ - description?: DescriptionResolver; - /** The alias to read metric data from */ - metricAlias?: MetricAliasResolver; - /** The alias to read log data from */ - logAlias?: LogAliasResolver; - /** The field mapping to use for this source */ - fields?: FieldsResolver; - /** The columns to use for log display */ - logColumns?: LogColumnsResolver; - } - - export type NameResolver< - R = string, - Parent = InfraSourceConfiguration, - Context = InfraContext - > = Resolver; - export type DescriptionResolver< - R = string, - Parent = InfraSourceConfiguration, - Context = InfraContext - > = Resolver; - export type MetricAliasResolver< - R = string, - Parent = InfraSourceConfiguration, - Context = InfraContext - > = Resolver; - export type LogAliasResolver< - R = string, - Parent = InfraSourceConfiguration, - Context = InfraContext - > = Resolver; - export type FieldsResolver< - R = InfraSourceFields, - Parent = InfraSourceConfiguration, - Context = InfraContext - > = Resolver; - export type LogColumnsResolver< - R = InfraSourceLogColumn[], - Parent = InfraSourceConfiguration, - Context = InfraContext - > = Resolver; -} -/** A mapping of semantic fields to their document counterparts */ -export namespace InfraSourceFieldsResolvers { - export interface Resolvers { - /** The field to identify a container by */ - container?: ContainerResolver; - /** The fields to identify a host by */ - host?: HostResolver; - /** The fields to use as the log message */ - message?: MessageResolver; - /** The field to identify a pod by */ - pod?: PodResolver; - /** The field to use as a tiebreaker for log events that have identical timestamps */ - tiebreaker?: TiebreakerResolver; - /** The field to use as a timestamp for metrics and logs */ - timestamp?: TimestampResolver; - } - - export type ContainerResolver< - R = string, - Parent = InfraSourceFields, - Context = InfraContext - > = Resolver; - export type HostResolver< - R = string, - Parent = InfraSourceFields, - Context = InfraContext - > = Resolver; - export type MessageResolver< - R = string[], - Parent = InfraSourceFields, - Context = InfraContext - > = Resolver; - export type PodResolver< - R = string, - Parent = InfraSourceFields, - Context = InfraContext - > = Resolver; - export type TiebreakerResolver< - R = string, - Parent = InfraSourceFields, - Context = InfraContext - > = Resolver; - export type TimestampResolver< - R = string, - Parent = InfraSourceFields, - Context = InfraContext - > = Resolver; -} -/** The built-in timestamp log column */ -export namespace InfraSourceTimestampLogColumnResolvers { - export interface Resolvers { - timestampColumn?: TimestampColumnResolver< - InfraSourceTimestampLogColumnAttributes, - TypeParent, - Context - >; - } - - export type TimestampColumnResolver< - R = InfraSourceTimestampLogColumnAttributes, - Parent = InfraSourceTimestampLogColumn, - Context = InfraContext - > = Resolver; -} - -export namespace InfraSourceTimestampLogColumnAttributesResolvers { - export interface Resolvers< - Context = InfraContext, - TypeParent = InfraSourceTimestampLogColumnAttributes - > { - /** A unique id for the column */ - id?: IdResolver; - } - - export type IdResolver< - R = string, - Parent = InfraSourceTimestampLogColumnAttributes, - Context = InfraContext - > = Resolver; -} -/** The built-in message log column */ -export namespace InfraSourceMessageLogColumnResolvers { - export interface Resolvers { - messageColumn?: MessageColumnResolver< - InfraSourceMessageLogColumnAttributes, - TypeParent, - Context - >; - } - - export type MessageColumnResolver< - R = InfraSourceMessageLogColumnAttributes, - Parent = InfraSourceMessageLogColumn, - Context = InfraContext - > = Resolver; -} - -export namespace InfraSourceMessageLogColumnAttributesResolvers { - export interface Resolvers< - Context = InfraContext, - TypeParent = InfraSourceMessageLogColumnAttributes - > { - /** A unique id for the column */ - id?: IdResolver; - } - - export type IdResolver< - R = string, - Parent = InfraSourceMessageLogColumnAttributes, - Context = InfraContext - > = Resolver; -} -/** A log column containing a field value */ -export namespace InfraSourceFieldLogColumnResolvers { - export interface Resolvers { - fieldColumn?: FieldColumnResolver; - } - - export type FieldColumnResolver< - R = InfraSourceFieldLogColumnAttributes, - Parent = InfraSourceFieldLogColumn, - Context = InfraContext - > = Resolver; -} - -export namespace InfraSourceFieldLogColumnAttributesResolvers { - export interface Resolvers< - Context = InfraContext, - TypeParent = InfraSourceFieldLogColumnAttributes - > { - /** A unique id for the column */ - id?: IdResolver; - /** The field name this column refers to */ - field?: FieldResolver; - } - - export type IdResolver< - R = string, - Parent = InfraSourceFieldLogColumnAttributes, - Context = InfraContext - > = Resolver; - export type FieldResolver< - R = string, - Parent = InfraSourceFieldLogColumnAttributes, - Context = InfraContext - > = Resolver; -} -/** The status of an infrastructure data source */ -export namespace InfraSourceStatusResolvers { - export interface Resolvers { - /** Whether the configured metric alias exists */ - metricAliasExists?: MetricAliasExistsResolver; - /** Whether the configured log alias exists */ - logAliasExists?: LogAliasExistsResolver; - /** Whether the configured alias or wildcard pattern resolve to any metric indices */ - metricIndicesExist?: MetricIndicesExistResolver; - /** Whether the configured alias or wildcard pattern resolve to any log indices */ - logIndicesExist?: LogIndicesExistResolver; - /** The list of indices in the metric alias */ - metricIndices?: MetricIndicesResolver; - /** The list of indices in the log alias */ - logIndices?: LogIndicesResolver; - /** The list of fields defined in the index mappings */ - indexFields?: IndexFieldsResolver; - } - - export type MetricAliasExistsResolver< - R = boolean, - Parent = InfraSourceStatus, - Context = InfraContext - > = Resolver; - export type LogAliasExistsResolver< - R = boolean, - Parent = InfraSourceStatus, - Context = InfraContext - > = Resolver; - export type MetricIndicesExistResolver< - R = boolean, - Parent = InfraSourceStatus, - Context = InfraContext - > = Resolver; - export type LogIndicesExistResolver< - R = boolean, - Parent = InfraSourceStatus, - Context = InfraContext - > = Resolver; - export type MetricIndicesResolver< - R = string[], - Parent = InfraSourceStatus, - Context = InfraContext - > = Resolver; - export type LogIndicesResolver< - R = string[], - Parent = InfraSourceStatus, - Context = InfraContext - > = Resolver; - export type IndexFieldsResolver< - R = InfraIndexField[], - Parent = InfraSourceStatus, - Context = InfraContext - > = Resolver; - export interface IndexFieldsArgs { - indexType?: InfraIndexType | null; - } -} -/** A descriptor of a field in an index */ -export namespace InfraIndexFieldResolvers { - export interface Resolvers { - /** The name of the field */ - name?: NameResolver; - /** The type of the field's values as recognized by Kibana */ - type?: TypeResolver; - /** Whether the field's values can be efficiently searched for */ - searchable?: SearchableResolver; - /** Whether the field's values can be aggregated */ - aggregatable?: AggregatableResolver; - /** Whether the field should be displayed based on event.module and a ECS allowed list */ - displayable?: DisplayableResolver; - } - - export type NameResolver = Resolver< - R, - Parent, - Context - >; - export type TypeResolver = Resolver< - R, - Parent, - Context - >; - export type SearchableResolver< - R = boolean, - Parent = InfraIndexField, - Context = InfraContext - > = Resolver; - export type AggregatableResolver< - R = boolean, - Parent = InfraIndexField, - Context = InfraContext - > = Resolver; - export type DisplayableResolver< - R = boolean, - Parent = InfraIndexField, - Context = InfraContext - > = Resolver; -} -/** A consecutive sequence of log entries */ -export namespace InfraLogEntryIntervalResolvers { - export interface Resolvers { - /** The key corresponding to the start of the interval covered by the entries */ - start?: StartResolver; - /** The key corresponding to the end of the interval covered by the entries */ - end?: EndResolver; - /** Whether there are more log entries available before the start */ - hasMoreBefore?: HasMoreBeforeResolver; - /** Whether there are more log entries available after the end */ - hasMoreAfter?: HasMoreAfterResolver; - /** The query the log entries were filtered by */ - filterQuery?: FilterQueryResolver; - /** The query the log entries were highlighted with */ - highlightQuery?: HighlightQueryResolver; - /** A list of the log entries */ - entries?: EntriesResolver; - } - - export type StartResolver< - R = InfraTimeKey | null, - Parent = InfraLogEntryInterval, - Context = InfraContext - > = Resolver; - export type EndResolver< - R = InfraTimeKey | null, - Parent = InfraLogEntryInterval, - Context = InfraContext - > = Resolver; - export type HasMoreBeforeResolver< - R = boolean, - Parent = InfraLogEntryInterval, - Context = InfraContext - > = Resolver; - export type HasMoreAfterResolver< - R = boolean, - Parent = InfraLogEntryInterval, - Context = InfraContext - > = Resolver; - export type FilterQueryResolver< - R = string | null, - Parent = InfraLogEntryInterval, - Context = InfraContext - > = Resolver; - export type HighlightQueryResolver< - R = string | null, - Parent = InfraLogEntryInterval, - Context = InfraContext - > = Resolver; - export type EntriesResolver< - R = InfraLogEntry[], - Parent = InfraLogEntryInterval, - Context = InfraContext - > = Resolver; -} -/** A representation of the log entry's position in the event stream */ -export namespace InfraTimeKeyResolvers { - export interface Resolvers { - /** The timestamp of the event that the log entry corresponds to */ - time?: TimeResolver; - /** The tiebreaker that disambiguates events with the same timestamp */ - tiebreaker?: TiebreakerResolver; - } - - export type TimeResolver = Resolver< - R, - Parent, - Context - >; - export type TiebreakerResolver< - R = number, - Parent = InfraTimeKey, - Context = InfraContext - > = Resolver; -} -/** A log entry */ -export namespace InfraLogEntryResolvers { - export interface Resolvers { - /** A unique representation of the log entry's position in the event stream */ - key?: KeyResolver; - /** The log entry's id */ - gid?: GidResolver; - /** The source id */ - source?: SourceResolver; - /** The columns used for rendering the log entry */ - columns?: ColumnsResolver; - } - - export type KeyResolver< - R = InfraTimeKey, - Parent = InfraLogEntry, - Context = InfraContext - > = Resolver; - export type GidResolver = Resolver< - R, - Parent, - Context - >; - export type SourceResolver = Resolver< - R, - Parent, - Context - >; - export type ColumnsResolver< - R = InfraLogEntryColumn[], - Parent = InfraLogEntry, - Context = InfraContext - > = Resolver; -} -/** A special built-in column that contains the log entry's timestamp */ -export namespace InfraLogEntryTimestampColumnResolvers { - export interface Resolvers { - /** The id of the corresponding column configuration */ - columnId?: ColumnIdResolver; - /** The timestamp */ - timestamp?: TimestampResolver; - } - - export type ColumnIdResolver< - R = string, - Parent = InfraLogEntryTimestampColumn, - Context = InfraContext - > = Resolver; - export type TimestampResolver< - R = number, - Parent = InfraLogEntryTimestampColumn, - Context = InfraContext - > = Resolver; -} -/** A special built-in column that contains the log entry's constructed message */ -export namespace InfraLogEntryMessageColumnResolvers { - export interface Resolvers { - /** The id of the corresponding column configuration */ - columnId?: ColumnIdResolver; - /** A list of the formatted log entry segments */ - message?: MessageResolver; - } - - export type ColumnIdResolver< - R = string, - Parent = InfraLogEntryMessageColumn, - Context = InfraContext - > = Resolver; - export type MessageResolver< - R = InfraLogMessageSegment[], - Parent = InfraLogEntryMessageColumn, - Context = InfraContext - > = Resolver; -} -/** A segment of the log entry message that was derived from a field */ -export namespace InfraLogMessageFieldSegmentResolvers { - export interface Resolvers { - /** The field the segment was derived from */ - field?: FieldResolver; - /** The segment's message */ - value?: ValueResolver; - /** A list of highlighted substrings of the value */ - highlights?: HighlightsResolver; - } - - export type FieldResolver< - R = string, - Parent = InfraLogMessageFieldSegment, - Context = InfraContext - > = Resolver; - export type ValueResolver< - R = string, - Parent = InfraLogMessageFieldSegment, - Context = InfraContext - > = Resolver; - export type HighlightsResolver< - R = string[], - Parent = InfraLogMessageFieldSegment, - Context = InfraContext - > = Resolver; -} -/** A segment of the log entry message that was derived from a string literal */ -export namespace InfraLogMessageConstantSegmentResolvers { - export interface Resolvers { - /** The segment's message */ - constant?: ConstantResolver; - } - - export type ConstantResolver< - R = string, - Parent = InfraLogMessageConstantSegment, - Context = InfraContext - > = Resolver; -} -/** A column that contains the value of a field of the log entry */ -export namespace InfraLogEntryFieldColumnResolvers { - export interface Resolvers { - /** The id of the corresponding column configuration */ - columnId?: ColumnIdResolver; - /** The field name of the column */ - field?: FieldResolver; - /** The value of the field in the log entry */ - value?: ValueResolver; - /** A list of highlighted substrings of the value */ - highlights?: HighlightsResolver; - } - - export type ColumnIdResolver< - R = string, - Parent = InfraLogEntryFieldColumn, - Context = InfraContext - > = Resolver; - export type FieldResolver< - R = string, - Parent = InfraLogEntryFieldColumn, - Context = InfraContext - > = Resolver; - export type ValueResolver< - R = string, - Parent = InfraLogEntryFieldColumn, - Context = InfraContext - > = Resolver; - export type HighlightsResolver< - R = string[], - Parent = InfraLogEntryFieldColumn, - Context = InfraContext - > = Resolver; -} - -export namespace InfraSnapshotResponseResolvers { - export interface Resolvers { - /** Nodes of type host, container or pod grouped by 0, 1 or 2 terms */ - nodes?: NodesResolver; - } - - export type NodesResolver< - R = InfraSnapshotNode[], - Parent = InfraSnapshotResponse, - Context = InfraContext - > = Resolver; - export interface NodesArgs { - type: InfraNodeType; - - groupBy: InfraSnapshotGroupbyInput[]; - - metric: InfraSnapshotMetricInput; - } -} - -export namespace InfraSnapshotNodeResolvers { - export interface Resolvers { - path?: PathResolver; - - metric?: MetricResolver; - } - - export type PathResolver< - R = InfraSnapshotNodePath[], - Parent = InfraSnapshotNode, - Context = InfraContext - > = Resolver; - export type MetricResolver< - R = InfraSnapshotNodeMetric, - Parent = InfraSnapshotNode, - Context = InfraContext - > = Resolver; -} - -export namespace InfraSnapshotNodePathResolvers { - export interface Resolvers { - value?: ValueResolver; - - label?: LabelResolver; - - ip?: IpResolver; - } - - export type ValueResolver< - R = string, - Parent = InfraSnapshotNodePath, - Context = InfraContext - > = Resolver; - export type LabelResolver< - R = string, - Parent = InfraSnapshotNodePath, - Context = InfraContext - > = Resolver; - export type IpResolver< - R = string | null, - Parent = InfraSnapshotNodePath, - Context = InfraContext - > = Resolver; -} - -export namespace InfraSnapshotNodeMetricResolvers { - export interface Resolvers { - name?: NameResolver; - - value?: ValueResolver; - - avg?: AvgResolver; - - max?: MaxResolver; - } - - export type NameResolver< - R = InfraSnapshotMetricType, - Parent = InfraSnapshotNodeMetric, - Context = InfraContext - > = Resolver; - export type ValueResolver< - R = number | null, - Parent = InfraSnapshotNodeMetric, - Context = InfraContext - > = Resolver; - export type AvgResolver< - R = number | null, - Parent = InfraSnapshotNodeMetric, - Context = InfraContext - > = Resolver; - export type MaxResolver< - R = number | null, - Parent = InfraSnapshotNodeMetric, - Context = InfraContext - > = Resolver; -} - -export namespace InfraMetricDataResolvers { - export interface Resolvers { - id?: IdResolver; - - series?: SeriesResolver; - } - - export type IdResolver< - R = InfraMetric | null, - Parent = InfraMetricData, - Context = InfraContext - > = Resolver; - export type SeriesResolver< - R = InfraDataSeries[], - Parent = InfraMetricData, - Context = InfraContext - > = Resolver; -} - -export namespace InfraDataSeriesResolvers { - export interface Resolvers { - id?: IdResolver; - - label?: LabelResolver; - - data?: DataResolver; - } - - export type IdResolver = Resolver< - R, - Parent, - Context - >; - export type LabelResolver< - R = string, - Parent = InfraDataSeries, - Context = InfraContext - > = Resolver; - export type DataResolver< - R = InfraDataPoint[], - Parent = InfraDataSeries, - Context = InfraContext - > = Resolver; -} - -export namespace InfraDataPointResolvers { - export interface Resolvers { - timestamp?: TimestampResolver; - - value?: ValueResolver; - } - - export type TimestampResolver< - R = number, - Parent = InfraDataPoint, - Context = InfraContext - > = Resolver; - export type ValueResolver< - R = number | null, - Parent = InfraDataPoint, - Context = InfraContext - > = Resolver; -} - -export namespace MutationResolvers { - export interface Resolvers { - /** Create a new source of infrastructure data */ - createSource?: CreateSourceResolver; - /** Modify an existing source */ - updateSource?: UpdateSourceResolver; - /** Delete a source of infrastructure data */ - deleteSource?: DeleteSourceResolver; - } - - export type CreateSourceResolver< - R = UpdateSourceResult, - Parent = never, - Context = InfraContext - > = Resolver; - export interface CreateSourceArgs { - /** The id of the source */ - id: string; - - sourceProperties: UpdateSourceInput; - } - - export type UpdateSourceResolver< - R = UpdateSourceResult, - Parent = never, - Context = InfraContext - > = Resolver; - export interface UpdateSourceArgs { - /** The id of the source */ - id: string; - /** The properties to update the source with */ - sourceProperties: UpdateSourceInput; - } - - export type DeleteSourceResolver< - R = DeleteSourceResult, - Parent = never, - Context = InfraContext - > = Resolver; - export interface DeleteSourceArgs { - /** The id of the source */ - id: string; - } -} -/** The result of a successful source update */ -export namespace UpdateSourceResultResolvers { - export interface Resolvers { - /** The source that was updated */ - source?: SourceResolver; - } - - export type SourceResolver< - R = InfraSource, - Parent = UpdateSourceResult, - Context = InfraContext - > = Resolver; -} -/** The result of a source deletion operations */ -export namespace DeleteSourceResultResolvers { - export interface Resolvers { - /** The id of the source that was deleted */ - id?: IdResolver; - } - - export type IdResolver< - R = string, - Parent = DeleteSourceResult, - Context = InfraContext - > = Resolver; -} diff --git a/x-pack/plugins/infra/server/infra_server.ts b/x-pack/plugins/infra/server/infra_server.ts index b510519a4fd0d..6ce5d5b8c53e8 100644 --- a/x-pack/plugins/infra/server/infra_server.ts +++ b/x-pack/plugins/infra/server/infra_server.ts @@ -4,11 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IResolvers, makeExecutableSchema } from 'graphql-tools'; import { initIpToHostName } from './routes/ip_to_hostname'; -import { schemas } from './graphql'; -import { createSourceStatusResolvers } from './graphql/source_status'; -import { createSourcesResolvers } from './graphql/sources'; import { InfraBackendLibs } from './lib/infra_types'; import { initGetLogEntryCategoriesRoute, @@ -44,16 +40,6 @@ import { initGetLogAlertsChartPreviewDataRoute } from './routes/log_alerts'; import { initProcessListRoute } from './routes/process_list'; export const initInfraServer = (libs: InfraBackendLibs) => { - const schema = makeExecutableSchema({ - resolvers: [ - createSourcesResolvers(libs) as IResolvers, - createSourceStatusResolvers(libs) as IResolvers, - ], - typeDefs: schemas, - }); - - libs.framework.registerGraphQLEndpoint('/graphql', schema); - initIpToHostName(libs); initGetLogEntryCategoriesRoute(libs); initGetLogEntryCategoryDatasetsRoute(libs); diff --git a/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts b/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts index b96b0e5bb0b48..6c83dae32912e 100644 --- a/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts +++ b/x-pack/plugins/infra/server/lib/adapters/framework/kibana_framework_adapter.ts @@ -4,9 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { GraphQLSchema } from 'graphql'; -import { runHttpQuery } from 'apollo-server-core'; -import { schema, TypeOf } from '@kbn/config-schema'; import { InfraRouteConfig, InfraTSVBResponse, @@ -23,7 +20,6 @@ import { CoreSetup, IRouter, KibanaRequest, - KibanaResponseFactory, RouteMethod, } from '../../../../../../../src/core/server'; import { RequestHandler } from '../../../../../../../src/core/server'; @@ -73,79 +69,6 @@ export class KibanaFramework { } } - public registerGraphQLEndpoint(routePath: string, gqlSchema: GraphQLSchema) { - // These endpoints are validated by GraphQL at runtime and with GraphQL generated types - const body = schema.object({}, { unknowns: 'allow' }); - type Body = TypeOf; - - const routeOptions = { - path: `/api/infra${routePath}`, - validate: { - body, - }, - options: { - tags: ['access:infra'], - }, - }; - async function handler( - context: InfraPluginRequestHandlerContext, - request: KibanaRequest, - response: KibanaResponseFactory - ) { - try { - const query = - request.route.method === 'post' - ? (request.body as Record) - : (request.query as Record); - - const gqlResponse = await runHttpQuery([context, request], { - method: request.route.method.toUpperCase(), - options: (req: InfraPluginRequestHandlerContext, rawReq: KibanaRequest) => ({ - context: { req, rawReq }, - schema: gqlSchema, - }), - query, - }); - - return response.ok({ - body: gqlResponse, - headers: { - 'content-type': 'application/json', - }, - }); - } catch (error) { - const errorBody = { - message: error.message, - }; - - if ('HttpQueryError' !== error.name) { - return response.internalError({ - body: errorBody, - }); - } - - if (error.isGraphQLError === true) { - return response.customError({ - statusCode: error.statusCode, - body: errorBody, - headers: { - 'Content-Type': 'application/json', - }, - }); - } - - const { headers = [], statusCode = 500 } = error; - return response.customError({ - statusCode, - headers, - body: errorBody, - }); - } - } - this.router.post(routeOptions, handler); - this.router.get(routeOptions, handler); - } - callWithRequest( requestContext: InfraPluginRequestHandlerContext, endpoint: 'search', @@ -187,7 +110,7 @@ export class KibanaFramework { options?: CallWithRequestParams ): Promise; - public async callWithRequest( + public async callWithRequest( requestContext: InfraPluginRequestHandlerContext, endpoint: string, params: CallWithRequestParams diff --git a/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts b/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts index ffbc750af14f8..6702a43cb2316 100644 --- a/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts +++ b/x-pack/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts @@ -215,6 +215,7 @@ function mapHitsToLogEntryDocuments(hits: SortedSearchHit[], fields: string[]): return { id: hit._id, + index: hit._index, cursor: { time: hit.sort[0], tiebreaker: hit.sort[1] }, fields: logFields, highlights: hit.highlight || {}, diff --git a/x-pack/plugins/infra/server/lib/adapters/metrics/lib/errors.ts b/x-pack/plugins/infra/server/lib/adapters/metrics/lib/errors.ts deleted file mode 100644 index 750858f3ce1fa..0000000000000 --- a/x-pack/plugins/infra/server/lib/adapters/metrics/lib/errors.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 { ApolloError } from 'apollo-server-errors'; -import { InfraMetricsErrorCodes } from '../../../../../common/errors'; - -export class InvalidNodeError extends ApolloError { - constructor(message: string) { - super(message, InfraMetricsErrorCodes.invalid_node); - Object.defineProperty(this, 'name', { value: 'InvalidNodeError' }); - } -} diff --git a/x-pack/plugins/infra/server/lib/domains/fields_domain.ts b/x-pack/plugins/infra/server/lib/domains/fields_domain.ts index a8bd09c28f949..3b0a1c61f6af0 100644 --- a/x-pack/plugins/infra/server/lib/domains/fields_domain.ts +++ b/x-pack/plugins/infra/server/lib/domains/fields_domain.ts @@ -5,9 +5,8 @@ */ import type { InfraPluginRequestHandlerContext } from '../../types'; -import { InfraIndexField, InfraIndexType } from '../../graphql/types'; import { FieldsAdapter } from '../adapters/fields'; -import { InfraSources } from '../sources'; +import { InfraSourceIndexField, InfraSources } from '../sources'; export class InfraFieldsDomain { constructor( @@ -18,14 +17,14 @@ export class InfraFieldsDomain { public async getFields( requestContext: InfraPluginRequestHandlerContext, sourceId: string, - indexType: InfraIndexType - ): Promise { + indexType: 'LOGS' | 'METRICS' | 'ANY' + ): Promise { const { configuration } = await this.libs.sources.getSourceConfiguration( requestContext.core.savedObjects.client, sourceId ); - const includeMetricIndices = [InfraIndexType.ANY, InfraIndexType.METRICS].includes(indexType); - const includeLogIndices = [InfraIndexType.ANY, InfraIndexType.LOGS].includes(indexType); + const includeMetricIndices = ['ANY', 'METRICS'].includes(indexType); + const includeLogIndices = ['ANY', 'LOGS'].includes(indexType); const fields = await this.adapter.getIndexFields( requestContext, diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts b/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts index 4c5debe58ed26..e318075045522 100644 --- a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts +++ b/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts @@ -12,19 +12,19 @@ import { LogEntriesSummaryHighlightsBucket, LogEntriesRequest, } from '../../../../common/http_api'; -import { LogEntry, LogColumn } from '../../../../common/log_entry'; +import { LogColumn, LogEntryCursor, LogEntry } from '../../../../common/log_entry'; import { InfraSourceConfiguration, InfraSources, SavedSourceConfigurationFieldColumnRuntimeType, } from '../../sources'; -import { getBuiltinRules } from './builtin_rules'; +import { getBuiltinRules } from '../../../services/log_entries/message/builtin_rules'; import { CompiledLogMessageFormattingRule, Fields, Highlights, compileFormattingRules, -} from './message'; +} from '../../../services/log_entries/message/message'; import { KibanaFramework } from '../../adapters/framework/kibana_framework_adapter'; import { decodeOrThrow } from '../../../../common/runtime_types'; import { @@ -33,7 +33,6 @@ import { CompositeDatasetKey, createLogEntryDatasetsQuery, } from './queries/log_entry_datasets'; -import { LogEntryCursor } from '../../../../common/log_entry'; export interface LogEntriesParams { startTimestamp: number; @@ -156,6 +155,7 @@ export class InfraLogEntriesDomain { const entries = documents.map((doc) => { return { id: doc.id, + index: doc.index, cursor: doc.cursor, columns: columnDefinitions.map( (column): LogColumn => { @@ -317,6 +317,7 @@ export type LogEntryQuery = JsonObject; export interface LogEntryDocument { id: string; + index: string; fields: Fields; highlights: Highlights; cursor: LogEntryCursor; diff --git a/x-pack/plugins/infra/server/lib/infra_types.ts b/x-pack/plugins/infra/server/lib/infra_types.ts index 084ece52302b0..eb24efb846451 100644 --- a/x-pack/plugins/infra/server/lib/infra_types.ts +++ b/x-pack/plugins/infra/server/lib/infra_types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { InfraSourceConfiguration } from '../../common/graphql/types'; +import { InfraSourceConfiguration } from '../../common/http_api/source_api'; import { InfraFieldsDomain } from './domains/fields_domain'; import { InfraLogEntriesDomain } from './domains/log_entries_domain'; import { InfraMetricsDomain } from './domains/metrics_domain'; @@ -13,13 +13,6 @@ import { InfraSourceStatus } from './source_status'; import { InfraConfig } from '../plugin'; import { KibanaFramework } from './adapters/framework/kibana_framework_adapter'; -// NP_TODO: We shouldn't need this context anymore but I am -// not sure how the graphql stuff uses it, so we can't remove it yet -export interface InfraContext { - req: any; - rawReq?: any; -} - export interface InfraDomainLibs { fields: InfraFieldsDomain; logEntries: InfraLogEntriesDomain; diff --git a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts index 071a8a94e009b..5d4846598d204 100644 --- a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts +++ b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts @@ -5,7 +5,6 @@ */ import type { ILegacyScopedClusterClient } from 'src/core/server'; -import { LogEntryContext } from '../../../common/log_entry'; import { compareDatasetsByMaximumAnomalyScore, getJobId, @@ -13,6 +12,7 @@ import { logEntryCategoriesJobTypes, CategoriesSort, } from '../../../common/log_analysis'; +import { LogEntryContext } from '../../../common/log_entry'; import { startTracingSpan } from '../../../common/performance_tracing'; import { decodeOrThrow } from '../../../common/runtime_types'; import type { MlAnomalyDetectors, MlSystem } from '../../types'; diff --git a/x-pack/plugins/infra/server/routes/log_sources/status.ts b/x-pack/plugins/infra/server/routes/log_sources/status.ts index e9466f7fa8878..71a91e44669d8 100644 --- a/x-pack/plugins/infra/server/routes/log_sources/status.ts +++ b/x-pack/plugins/infra/server/routes/log_sources/status.ts @@ -11,7 +11,6 @@ import { LOG_SOURCE_STATUS_PATH, } from '../../../common/http_api/log_sources'; import { createValidationFunction } from '../../../common/runtime_types'; -import { InfraIndexType } from '../../graphql/types'; import { InfraBackendLibs } from '../../lib/infra_types'; export const initLogSourceStatusRoutes = ({ @@ -34,7 +33,7 @@ export const initLogSourceStatusRoutes = ({ const logIndexStatus = await sourceStatus.getLogIndexStatus(requestContext, sourceId); const logIndexFields = logIndexStatus !== 'missing' - ? await fields.getFields(requestContext, sourceId, InfraIndexType.LOGS) + ? await fields.getFields(requestContext, sourceId, 'LOGS') : []; return response.ok({ diff --git a/x-pack/plugins/infra/server/routes/source/index.ts b/x-pack/plugins/infra/server/routes/source/index.ts index 9ff3902f1eae7..31d309c6295fd 100644 --- a/x-pack/plugins/infra/server/routes/source/index.ts +++ b/x-pack/plugins/infra/server/routes/source/index.ts @@ -4,20 +4,25 @@ * you may not use this file except in compliance with the Elastic License. */ import { schema } from '@kbn/config-schema'; -import { SourceResponseRuntimeType } from '../../../common/http_api/source_api'; +import Boom from '@hapi/boom'; +import { createValidationFunction } from '../../../common/runtime_types'; +import { + InfraSourceStatus, + SavedSourceConfigurationRuntimeType, + SourceResponseRuntimeType, +} from '../../../common/http_api/source_api'; import { InfraBackendLibs } from '../../lib/infra_types'; -import { InfraIndexType } from '../../graphql/types'; import { hasData } from '../../lib/sources/has_data'; import { createSearchClient } from '../../lib/create_search_client'; const typeToInfraIndexType = (value: string | undefined) => { switch (value) { case 'metrics': - return InfraIndexType.METRICS; + return 'METRICS'; case 'logs': - return InfraIndexType.LOGS; + return 'LOGS'; default: - return InfraIndexType.ANY; + return 'ANY'; } }; @@ -50,14 +55,14 @@ export const initSourceRoute = (libs: InfraBackendLibs) => { return response.notFound(); } - const status = { + const status: InfraSourceStatus = { logIndicesExist: logIndexStatus !== 'missing', metricIndicesExist, indexFields, }; return response.ok({ - body: SourceResponseRuntimeType.encode({ source, status }), + body: SourceResponseRuntimeType.encode({ source: { ...source, status } }), }); } catch (error) { return response.internalError({ @@ -67,6 +72,79 @@ export const initSourceRoute = (libs: InfraBackendLibs) => { } ); + framework.registerRoute( + { + method: 'patch', + path: '/api/metrics/source/{sourceId}', + validate: { + params: schema.object({ + sourceId: schema.string(), + }), + body: createValidationFunction(SavedSourceConfigurationRuntimeType), + }, + }, + framework.router.handleLegacyErrors(async (requestContext, request, response) => { + const { sources } = libs; + const { sourceId } = request.params; + const patchedSourceConfigurationProperties = request.body; + + try { + const sourceConfiguration = await sources.getSourceConfiguration( + requestContext.core.savedObjects.client, + sourceId + ); + + if (sourceConfiguration.origin === 'internal') { + response.conflict({ + body: 'A conflicting read-only source configuration already exists.', + }); + } + + const sourceConfigurationExists = sourceConfiguration.origin === 'stored'; + const patchedSourceConfiguration = await (sourceConfigurationExists + ? sources.updateSourceConfiguration( + requestContext.core.savedObjects.client, + sourceId, + patchedSourceConfigurationProperties + ) + : sources.createSourceConfiguration( + requestContext.core.savedObjects.client, + sourceId, + patchedSourceConfigurationProperties + )); + + const [logIndexStatus, metricIndicesExist, indexFields] = await Promise.all([ + libs.sourceStatus.getLogIndexStatus(requestContext, sourceId), + libs.sourceStatus.hasMetricIndices(requestContext, sourceId), + libs.fields.getFields(requestContext, sourceId, typeToInfraIndexType('metrics')), + ]); + + const status: InfraSourceStatus = { + logIndicesExist: logIndexStatus !== 'missing', + metricIndicesExist, + indexFields, + }; + + return response.ok({ + body: SourceResponseRuntimeType.encode({ + source: { ...patchedSourceConfiguration, status }, + }), + }); + } catch (error) { + if (Boom.isBoom(error)) { + throw error; + } + + return response.customError({ + statusCode: error.statusCode ?? 500, + body: { + message: error.message ?? 'An unexpected error occurred', + }, + }); + } + }) + ); + framework.registerRoute( { method: 'get', diff --git a/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.test.ts b/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.test.ts new file mode 100644 index 0000000000000..f7c2343e0fccb --- /dev/null +++ b/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.test.ts @@ -0,0 +1,320 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ResponseError } from '@elastic/elasticsearch/lib/errors'; +import { of, throwError } from 'rxjs'; +import { + elasticsearchServiceMock, + savedObjectsClientMock, + uiSettingsServiceMock, +} from 'src/core/server/mocks'; +import { + IEsSearchRequest, + IEsSearchResponse, + ISearchStrategy, + SearchStrategyDependencies, +} from 'src/plugins/data/server'; +import { createSearchSessionsClientMock } from '../../../../../../src/plugins/data/server/search/mocks'; +import { InfraSource } from '../../lib/sources'; +import { createInfraSourcesMock } from '../../lib/sources/mocks'; +import { + logEntriesSearchRequestStateRT, + logEntriesSearchStrategyProvider, +} from './log_entries_search_strategy'; + +describe('LogEntries search strategy', () => { + it('handles initial search requests', async () => { + const esSearchStrategyMock = createEsSearchStrategyMock({ + id: 'ASYNC_REQUEST_ID', + isRunning: true, + rawResponse: { + took: 0, + _shards: { total: 1, failed: 0, skipped: 0, successful: 0 }, + timed_out: false, + hits: { total: 0, max_score: 0, hits: [] }, + }, + }); + + const dataMock = createDataPluginMock(esSearchStrategyMock); + const sourcesMock = createInfraSourcesMock(); + sourcesMock.getSourceConfiguration.mockResolvedValue(createSourceConfigurationMock()); + const mockDependencies = createSearchStrategyDependenciesMock(); + + const logEntriesSearchStrategy = logEntriesSearchStrategyProvider({ + data: dataMock, + sources: sourcesMock, + }); + + const response = await logEntriesSearchStrategy + .search( + { + params: { + sourceId: 'SOURCE_ID', + startTimestamp: 100, + endTimestamp: 200, + size: 3, + }, + }, + {}, + mockDependencies + ) + .toPromise(); + + expect(sourcesMock.getSourceConfiguration).toHaveBeenCalled(); + expect(esSearchStrategyMock.search).toHaveBeenCalledWith( + expect.objectContaining({ + params: expect.objectContaining({ + index: 'log-indices-*', + body: expect.objectContaining({ + fields: expect.arrayContaining(['event.dataset', 'message']), + }), + }), + }), + expect.anything(), + expect.anything() + ); + expect(response.id).toEqual(expect.any(String)); + expect(response.isRunning).toBe(true); + }); + + it('handles subsequent polling requests', async () => { + const esSearchStrategyMock = createEsSearchStrategyMock({ + id: 'ASYNC_REQUEST_ID', + isRunning: false, + rawResponse: { + took: 1, + _shards: { total: 1, failed: 0, skipped: 0, successful: 1 }, + timed_out: false, + hits: { + total: 0, + max_score: 0, + hits: [ + { + _id: 'HIT_ID', + _index: 'HIT_INDEX', + _type: '_doc', + _score: 0, + _source: null, + fields: { + '@timestamp': [1605116827143], + 'event.dataset': ['HIT_DATASET'], + MESSAGE_FIELD: ['HIT_MESSAGE'], + 'container.id': ['HIT_CONTAINER_ID'], + }, + sort: [1605116827143 as any, 1 as any], // incorrectly typed as string upstream + }, + ], + }, + }, + }); + const dataMock = createDataPluginMock(esSearchStrategyMock); + const sourcesMock = createInfraSourcesMock(); + sourcesMock.getSourceConfiguration.mockResolvedValue(createSourceConfigurationMock()); + const mockDependencies = createSearchStrategyDependenciesMock(); + + const logEntriesSearchStrategy = logEntriesSearchStrategyProvider({ + data: dataMock, + sources: sourcesMock, + }); + const requestId = logEntriesSearchRequestStateRT.encode({ + esRequestId: 'ASYNC_REQUEST_ID', + }); + + const response = await logEntriesSearchStrategy + .search( + { + id: requestId, + params: { + sourceId: 'SOURCE_ID', + startTimestamp: 100, + endTimestamp: 200, + size: 3, + }, + }, + {}, + mockDependencies + ) + .toPromise(); + + expect(sourcesMock.getSourceConfiguration).toHaveBeenCalled(); + expect(esSearchStrategyMock.search).toHaveBeenCalled(); + expect(response.id).toEqual(requestId); + expect(response.isRunning).toBe(false); + expect(response.rawResponse.data.entries).toEqual([ + { + id: 'HIT_ID', + index: 'HIT_INDEX', + cursor: { + time: 1605116827143, + tiebreaker: 1, + }, + columns: [ + { + columnId: 'TIMESTAMP_COLUMN_ID', + timestamp: 1605116827143, + }, + { + columnId: 'DATASET_COLUMN_ID', + field: 'event.dataset', + value: ['HIT_DATASET'], + highlights: [], + }, + { + columnId: 'MESSAGE_COLUMN_ID', + message: [ + { + field: 'MESSAGE_FIELD', + value: ['HIT_MESSAGE'], + highlights: [], + }, + ], + }, + ], + context: { + 'container.id': 'HIT_CONTAINER_ID', + }, + }, + ]); + }); + + it('forwards errors from the underlying search strategy', async () => { + const esSearchStrategyMock = createEsSearchStrategyMock({ + id: 'ASYNC_REQUEST_ID', + isRunning: false, + rawResponse: { + took: 1, + _shards: { total: 1, failed: 0, skipped: 0, successful: 1 }, + timed_out: false, + hits: { total: 0, max_score: 0, hits: [] }, + }, + }); + const dataMock = createDataPluginMock(esSearchStrategyMock); + const sourcesMock = createInfraSourcesMock(); + sourcesMock.getSourceConfiguration.mockResolvedValue(createSourceConfigurationMock()); + const mockDependencies = createSearchStrategyDependenciesMock(); + + const logEntriesSearchStrategy = logEntriesSearchStrategyProvider({ + data: dataMock, + sources: sourcesMock, + }); + + const response = logEntriesSearchStrategy.search( + { + id: logEntriesSearchRequestStateRT.encode({ esRequestId: 'UNKNOWN_ID' }), + params: { + sourceId: 'SOURCE_ID', + startTimestamp: 100, + endTimestamp: 200, + size: 3, + }, + }, + {}, + mockDependencies + ); + + await expect(response.toPromise()).rejects.toThrowError(ResponseError); + }); + + it('forwards cancellation to the underlying search strategy', async () => { + const esSearchStrategyMock = createEsSearchStrategyMock({ + id: 'ASYNC_REQUEST_ID', + isRunning: false, + rawResponse: { + took: 1, + _shards: { total: 1, failed: 0, skipped: 0, successful: 1 }, + timed_out: false, + hits: { total: 0, max_score: 0, hits: [] }, + }, + }); + const dataMock = createDataPluginMock(esSearchStrategyMock); + const sourcesMock = createInfraSourcesMock(); + sourcesMock.getSourceConfiguration.mockResolvedValue(createSourceConfigurationMock()); + const mockDependencies = createSearchStrategyDependenciesMock(); + + const logEntriesSearchStrategy = logEntriesSearchStrategyProvider({ + data: dataMock, + sources: sourcesMock, + }); + const requestId = logEntriesSearchRequestStateRT.encode({ + esRequestId: 'ASYNC_REQUEST_ID', + }); + + await logEntriesSearchStrategy.cancel?.(requestId, {}, mockDependencies); + + expect(esSearchStrategyMock.cancel).toHaveBeenCalled(); + }); +}); + +const createSourceConfigurationMock = (): InfraSource => ({ + id: 'SOURCE_ID', + origin: 'stored' as const, + configuration: { + name: 'SOURCE_NAME', + description: 'SOURCE_DESCRIPTION', + logAlias: 'log-indices-*', + metricAlias: 'metric-indices-*', + inventoryDefaultView: 'DEFAULT_VIEW', + metricsExplorerDefaultView: 'DEFAULT_VIEW', + logColumns: [ + { timestampColumn: { id: 'TIMESTAMP_COLUMN_ID' } }, + { + fieldColumn: { + id: 'DATASET_COLUMN_ID', + field: 'event.dataset', + }, + }, + { + messageColumn: { id: 'MESSAGE_COLUMN_ID' }, + }, + ], + fields: { + pod: 'POD_FIELD', + host: 'HOST_FIELD', + container: 'CONTAINER_FIELD', + message: ['MESSAGE_FIELD'], + timestamp: 'TIMESTAMP_FIELD', + tiebreaker: 'TIEBREAKER_FIELD', + }, + }, +}); + +const createEsSearchStrategyMock = (esSearchResponse: IEsSearchResponse) => ({ + search: jest.fn((esSearchRequest: IEsSearchRequest) => { + if (typeof esSearchRequest.id === 'string') { + if (esSearchRequest.id === esSearchResponse.id) { + return of(esSearchResponse); + } else { + return throwError( + new ResponseError({ + body: {}, + headers: {}, + meta: {} as any, + statusCode: 404, + warnings: [], + }) + ); + } + } else { + return of(esSearchResponse); + } + }), + cancel: jest.fn().mockResolvedValue(undefined), +}); + +const createSearchStrategyDependenciesMock = (): SearchStrategyDependencies => ({ + uiSettingsClient: uiSettingsServiceMock.createClient(), + esClient: elasticsearchServiceMock.createScopedClusterClient(), + savedObjectsClient: savedObjectsClientMock.create(), + searchSessionsClient: createSearchSessionsClientMock(), +}); + +// using the official data mock from within x-pack doesn't type-check successfully, +// because the `licensing` plugin modifies the `RequestHandlerContext` core type. +const createDataPluginMock = (esSearchStrategyMock: ISearchStrategy): any => ({ + search: { + getSearchStrategy: jest.fn().mockReturnValue(esSearchStrategyMock), + }, +}); diff --git a/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.ts b/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.ts new file mode 100644 index 0000000000000..6ce3d4410a2dd --- /dev/null +++ b/x-pack/plugins/infra/server/services/log_entries/log_entries_search_strategy.ts @@ -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 { pick } from '@kbn/std'; +import * as rt from 'io-ts'; +import { combineLatest, concat, defer, forkJoin, of } from 'rxjs'; +import { concatMap, filter, map, shareReplay, take } from 'rxjs/operators'; +import type { + IEsSearchRequest, + IKibanaSearchRequest, + IKibanaSearchResponse, +} from '../../../../../../src/plugins/data/common'; +import type { + ISearchStrategy, + PluginStart as DataPluginStart, +} from '../../../../../../src/plugins/data/server'; +import { LogSourceColumnConfiguration } from '../../../common/http_api/log_sources'; +import { + getLogEntryCursorFromHit, + LogColumn, + LogEntry, + LogEntryAfterCursor, + logEntryAfterCursorRT, + LogEntryBeforeCursor, + logEntryBeforeCursorRT, + LogEntryContext, +} from '../../../common/log_entry'; +import { decodeOrThrow } from '../../../common/runtime_types'; +import { + LogEntriesSearchRequestParams, + logEntriesSearchRequestParamsRT, + LogEntriesSearchResponsePayload, + logEntriesSearchResponsePayloadRT, +} from '../../../common/search_strategies/log_entries/log_entries'; +import type { IInfraSources } from '../../lib/sources'; +import { + createAsyncRequestRTs, + createErrorFromShardFailure, + jsonFromBase64StringRT, +} from '../../utils/typed_search_strategy'; +import { + CompiledLogMessageFormattingRule, + compileFormattingRules, + getBuiltinRules, +} from './message'; +import { + createGetLogEntriesQuery, + getLogEntriesResponseRT, + getSortDirection, + LogEntryHit, +} from './queries/log_entries'; + +type LogEntriesSearchRequest = IKibanaSearchRequest; +type LogEntriesSearchResponse = IKibanaSearchResponse; + +export const logEntriesSearchStrategyProvider = ({ + data, + sources, +}: { + data: DataPluginStart; + sources: IInfraSources; +}): ISearchStrategy => { + const esSearchStrategy = data.search.getSearchStrategy('ese'); + + return { + search: (rawRequest, options, dependencies) => + defer(() => { + const request = decodeOrThrow(asyncRequestRT)(rawRequest); + + const sourceConfiguration$ = defer(() => + sources.getSourceConfiguration(dependencies.savedObjectsClient, request.params.sourceId) + ).pipe(take(1), shareReplay(1)); + + const messageFormattingRules$ = defer(() => + sourceConfiguration$.pipe( + map(({ configuration }) => + compileFormattingRules(getBuiltinRules(configuration.fields.message)) + ) + ) + ).pipe(take(1), shareReplay(1)); + + const recoveredRequest$ = of(request).pipe( + filter(asyncRecoveredRequestRT.is), + map(({ id: { esRequestId } }) => ({ id: esRequestId })) + ); + + const initialRequest$ = of(request).pipe( + filter(asyncInitialRequestRT.is), + concatMap(({ params }) => + forkJoin([sourceConfiguration$, messageFormattingRules$]).pipe( + map( + ([{ configuration }, messageFormattingRules]): IEsSearchRequest => { + return { + params: createGetLogEntriesQuery( + configuration.logAlias, + params.startTimestamp, + params.endTimestamp, + pickRequestCursor(params), + params.size + 1, + configuration.fields.timestamp, + configuration.fields.tiebreaker, + messageFormattingRules.requiredFields, + params.query, + params.highlightPhrase + ), + }; + } + ) + ) + ) + ); + + const searchResponse$ = concat(recoveredRequest$, initialRequest$).pipe( + take(1), + concatMap((esRequest) => esSearchStrategy.search(esRequest, options, dependencies)) + ); + + return combineLatest([searchResponse$, sourceConfiguration$, messageFormattingRules$]).pipe( + map(([esResponse, { configuration }, messageFormattingRules]) => { + const rawResponse = decodeOrThrow(getLogEntriesResponseRT)(esResponse.rawResponse); + + const entries = rawResponse.hits.hits + .slice(0, request.params.size) + .map(getLogEntryFromHit(configuration.logColumns, messageFormattingRules)); + + const sortDirection = getSortDirection(pickRequestCursor(request.params)); + + if (sortDirection === 'desc') { + entries.reverse(); + } + + const hasMore = rawResponse.hits.hits.length > entries.length; + const hasMoreBefore = sortDirection === 'desc' ? hasMore : undefined; + const hasMoreAfter = sortDirection === 'asc' ? hasMore : undefined; + + const { topCursor, bottomCursor } = getResponseCursors(entries); + + const errors = (rawResponse._shards.failures ?? []).map(createErrorFromShardFailure); + + return { + ...esResponse, + ...(esResponse.id + ? { id: logEntriesSearchRequestStateRT.encode({ esRequestId: esResponse.id }) } + : {}), + rawResponse: logEntriesSearchResponsePayloadRT.encode({ + data: { entries, topCursor, bottomCursor, hasMoreBefore, hasMoreAfter }, + errors, + }), + }; + }) + ); + }), + cancel: async (id, options, dependencies) => { + const { esRequestId } = decodeOrThrow(logEntriesSearchRequestStateRT)(id); + return await esSearchStrategy.cancel?.(esRequestId, options, dependencies); + }, + }; +}; + +// exported for tests +export const logEntriesSearchRequestStateRT = rt.string.pipe(jsonFromBase64StringRT).pipe( + rt.type({ + esRequestId: rt.string, + }) +); + +const { asyncInitialRequestRT, asyncRecoveredRequestRT, asyncRequestRT } = createAsyncRequestRTs( + logEntriesSearchRequestStateRT, + logEntriesSearchRequestParamsRT +); + +const getLogEntryFromHit = ( + columnDefinitions: LogSourceColumnConfiguration[], + messageFormattingRules: CompiledLogMessageFormattingRule +) => (hit: LogEntryHit): LogEntry => { + const cursor = getLogEntryCursorFromHit(hit); + return { + id: hit._id, + index: hit._index, + cursor, + columns: columnDefinitions.map( + (column): LogColumn => { + if ('timestampColumn' in column) { + return { + columnId: column.timestampColumn.id, + timestamp: cursor.time, + }; + } else if ('messageColumn' in column) { + return { + columnId: column.messageColumn.id, + message: messageFormattingRules.format(hit.fields, hit.highlight || {}), + }; + } else { + return { + columnId: column.fieldColumn.id, + field: column.fieldColumn.field, + value: hit.fields[column.fieldColumn.field] ?? [], + highlights: hit.highlight?.[column.fieldColumn.field] ?? [], + }; + } + } + ), + context: getContextFromHit(hit), + }; +}; + +const pickRequestCursor = ( + params: LogEntriesSearchRequestParams +): LogEntryAfterCursor | LogEntryBeforeCursor | null => { + if (logEntryAfterCursorRT.is(params)) { + return pick(params, ['after']); + } else if (logEntryBeforeCursorRT.is(params)) { + return pick(params, ['before']); + } + + return null; +}; + +const getContextFromHit = (hit: LogEntryHit): LogEntryContext => { + // Get all context fields, then test for the presence and type of the ones that go together + const containerId = hit.fields['container.id']?.[0]; + const hostName = hit.fields['host.name']?.[0]; + const logFilePath = hit.fields['log.file.path']?.[0]; + + if (typeof containerId === 'string') { + return { 'container.id': containerId }; + } + + if (typeof hostName === 'string' && typeof logFilePath === 'string') { + return { 'host.name': hostName, 'log.file.path': logFilePath }; + } + + return {}; +}; + +function getResponseCursors(entries: LogEntry[]) { + const hasEntries = entries.length > 0; + const topCursor = hasEntries ? entries[0].cursor : null; + const bottomCursor = hasEntries ? entries[entries.length - 1].cursor : null; + + return { topCursor, bottomCursor }; +} diff --git a/x-pack/plugins/infra/server/services/log_entries/log_entries_service.ts b/x-pack/plugins/infra/server/services/log_entries/log_entries_service.ts index edd53be9db841..9aba69428f257 100644 --- a/x-pack/plugins/infra/server/services/log_entries/log_entries_service.ts +++ b/x-pack/plugins/infra/server/services/log_entries/log_entries_service.ts @@ -6,12 +6,18 @@ import { CoreSetup } from 'src/core/server'; import { LOG_ENTRY_SEARCH_STRATEGY } from '../../../common/search_strategies/log_entries/log_entry'; +import { LOG_ENTRIES_SEARCH_STRATEGY } from '../../../common/search_strategies/log_entries/log_entries'; +import { logEntriesSearchStrategyProvider } from './log_entries_search_strategy'; import { logEntrySearchStrategyProvider } from './log_entry_search_strategy'; import { LogEntriesServiceSetupDeps, LogEntriesServiceStartDeps } from './types'; export class LogEntriesService { public setup(core: CoreSetup, setupDeps: LogEntriesServiceSetupDeps) { core.getStartServices().then(([, startDeps]) => { + setupDeps.data.search.registerSearchStrategy( + LOG_ENTRIES_SEARCH_STRATEGY, + logEntriesSearchStrategyProvider({ ...setupDeps, ...startDeps }) + ); setupDeps.data.search.registerSearchStrategy( LOG_ENTRY_SEARCH_STRATEGY, logEntrySearchStrategyProvider({ ...setupDeps, ...startDeps }) diff --git a/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.test.ts b/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.test.ts index 38626675f5ae7..e9cdd09d84ac9 100644 --- a/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.test.ts +++ b/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.test.ts @@ -22,6 +22,7 @@ import { logEntrySearchRequestStateRT, logEntrySearchStrategyProvider, } from './log_entry_search_strategy'; +import { createSearchSessionsClientMock } from '../../../../../../src/plugins/data/server/search/mocks'; describe('LogEntry search strategy', () => { it('handles initial search requests', async () => { @@ -121,7 +122,7 @@ describe('LogEntry search strategy', () => { expect(response.rawResponse.data).toEqual({ id: 'HIT_ID', index: 'HIT_INDEX', - key: { + cursor: { time: 1605116827143, tiebreaker: 1, }, @@ -244,6 +245,7 @@ const createSearchStrategyDependenciesMock = (): SearchStrategyDependencies => ( uiSettingsClient: uiSettingsServiceMock.createClient(), esClient: elasticsearchServiceMock.createScopedClusterClient(), savedObjectsClient: savedObjectsClientMock.create(), + searchSessionsClient: createSearchSessionsClientMock(), }); // using the official data mock from within x-pack doesn't type-check successfully, diff --git a/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.ts b/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.ts index a0dfe3d7176fd..ab2b72055e4a4 100644 --- a/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.ts +++ b/x-pack/plugins/infra/server/services/log_entries/log_entry_search_strategy.ts @@ -119,6 +119,6 @@ const { asyncInitialRequestRT, asyncRecoveredRequestRT, asyncRequestRT } = creat const createLogEntryFromHit = (hit: LogEntryHit) => ({ id: hit._id, index: hit._index, - key: getLogEntryCursorFromHit(hit), + cursor: getLogEntryCursorFromHit(hit), fields: Object.entries(hit.fields).map(([field, value]) => ({ field, value })), }); diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_apache2.test.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_apache2.test.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_apache2.test.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_apache2.test.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_apache2.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_apache2.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_apache2.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_apache2.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_auditd.test.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_auditd.test.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_auditd.test.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_auditd.test.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_auditd.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_auditd.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_auditd.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_auditd.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_haproxy.test.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_haproxy.test.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_haproxy.test.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_haproxy.test.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_haproxy.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_haproxy.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_haproxy.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_haproxy.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_icinga.test.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_icinga.test.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_icinga.test.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_icinga.test.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_icinga.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_icinga.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_icinga.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_icinga.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_iis.test.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_iis.test.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_iis.test.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_iis.test.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_iis.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_iis.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_iis.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_iis.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_kafka.test.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_kafka.test.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_kafka.test.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_kafka.test.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_logstash.test.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_logstash.test.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_logstash.test.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_logstash.test.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_logstash.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_logstash.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_logstash.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_logstash.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_mongodb.test.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_mongodb.test.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_mongodb.test.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_mongodb.test.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_mongodb.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_mongodb.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_mongodb.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_mongodb.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_mysql.test.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_mysql.test.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_mysql.test.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_mysql.test.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_mysql.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_mysql.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_mysql.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_mysql.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_nginx.test.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_nginx.test.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_nginx.test.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_nginx.test.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_nginx.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_nginx.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_nginx.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_nginx.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_osquery.test.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_osquery.test.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_osquery.test.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_osquery.test.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_osquery.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_osquery.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_osquery.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_osquery.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_redis.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_redis.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_redis.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_redis.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_system.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_system.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_system.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_system.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_traefik.test.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_traefik.test.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_traefik.test.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_traefik.test.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_traefik.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_traefik.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/filebeat_traefik.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/filebeat_traefik.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/generic.test.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/generic.test.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/generic.test.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/generic.test.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/generic.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/generic.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/generic.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/generic.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/generic_webserver.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/generic_webserver.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/generic_webserver.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/generic_webserver.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/helpers.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/helpers.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/helpers.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/helpers.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/index.ts b/x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/index.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/builtin_rules/index.ts rename to x-pack/plugins/infra/server/services/log_entries/message/builtin_rules/index.ts diff --git a/x-pack/plugins/infra/common/graphql/shared/index.ts b/x-pack/plugins/infra/server/services/log_entries/message/index.ts similarity index 70% rename from x-pack/plugins/infra/common/graphql/shared/index.ts rename to x-pack/plugins/infra/server/services/log_entries/message/index.ts index 56c8675e76caf..05126eea075af 100644 --- a/x-pack/plugins/infra/common/graphql/shared/index.ts +++ b/x-pack/plugins/infra/server/services/log_entries/message/index.ts @@ -4,5 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -export { sharedFragments } from './fragments.gql_query'; -export { sharedSchema } from './schema.gql'; +export * from './message'; +export { getBuiltinRules } from './builtin_rules'; diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/message.ts b/x-pack/plugins/infra/server/services/log_entries/message/message.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/message.ts rename to x-pack/plugins/infra/server/services/log_entries/message/message.ts diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/rule_types.ts b/x-pack/plugins/infra/server/services/log_entries/message/rule_types.ts similarity index 100% rename from x-pack/plugins/infra/server/lib/domains/log_entries_domain/rule_types.ts rename to x-pack/plugins/infra/server/services/log_entries/message/rule_types.ts diff --git a/x-pack/plugins/infra/server/services/log_entries/queries/common.ts b/x-pack/plugins/infra/server/services/log_entries/queries/common.ts new file mode 100644 index 0000000000000..f170fa337a8b9 --- /dev/null +++ b/x-pack/plugins/infra/server/services/log_entries/queries/common.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. + */ + +export const createSortClause = ( + sortDirection: 'asc' | 'desc', + timestampField: string, + tiebreakerField: string +) => ({ + sort: { + [timestampField]: sortDirection, + [tiebreakerField]: sortDirection, + }, +}); + +export const createTimeRangeFilterClauses = ( + startTimestamp: number, + endTimestamp: number, + timestampField: string +) => [ + { + range: { + [timestampField]: { + gte: startTimestamp, + lte: endTimestamp, + format: 'epoch_millis', + }, + }, + }, +]; diff --git a/x-pack/plugins/infra/server/services/log_entries/queries/log_entries.ts b/x-pack/plugins/infra/server/services/log_entries/queries/log_entries.ts new file mode 100644 index 0000000000000..81476fa2b286e --- /dev/null +++ b/x-pack/plugins/infra/server/services/log_entries/queries/log_entries.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 type { RequestParams } from '@elastic/elasticsearch'; +import * as rt from 'io-ts'; +import { + LogEntryAfterCursor, + logEntryAfterCursorRT, + LogEntryBeforeCursor, + logEntryBeforeCursorRT, +} from '../../../../common/log_entry'; +import { jsonArrayRT, JsonObject } from '../../../../common/typed_json'; +import { + commonHitFieldsRT, + commonSearchSuccessResponseFieldsRT, +} from '../../../utils/elasticsearch_runtime_types'; +import { createSortClause, createTimeRangeFilterClauses } from './common'; + +export const createGetLogEntriesQuery = ( + logEntriesIndex: string, + startTimestamp: number, + endTimestamp: number, + cursor: LogEntryBeforeCursor | LogEntryAfterCursor | null | undefined, + size: number, + timestampField: string, + tiebreakerField: string, + fields: string[], + query?: JsonObject, + highlightTerm?: string +): RequestParams.AsyncSearchSubmit> => { + const sortDirection = getSortDirection(cursor); + const highlightQuery = createHighlightQuery(highlightTerm, fields); + + return { + index: logEntriesIndex, + allow_no_indices: true, + track_scores: false, + track_total_hits: false, + body: { + size, + query: { + bool: { + filter: [ + ...(query ? [query] : []), + ...(highlightQuery ? [highlightQuery] : []), + ...createTimeRangeFilterClauses(startTimestamp, endTimestamp, timestampField), + ], + }, + }, + fields, + _source: false, + ...createSortClause(sortDirection, timestampField, tiebreakerField), + ...createSearchAfterClause(cursor), + ...createHighlightClause(highlightQuery, fields), + }, + }; +}; + +export const getSortDirection = ( + cursor: LogEntryBeforeCursor | LogEntryAfterCursor | null | undefined +): 'asc' | 'desc' => (logEntryBeforeCursorRT.is(cursor) ? 'desc' : 'asc'); + +const createSearchAfterClause = ( + cursor: LogEntryBeforeCursor | LogEntryAfterCursor | null | undefined +): { search_after?: [number, number] } => { + if (logEntryBeforeCursorRT.is(cursor) && cursor.before !== 'last') { + return { + search_after: [cursor.before.time, cursor.before.tiebreaker], + }; + } else if (logEntryAfterCursorRT.is(cursor) && cursor.after !== 'first') { + return { + search_after: [cursor.after.time, cursor.after.tiebreaker], + }; + } + + return {}; +}; + +const createHighlightClause = (highlightQuery: JsonObject | undefined, fields: string[]) => + 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 createHighlightQuery = ( + highlightTerm: string | undefined, + fields: string[] +): JsonObject | undefined => { + if (highlightTerm) { + return { + multi_match: { + fields, + lenient: true, + query: highlightTerm, + type: 'phrase', + }, + }; + } +}; + +export const logEntryHitRT = rt.intersection([ + commonHitFieldsRT, + rt.type({ + fields: rt.record(rt.string, jsonArrayRT), + sort: rt.tuple([rt.number, rt.number]), + }), + rt.partial({ + highlight: rt.record(rt.string, rt.array(rt.string)), + }), +]); + +export type LogEntryHit = rt.TypeOf; + +export const getLogEntriesResponseRT = rt.intersection([ + commonSearchSuccessResponseFieldsRT, + rt.type({ + hits: rt.type({ + hits: rt.array(logEntryHitRT), + }), + }), +]); + +export type GetLogEntriesResponse = rt.TypeOf; diff --git a/x-pack/plugins/infra/server/types.ts b/x-pack/plugins/infra/server/types.ts index 2a30bf7cf093d..8731067e73080 100644 --- a/x-pack/plugins/infra/server/types.ts +++ b/x-pack/plugins/infra/server/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import type { RequestHandlerContext } from 'src/core/server'; -import type { DataApiRequestHandlerContext } from '../../../../src/plugins/data/server'; +import type { SearchRequestHandlerContext } from '../../../../src/plugins/data/server'; import { MlPluginSetup } from '../../ml/server'; export type MlSystem = ReturnType; @@ -27,5 +27,5 @@ export type InfraRequestHandlerContext = InfraMlRequestHandlerContext & */ export interface InfraPluginRequestHandlerContext extends RequestHandlerContext { infra: InfraRequestHandlerContext; - search: DataApiRequestHandlerContext; + search: SearchRequestHandlerContext; } diff --git a/x-pack/plugins/infra/server/utils/serialized_query.ts b/x-pack/plugins/infra/server/utils/serialized_query.ts index 8dfe00fcd380e..3a38a0de959dc 100644 --- a/x-pack/plugins/infra/server/utils/serialized_query.ts +++ b/x-pack/plugins/infra/server/utils/serialized_query.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { UserInputError } from 'apollo-server-errors'; - import { JsonObject } from '../../../../../src/plugins/kibana_utils/common'; export const parseFilterQuery = ( @@ -26,9 +24,6 @@ export const parseFilterQuery = ( return undefined; } } catch (err) { - throw new UserInputError(`Failed to parse query: ${err}`, { - query: filterQuery, - originalError: err, - }); + throw new Error(`Failed to parse query: ${err}`); } }; diff --git a/x-pack/plugins/infra/server/utils/typed_resolvers.ts b/x-pack/plugins/infra/server/utils/typed_resolvers.ts deleted file mode 100644 index d5f2d00abd504..0000000000000 --- a/x-pack/plugins/infra/server/utils/typed_resolvers.ts +++ /dev/null @@ -1,97 +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 { Resolver } from '../graphql/types'; - -type ResolverResult = R | Promise; - -type InfraResolverResult = - | Promise - | Promise<{ [P in keyof R]: () => Promise }> - | { [P in keyof R]: () => Promise } - | { [P in keyof R]: () => R[P] } - | R; - -export type ResultOf = Resolver_ extends Resolver> - ? Result - : never; - -export type SubsetResolverWithFields = R extends Resolver< - Array, - infer ParentInArray, - infer ContextInArray, - infer ArgsInArray -> - ? Resolver< - Array>>, - ParentInArray, - ContextInArray, - ArgsInArray - > - : R extends Resolver - ? Resolver>, Parent, Context, Args> - : never; - -export type SubsetResolverWithoutFields = R extends Resolver< - Array, - infer ParentInArray, - infer ContextInArray, - infer ArgsInArray -> - ? Resolver< - Array>>, - ParentInArray, - ContextInArray, - ArgsInArray - > - : R extends Resolver - ? Resolver>, Parent, Context, Args> - : never; - -export type ResolverWithParent = Resolver_ extends Resolver< - infer Result, - any, - infer Context, - infer Args -> - ? Resolver - : never; - -export type InfraResolver = Resolver< - InfraResolverResult, - Parent, - Context, - Args ->; - -export type InfraResolverOf = Resolver_ extends Resolver< - ResolverResult, - never, - infer ContextWithNeverParent, - infer ArgsWithNeverParent -> - ? InfraResolver - : Resolver_ extends Resolver< - ResolverResult, - infer Parent, - infer Context, - infer Args - > - ? InfraResolver - : never; - -export type InfraResolverWithFields = InfraResolverOf< - SubsetResolverWithFields ->; - -export type InfraResolverWithoutFields = InfraResolverOf< - SubsetResolverWithoutFields ->; - -export type ChildResolverOf = ResolverWithParent< - Resolver_, - ResultOf ->; diff --git a/x-pack/plugins/infra/types/graphql_fields.d.ts b/x-pack/plugins/infra/types/graphql_fields.d.ts deleted file mode 100644 index 5e5320a31b3bf..0000000000000 --- a/x-pack/plugins/infra/types/graphql_fields.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 'graphql-fields' { - function graphqlFields(info: any, obj?: any): any; - // eslint-disable-next-line import/no-default-export - export default graphqlFields; -} diff --git a/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx b/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx index 3df9e8a5145bc..93319aeb8d816 100644 --- a/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/datatable_visualization/visualization.tsx @@ -16,13 +16,13 @@ import type { import type { DatatableColumnWidth } from './components/types'; import { LensIconChartDatatable } from '../assets/chart_datatable'; -export interface LayerState { +export interface DatatableLayerState { layerId: string; columns: string[]; } export interface DatatableVisualizationState { - layers: LayerState[]; + layers: DatatableLayerState[]; sorting?: { columnId: string | undefined; direction: 'asc' | 'desc' | 'none'; @@ -30,7 +30,7 @@ export interface DatatableVisualizationState { columnWidth?: DatatableColumnWidth[]; } -function newLayerState(layerId: string): LayerState { +function newLayerState(layerId: string): DatatableLayerState { return { layerId, columns: [], @@ -300,7 +300,7 @@ function getDataSourceAndSortedColumns( datasourceLayers: Record, layerId: string ) { - const layer = state.layers.find((l: LayerState) => l.layerId === layerId); + const layer = state.layers.find((l: DatatableLayerState) => l.layerId === layerId); if (!layer) { return undefined; } diff --git a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_component.tsx b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_component.tsx index 0a9e9799bbc3a..5c61f4ed39b0e 100644 --- a/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_component.tsx +++ b/x-pack/plugins/lens/public/editor_frame_service/embeddable/embeddable_component.tsx @@ -14,7 +14,7 @@ import type { IndexPatternPersistedState } from '../../indexpattern_datasource/t import type { XYState } from '../../xy_visualization/types'; import type { PieVisualizationState } from '../../pie_visualization/types'; import type { DatatableVisualizationState } from '../../datatable_visualization/visualization'; -import type { State as MetricState } from '../../metric_visualization/types'; +import type { MetricState } from '../../metric_visualization/types'; type LensAttributes = Omit< Document, diff --git a/x-pack/plugins/lens/public/index.ts b/x-pack/plugins/lens/public/index.ts index 539e20360620b..580dadc887008 100644 --- a/x-pack/plugins/lens/public/index.ts +++ b/x-pack/plugins/lens/public/index.ts @@ -10,11 +10,49 @@ export { EmbeddableComponentProps, TypedLensByValueInput, } from './editor_frame_service/embeddable/embeddable_component'; -export type { XYState } from './xy_visualization/types'; -export type { PieVisualizationState } from './pie_visualization/types'; -export type { DatatableVisualizationState } from './datatable_visualization/visualization'; -export type { State as MetricState } from './metric_visualization/types'; -export type { IndexPatternPersistedState } from './indexpattern_datasource/types'; +export type { + XYState, + AxesSettingsConfig, + XYLayerConfig, + LegendConfig, + SeriesType, + ValueLabelConfig, + YAxisMode, +} from './xy_visualization/types'; +export type { + PieVisualizationState, + PieLayerState, + SharedPieLayerState, +} from './pie_visualization/types'; +export type { + DatatableVisualizationState, + DatatableLayerState, +} from './datatable_visualization/visualization'; +export type { MetricState } from './metric_visualization/types'; +export type { + IndexPatternPersistedState, + PersistedIndexPatternLayer, + IndexPatternColumn, + OperationType, + IncompleteColumn, + FiltersIndexPatternColumn, + RangeIndexPatternColumn, + TermsIndexPatternColumn, + DateHistogramIndexPatternColumn, + MinIndexPatternColumn, + MaxIndexPatternColumn, + AvgIndexPatternColumn, + CardinalityIndexPatternColumn, + SumIndexPatternColumn, + MedianIndexPatternColumn, + PercentileIndexPatternColumn, + CountIndexPatternColumn, + LastValueIndexPatternColumn, + CumulativeSumIndexPatternColumn, + CounterRateIndexPatternColumn, + DerivativeIndexPatternColumn, + MovingAverageIndexPatternColumn, +} from './indexpattern_datasource/types'; export { LensPublicStart } from './plugin'; export const plugin = () => new LensPlugin(); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts index 0c0aa34bb40b3..bc5bbcf56b33f 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts @@ -71,6 +71,28 @@ export type FieldBasedIndexPatternColumn = Extract>; } +export type PersistedIndexPatternLayer = Omit; export interface IndexPatternPrivateState { currentIndexPatternId: string; layers: Record; diff --git a/x-pack/plugins/lens/public/metric_visualization/metric_suggestions.ts b/x-pack/plugins/lens/public/metric_visualization/metric_suggestions.ts index c95467ab04e11..0d378f87585ac 100644 --- a/x-pack/plugins/lens/public/metric_visualization/metric_suggestions.ts +++ b/x-pack/plugins/lens/public/metric_visualization/metric_suggestions.ts @@ -5,7 +5,7 @@ */ import { SuggestionRequest, VisualizationSuggestion, TableSuggestion } from '../types'; -import { State } from './types'; +import { MetricState } from './types'; import { LensIconChartMetric } from '../assets/chart_metric'; /** @@ -17,7 +17,7 @@ export function getSuggestions({ table, state, keptLayerIds, -}: SuggestionRequest): Array> { +}: SuggestionRequest): Array> { // We only render metric charts for single-row queries. We require a single, numeric column. if ( table.isMultiRow || @@ -37,7 +37,7 @@ export function getSuggestions({ return [getSuggestion(table)]; } -function getSuggestion(table: TableSuggestion): VisualizationSuggestion { +function getSuggestion(table: TableSuggestion): VisualizationSuggestion { const col = table.columns[0]; const title = table.label || col.operation.label; diff --git a/x-pack/plugins/lens/public/metric_visualization/types.ts b/x-pack/plugins/lens/public/metric_visualization/types.ts index c4a3fd094abe6..4ce4aa8aed13c 100644 --- a/x-pack/plugins/lens/public/metric_visualization/types.ts +++ b/x-pack/plugins/lens/public/metric_visualization/types.ts @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -export interface State { +export interface MetricState { layerId: string; accessor?: string; } -export interface MetricConfig extends State { +export interface MetricConfig extends MetricState { title: string; description: string; metricTitle: string; diff --git a/x-pack/plugins/lens/public/metric_visualization/visualization.test.ts b/x-pack/plugins/lens/public/metric_visualization/visualization.test.ts index 5ee33f9b4b3dd..7344a9d681b2c 100644 --- a/x-pack/plugins/lens/public/metric_visualization/visualization.test.ts +++ b/x-pack/plugins/lens/public/metric_visualization/visualization.test.ts @@ -5,14 +5,14 @@ */ import { metricVisualization } from './visualization'; -import { State } from './types'; +import { MetricState } from './types'; import { createMockDatasource, createMockFramePublicAPI } from '../editor_frame_service/mocks'; import { generateId } from '../id_generator'; import { DatasourcePublicAPI, FramePublicAPI } from '../types'; jest.mock('../id_generator'); -function exampleState(): State { +function exampleState(): MetricState { return { accessor: 'a', layerId: 'l1', diff --git a/x-pack/plugins/lens/public/metric_visualization/visualization.tsx b/x-pack/plugins/lens/public/metric_visualization/visualization.tsx index d8c475734e67e..99058a5dce876 100644 --- a/x-pack/plugins/lens/public/metric_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/metric_visualization/visualization.tsx @@ -9,10 +9,10 @@ import { Ast } from '@kbn/interpreter/target/common'; import { getSuggestions } from './metric_suggestions'; import { LensIconChartMetric } from '../assets/chart_metric'; import { Visualization, OperationMetadata, DatasourcePublicAPI } from '../types'; -import { State } from './types'; +import { MetricState } from './types'; const toExpression = ( - state: State, + state: MetricState, datasourceLayers: Record, attributes?: { mode?: 'reduced' | 'full'; title?: string; description?: string } ): Ast | null => { @@ -41,7 +41,7 @@ const toExpression = ( }; }; -export const metricVisualization: Visualization = { +export const metricVisualization: Visualization = { id: 'lnsMetric', visualizationTypes: [ diff --git a/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx b/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx index f1eb4ccdd9cf7..58f2a286c4ae2 100644 --- a/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/toolbar.tsx @@ -17,12 +17,15 @@ import { } from '@elastic/eui'; import { Position } from '@elastic/charts'; import { DEFAULT_PERCENT_DECIMALS } from './constants'; -import { PieVisualizationState, SharedLayerState } from './types'; +import { PieVisualizationState, SharedPieLayerState } from './types'; import { VisualizationDimensionEditorProps, VisualizationToolbarProps } from '../types'; import { ToolbarPopover, LegendSettingsPopover } from '../shared_components'; import { PalettePicker } from '../shared_components'; -const numberOptions: Array<{ value: SharedLayerState['numberDisplay']; inputDisplay: string }> = [ +const numberOptions: Array<{ + value: SharedPieLayerState['numberDisplay']; + inputDisplay: string; +}> = [ { value: 'hidden', inputDisplay: i18n.translate('xpack.lens.pieChart.hiddenNumbersLabel', { @@ -44,7 +47,7 @@ const numberOptions: Array<{ value: SharedLayerState['numberDisplay']; inputDisp ]; const categoryOptions: Array<{ - value: SharedLayerState['categoryDisplay']; + value: SharedPieLayerState['categoryDisplay']; inputDisplay: string; }> = [ { @@ -68,7 +71,7 @@ const categoryOptions: Array<{ ]; const categoryOptionsTreemap: Array<{ - value: SharedLayerState['categoryDisplay']; + value: SharedPieLayerState['categoryDisplay']; inputDisplay: string; }> = [ { @@ -86,7 +89,7 @@ const categoryOptionsTreemap: Array<{ ]; const legendOptions: Array<{ - value: SharedLayerState['legendDisplay']; + value: SharedPieLayerState['legendDisplay']; label: string; id: string; }> = [ diff --git a/x-pack/plugins/lens/public/pie_visualization/types.ts b/x-pack/plugins/lens/public/pie_visualization/types.ts index 792f4d7a0b971..603f61c4b8ef2 100644 --- a/x-pack/plugins/lens/public/pie_visualization/types.ts +++ b/x-pack/plugins/lens/public/pie_visualization/types.ts @@ -7,7 +7,7 @@ import { PaletteOutput } from 'src/plugins/charts/public'; import { LensMultiTable } from '../types'; -export interface SharedLayerState { +export interface SharedPieLayerState { groups: string[]; metric?: string; numberDisplay: 'hidden' | 'percent' | 'value'; @@ -18,17 +18,17 @@ export interface SharedLayerState { percentDecimals?: number; } -export type LayerState = SharedLayerState & { +export type PieLayerState = SharedPieLayerState & { layerId: string; }; export interface PieVisualizationState { shape: 'donut' | 'pie' | 'treemap'; - layers: LayerState[]; + layers: PieLayerState[]; palette?: PaletteOutput; } -export type PieExpressionArgs = SharedLayerState & { +export type PieExpressionArgs = SharedPieLayerState & { title?: string; description?: string; shape: 'pie' | 'donut' | 'treemap'; diff --git a/x-pack/plugins/lens/public/pie_visualization/visualization.tsx b/x-pack/plugins/lens/public/pie_visualization/visualization.tsx index 2d9a345b978ec..e3701363fa153 100644 --- a/x-pack/plugins/lens/public/pie_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/pie_visualization/visualization.tsx @@ -11,12 +11,12 @@ import { I18nProvider } from '@kbn/i18n/react'; import { PaletteRegistry } from 'src/plugins/charts/public'; import { Visualization, OperationMetadata, AccessorConfig } from '../types'; import { toExpression, toPreviewExpression } from './to_expression'; -import { LayerState, PieVisualizationState } from './types'; +import { PieLayerState, PieVisualizationState } from './types'; import { suggestions } from './suggestions'; import { CHART_NAMES, MAX_PIE_BUCKETS, MAX_TREEMAP_BUCKETS } from './constants'; import { DimensionEditor, PieToolbar } from './toolbar'; -function newLayerState(layerId: string): LayerState { +function newLayerState(layerId: string): PieLayerState { return { layerId, groups: [], diff --git a/x-pack/plugins/lens/public/xy_visualization/axes_configuration.ts b/x-pack/plugins/lens/public/xy_visualization/axes_configuration.ts index 3c312abf1fd91..256d9efe35ead 100644 --- a/x-pack/plugins/lens/public/xy_visualization/axes_configuration.ts +++ b/x-pack/plugins/lens/public/xy_visualization/axes_configuration.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LayerConfig } from './types'; +import { XYLayerConfig } from './types'; import { Datatable, SerializedFieldFormat } from '../../../../../src/plugins/expressions/public'; import { IFieldFormat } from '../../../../../src/plugins/data/public'; @@ -29,7 +29,7 @@ export function isFormatterCompatible( } export function getAxesConfiguration( - layers: LayerConfig[], + layers: XYLayerConfig[], shouldRotate: boolean, tables?: Record, formatFactory?: (mapping: SerializedFieldFormat) => IFieldFormat diff --git a/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.tsx b/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.tsx index 931e62ea1d13f..ef25e5d6acdb6 100644 --- a/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/axis_settings_popover.tsx @@ -15,7 +15,7 @@ import { IconType, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { LayerConfig, AxesSettingsConfig } from './types'; +import { XYLayerConfig, AxesSettingsConfig } from './types'; import { ToolbarPopover } from '../shared_components'; import { isHorizontalChart } from './state_helpers'; import { EuiIconAxisBottom } from '../assets/axis_bottom'; @@ -33,7 +33,7 @@ export interface AxisSettingsPopoverProps { /** * Contains the chart layers */ - layers?: LayerConfig[]; + layers?: XYLayerConfig[]; /** * Determines the axis title */ diff --git a/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts b/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts index 210101dc25c76..d83b3d59733f5 100644 --- a/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts +++ b/x-pack/plugins/lens/public/xy_visualization/color_assignment.ts @@ -9,7 +9,7 @@ import { PaletteOutput, PaletteRegistry } from 'src/plugins/charts/public'; import { Datatable } from 'src/plugins/expressions'; import { AccessorConfig, FormatFactory, FramePublicAPI } from '../types'; import { getColumnToLabelMap } from './state_helpers'; -import { LayerConfig } from './types'; +import { XYLayerConfig } from './types'; const isPrimitive = (value: unknown): boolean => value != null && typeof value !== 'object'; @@ -95,7 +95,7 @@ export function getColorAssignments( export function getAccessorColorConfig( colorAssignments: ColorAssignments, frame: FramePublicAPI, - layer: LayerConfig, + layer: XYLayerConfig, paletteService: PaletteRegistry ): AccessorConfig[] { const layerContainsSplits = Boolean(layer.splitAccessor); diff --git a/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts b/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts index bd479062e2a06..17682256a8a16 100644 --- a/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts +++ b/x-pack/plugins/lens/public/xy_visualization/state_helpers.ts @@ -6,7 +6,7 @@ import { EuiIconType } from '@elastic/eui/src/components/icon/icon'; import { FramePublicAPI, DatasourcePublicAPI } from '../types'; -import { SeriesType, visualizationTypes, LayerConfig, YConfig, ValidLayer } from './types'; +import { SeriesType, visualizationTypes, XYLayerConfig, YConfig, ValidLayer } from './types'; export function isHorizontalSeries(seriesType: SeriesType) { return ( @@ -30,7 +30,7 @@ export function getIconForSeries(type: SeriesType): EuiIconType { return (definition.icon as EuiIconType) || 'empty'; } -export const getSeriesColor = (layer: LayerConfig, accessor: string) => { +export const getSeriesColor = (layer: XYLayerConfig, accessor: string) => { if (layer.splitAccessor) { return null; } @@ -39,7 +39,7 @@ export const getSeriesColor = (layer: LayerConfig, accessor: string) => { ); }; -export const getColumnToLabelMap = (layer: LayerConfig, datasource: DatasourcePublicAPI) => { +export const getColumnToLabelMap = (layer: XYLayerConfig, datasource: DatasourcePublicAPI) => { const columnToLabel: Record = {}; layer.accessors.concat(layer.splitAccessor ? [layer.splitAccessor] : []).forEach((accessor) => { diff --git a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts index fda7c93af03a5..02f28b8801b94 100644 --- a/x-pack/plugins/lens/public/xy_visualization/to_expression.ts +++ b/x-pack/plugins/lens/public/xy_visualization/to_expression.ts @@ -7,11 +7,11 @@ import { Ast } from '@kbn/interpreter/common'; import { ScaleType } from '@elastic/charts'; import { PaletteRegistry } from 'src/plugins/charts/public'; -import { State, ValidLayer, LayerConfig } from './types'; +import { State, ValidLayer, XYLayerConfig } from './types'; import { OperationMetadata, DatasourcePublicAPI } from '../types'; import { getColumnToLabelMap } from './state_helpers'; -export const getSortedAccessors = (datasource: DatasourcePublicAPI, layer: LayerConfig) => { +export const getSortedAccessors = (datasource: DatasourcePublicAPI, layer: XYLayerConfig) => { const originalOrder = datasource .getTableSpec() .map(({ columnId }: { columnId: string }) => columnId) diff --git a/x-pack/plugins/lens/public/xy_visualization/types.ts b/x-pack/plugins/lens/public/xy_visualization/types.ts index 88e95f2ca3271..759674f8b013b 100644 --- a/x-pack/plugins/lens/public/xy_visualization/types.ts +++ b/x-pack/plugins/lens/public/xy_visualization/types.ts @@ -372,7 +372,7 @@ export interface YConfig { color?: string; } -export interface LayerConfig { +export interface XYLayerConfig { hide?: boolean; layerId: string; xAccessor?: string; @@ -383,11 +383,11 @@ export interface LayerConfig { palette?: PaletteOutput; } -export interface ValidLayer extends LayerConfig { - xAccessor: NonNullable; +export interface ValidLayer extends XYLayerConfig { + xAccessor: NonNullable; } -export type LayerArgs = LayerConfig & { +export type LayerArgs = XYLayerConfig & { columnToLabel?: string; // Actually a JSON key-value pair yScaleType: 'time' | 'linear' | 'log' | 'sqrt'; xScaleType: 'time' | 'linear' | 'ordinal'; @@ -420,7 +420,7 @@ export interface XYState { legend: LegendConfig; valueLabels?: ValueLabelConfig; fittingFunction?: FittingFunction; - layers: LayerConfig[]; + layers: XYLayerConfig[]; xTitle?: string; yTitle?: string; yRightTitle?: string; diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts index cab1a0185333f..df0b071f18934 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.test.ts @@ -7,7 +7,7 @@ import { getXyVisualization } from './visualization'; import { Position } from '@elastic/charts'; import { Operation } from '../types'; -import { State, SeriesType, LayerConfig } from './types'; +import { State, SeriesType, XYLayerConfig } from './types'; import { createMockDatasource, createMockFramePublicAPI } from '../editor_frame_service/mocks'; import { LensIconChartBar } from '../assets/chart_bar'; import { chartPluginMock } from '../../../../../src/plugins/charts/public/mocks'; @@ -422,7 +422,7 @@ describe('xy_visualization', () => { }); describe('color assignment', () => { - function callConfig(layerConfigOverride: Partial) { + function callConfig(layerConfigOverride: Partial) { const baseState = exampleState(); const options = xyVisualization.getConfiguration({ state: { @@ -441,16 +441,16 @@ describe('xy_visualization', () => { return options; } - function callConfigForYConfigs(layerConfigOverride: Partial) { + function callConfigForYConfigs(layerConfigOverride: Partial) { return callConfig(layerConfigOverride).find(({ groupId }) => groupId === 'y'); } - function callConfigForBreakdownConfigs(layerConfigOverride: Partial) { + function callConfigForBreakdownConfigs(layerConfigOverride: Partial) { return callConfig(layerConfigOverride).find(({ groupId }) => groupId === 'breakdown'); } function callConfigAndFindYConfig( - layerConfigOverride: Partial, + layerConfigOverride: Partial, assertionAccessor: string ) { const accessorConfig = callConfigForYConfigs(layerConfigOverride)?.accessors.find( diff --git a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx index e05871fd35a5e..30df138cde9d6 100644 --- a/x-pack/plugins/lens/public/xy_visualization/visualization.tsx +++ b/x-pack/plugins/lens/public/xy_visualization/visualization.tsx @@ -15,7 +15,7 @@ import { DataPublicPluginStart } from 'src/plugins/data/public'; import { getSuggestions } from './xy_suggestions'; import { LayerContextMenu, XyToolbar, DimensionEditor } from './xy_config_panel'; import { Visualization, OperationMetadata, VisualizationType, AccessorConfig } from '../types'; -import { State, SeriesType, visualizationTypes, LayerConfig } from './types'; +import { State, SeriesType, visualizationTypes, XYLayerConfig } from './types'; import { isHorizontalChart } from './state_helpers'; import { toExpression, toPreviewExpression, getSortedAccessors } from './to_expression'; import { LensIconChartBarStacked } from '../assets/chart_bar_stacked'; @@ -341,9 +341,9 @@ export const getXyVisualization = ({ getErrorMessages(state, frame) { // Data error handling below here - const hasNoAccessors = ({ accessors }: LayerConfig) => + const hasNoAccessors = ({ accessors }: XYLayerConfig) => accessors == null || accessors.length === 0; - const hasNoSplitAccessor = ({ splitAccessor, seriesType }: LayerConfig) => + const hasNoSplitAccessor = ({ splitAccessor, seriesType }: XYLayerConfig) => seriesType.includes('percentage') && splitAccessor == null; const errors: Array<{ @@ -354,14 +354,14 @@ export const getXyVisualization = ({ // check if the layers in the state are compatible with this type of chart if (state && state.layers.length > 1) { // Order is important here: Y Axis is fundamental to exist to make it valid - const checks: Array<[string, (layer: LayerConfig) => boolean]> = [ + const checks: Array<[string, (layer: XYLayerConfig) => boolean]> = [ ['Y', hasNoAccessors], ['Break down', hasNoSplitAccessor], ]; // filter out those layers with no accessors at all const filteredLayers = state.layers.filter( - ({ accessors, xAccessor, splitAccessor }: LayerConfig) => + ({ accessors, xAccessor, splitAccessor }: XYLayerConfig) => accessors.length > 0 || xAccessor != null || splitAccessor != null ); for (const [dimension, criteria] of checks) { @@ -382,7 +382,7 @@ export const getXyVisualization = ({ const layers = state.layers; - const filteredLayers = layers.filter(({ accessors }: LayerConfig) => accessors.length > 0); + const filteredLayers = layers.filter(({ accessors }: XYLayerConfig) => accessors.length > 0); const accessorsWithArrayValues = []; for (const layer of filteredLayers) { const { layerId, accessors } = layer; @@ -409,8 +409,8 @@ export const getXyVisualization = ({ function validateLayersForDimension( dimension: string, - layers: LayerConfig[], - missingCriteria: (layer: LayerConfig) => boolean + layers: XYLayerConfig[], + missingCriteria: (layer: XYLayerConfig) => boolean ): | { valid: true } | { @@ -480,7 +480,7 @@ function getMessageIdsForDimension(dimension: string, layers: number[], isHorizo return { shortMessage: '', longMessage: '' }; } -function newLayerState(seriesType: SeriesType, layerId: string): LayerConfig { +function newLayerState(seriesType: SeriesType, layerId: string): XYLayerConfig { return { layerId, seriesType, diff --git a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts index a308a0c293029..c181df9394cd9 100644 --- a/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts +++ b/x-pack/plugins/lens/public/xy_visualization/xy_suggestions.ts @@ -15,7 +15,7 @@ import { TableSuggestion, TableChangeType, } from '../types'; -import { State, SeriesType, XYState, visualizationTypes, LayerConfig } from './types'; +import { State, SeriesType, XYState, visualizationTypes, XYLayerConfig } from './types'; import { getIconForSeries } from './state_helpers'; const columnSortOrder = { @@ -485,7 +485,7 @@ function buildSuggestion({ splitBy = xValue; xValue = undefined; } - const existingLayer: LayerConfig | {} = getExistingLayer(currentState, layerId) || {}; + const existingLayer: XYLayerConfig | {} = getExistingLayer(currentState, layerId) || {}; const accessors = yValues.map((col) => col.columnId); const newLayer = { ...existingLayer, diff --git a/x-pack/plugins/lens/readme.md b/x-pack/plugins/lens/readme.md index 9fa6ad8ee30af..a180473ed31a4 100644 --- a/x-pack/plugins/lens/readme.md +++ b/x-pack/plugins/lens/readme.md @@ -1,5 +1,12 @@ # Lens +Visualization editor allowing to quickly and easily configure compelling visualizations to use on dashboards and canvas workpads. + +## Embedding + +It's possible to embed Lens visualizations in other apps using `EmbeddableComponent` and `navigateToPrefilledEditor` +exposed via contract. For more information check out the example in `x-pack/examples/embedded_lens_example`. + ## Testing Run all tests from the `x-pack` root directory diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts index 8ac014c820ace..73d3bcb1eea0d 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.test.ts @@ -278,6 +278,17 @@ describe('ESGeoGridSource', () => { "rootdir/api/maps/mvt/getGridTile;?x={x}&y={y}&z={z}&geometryFieldName=bar&index=undefined&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:())),'1':('0':size,'1':0),'2':('0':filter,'1':!((geo_bounding_box:(bar:(bottom_right:!(180,-82.67628),top_left:!(-180,82.67628)))))),'3':('0':query),'4':('0':index,'1':(fields:())),'5':('0':query,'1':(language:KQL,query:'',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':aggs,'1':(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:bar))),geotile_grid:(bounds:!n,field:bar,precision:!n,shard_size:65535,size:65535))))))&requestType=heatmap&geoFieldType=geo_point" ); }); + + it('should include searchSourceId in urlTemplateWithMeta', async () => { + const urlTemplateWithMeta = await geogridSource.getUrlTemplateWithMeta({ + ...vectorSourceRequestMeta, + searchSessionId: '1', + }); + + expect(urlTemplateWithMeta.urlTemplate).toBe( + "rootdir/api/maps/mvt/getGridTile;?x={x}&y={y}&z={z}&geometryFieldName=bar&index=undefined&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:())),'1':('0':size,'1':0),'2':('0':filter,'1':!((geo_bounding_box:(bar:(bottom_right:!(180,-82.67628),top_left:!(-180,82.67628)))))),'3':('0':query),'4':('0':index,'1':(fields:())),'5':('0':query,'1':(language:KQL,query:'',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':aggs,'1':(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:bar))),geotile_grid:(bounds:!n,field:bar,precision:!n,shard_size:65535,size:65535))))))&requestType=heatmap&geoFieldType=geo_point&searchSessionId=1" + ); + }); }); describe('Gold+ usage', () => { diff --git a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx index 24b7e0dec519c..8e82a60a802c7 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_geo_grid_source/es_geo_grid_source.tsx @@ -443,12 +443,22 @@ export class ESGeoGridSource extends AbstractESAggSource implements ITiledSingle ); const geoField = await this._getGeoField(); - const urlTemplate = `${mvtUrlServicePath}?x={x}&y={y}&z={z}&geometryFieldName=${this._descriptor.geoField}&index=${indexPattern.title}&requestBody=${risonDsl}&requestType=${this._descriptor.requestType}&geoFieldType=${geoField.type}`; + const urlTemplate = `${mvtUrlServicePath}\ +?x={x}\ +&y={y}\ +&z={z}\ +&geometryFieldName=${this._descriptor.geoField}\ +&index=${indexPattern.title}\ +&requestBody=${risonDsl}\ +&requestType=${this._descriptor.requestType}\ +&geoFieldType=${geoField.type}`; return { layerName: this.getLayerName(), minSourceZoom: this.getMinZoom(), maxSourceZoom: this.getMaxZoom(), - urlTemplate, + urlTemplate: searchFilters.searchSessionId + ? urlTemplate + `&searchSessionId=${searchFilters.searchSessionId}` + : urlTemplate, }; } diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts index 3f8b9d3e28e1a..61b3ee348b46c 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.test.ts @@ -116,6 +116,20 @@ describe('ESSearchSource', () => { `rootdir/api/maps/mvt/getTile;?x={x}&y={y}&z={z}&geometryFieldName=bar&index=foobar-title-*&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:(),title:'foobar-title-*')),'1':('0':size,'1':1000),'2':('0':filter,'1':!()),'3':('0':query),'4':('0':index,'1':(fields:(),title:'foobar-title-*')),'5':('0':query,'1':(language:KQL,query:'tooltipField: foobar',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':fieldsFromSource,'1':!(tooltipField,styleField)),'7':('0':source,'1':!(tooltipField,styleField))))&geoFieldType=geo_shape` ); }); + + it('should include searchSourceId in urlTemplateWithMeta', async () => { + const esSearchSource = new ESSearchSource({ + geoField: geoFieldName, + indexPatternId: 'ipId', + }); + const urlTemplateWithMeta = await esSearchSource.getUrlTemplateWithMeta({ + ...searchFilters, + searchSessionId: '1', + }); + expect(urlTemplateWithMeta.urlTemplate).toBe( + `rootdir/api/maps/mvt/getTile;?x={x}&y={y}&z={z}&geometryFieldName=bar&index=foobar-title-*&requestBody=(foobar:ES_DSL_PLACEHOLDER,params:('0':('0':index,'1':(fields:(),title:'foobar-title-*')),'1':('0':size,'1':1000),'2':('0':filter,'1':!()),'3':('0':query),'4':('0':index,'1':(fields:(),title:'foobar-title-*')),'5':('0':query,'1':(language:KQL,query:'tooltipField: foobar',queryLastTriggeredAt:'2019-04-25T20:53:22.331Z')),'6':('0':fieldsFromSource,'1':!(tooltipField,styleField)),'7':('0':source,'1':!(tooltipField,styleField))))&geoFieldType=geo_shape&searchSessionId=1` + ); + }); }); }); diff --git a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx index b70a433f2c729..8c5c63931db26 100644 --- a/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx +++ b/x-pack/plugins/maps/public/classes/sources/es_search_source/es_search_source.tsx @@ -729,12 +729,21 @@ export class ESSearchSource extends AbstractESSource implements ITiledSingleLaye const geoField = await this._getGeoField(); - const urlTemplate = `${mvtUrlServicePath}?x={x}&y={y}&z={z}&geometryFieldName=${this._descriptor.geoField}&index=${indexPattern.title}&requestBody=${risonDsl}&geoFieldType=${geoField.type}`; + const urlTemplate = `${mvtUrlServicePath}\ +?x={x}\ +&y={y}\ +&z={z}\ +&geometryFieldName=${this._descriptor.geoField}\ +&index=${indexPattern.title}\ +&requestBody=${risonDsl}\ +&geoFieldType=${geoField.type}`; return { layerName: this.getLayerName(), minSourceZoom: this.getMinZoom(), maxSourceZoom: this.getMaxZoom(), - urlTemplate, + urlTemplate: searchFilters.searchSessionId + ? urlTemplate + `&searchSessionId=${searchFilters.searchSessionId}` + : urlTemplate, }; } } diff --git a/x-pack/plugins/maps/public/render_app.tsx b/x-pack/plugins/maps/public/render_app.tsx index b62ac6518a374..bf122c13e9418 100644 --- a/x-pack/plugins/maps/public/render_app.tsx +++ b/x-pack/plugins/maps/public/render_app.tsx @@ -73,13 +73,13 @@ export async function renderApp({ ...withNotifyOnErrors(getToasts()), }); + const stateTransfer = getEmbeddableService().getStateTransfer(); + setAppChrome(); function renderMapApp(routeProps: RouteComponentProps<{ savedMapId?: string }>) { - const stateTransfer = getEmbeddableService()?.getStateTransfer(); - const { embeddableId, originatingApp, valueInput } = - stateTransfer?.getIncomingEditorState() || {}; + stateTransfer.getIncomingEditorState() || {}; let mapEmbeddableInput; if (routeProps.match.params.savedMapId) { @@ -119,7 +119,7 @@ export async function renderApp({ const newPath = hash.substr(1); return ; } else if (pathname === '/' || pathname === '') { - return ; + return ; } else { return ; } diff --git a/x-pack/plugins/maps/public/routes/list_page/load_list_and_render.tsx b/x-pack/plugins/maps/public/routes/list_page/load_list_and_render.tsx index 087fd82300ce3..03990fd477ee3 100644 --- a/x-pack/plugins/maps/public/routes/list_page/load_list_and_render.tsx +++ b/x-pack/plugins/maps/public/routes/list_page/load_list_and_render.tsx @@ -10,8 +10,9 @@ import { Redirect } from 'react-router-dom'; import { getSavedObjectsClient, getToasts } from '../../kibana_services'; import { MapsListView } from './maps_list_view'; import { MAP_SAVED_OBJECT_TYPE } from '../../../common/constants'; +import { EmbeddableStateTransfer } from '../../../../../../src/plugins/embeddable/public'; -export class LoadListAndRender extends React.Component { +export class LoadListAndRender extends React.Component<{ stateTransfer: EmbeddableStateTransfer }> { _isMounted: boolean = false; state = { mapsLoaded: false, @@ -20,6 +21,7 @@ export class LoadListAndRender extends React.Component { componentDidMount() { this._isMounted = true; + this.props.stateTransfer.clearEditorState(); this._loadMapsList(); } diff --git a/x-pack/plugins/maps/server/mvt/__fixtures__/json/0_0_0_gridagg.json b/x-pack/plugins/maps/server/mvt/__fixtures__/json/0_0_0_gridagg.json deleted file mode 100644 index 0945dc57fa512..0000000000000 --- a/x-pack/plugins/maps/server/mvt/__fixtures__/json/0_0_0_gridagg.json +++ /dev/null @@ -1 +0,0 @@ -{"took":2,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":{"value":10000,"relation":"gte"},"max_score":null,"hits":[]},"aggregations":{"gridSplit":{"buckets":[{"key":"7/37/48","doc_count":42637,"avg_of_TOTAL_AV":{"value":5398920.390458991},"gridCentroid":{"location":{"lat":40.77936432658204,"lon":-73.96795676049909},"count":42637}}]}}} diff --git a/x-pack/plugins/maps/server/mvt/__fixtures__/json/0_0_0_search.json b/x-pack/plugins/maps/server/mvt/__fixtures__/json/0_0_0_search.json deleted file mode 100644 index 0fc99ffd811f7..0000000000000 --- a/x-pack/plugins/maps/server/mvt/__fixtures__/json/0_0_0_search.json +++ /dev/null @@ -1 +0,0 @@ -{"took":0,"timed_out":false,"_shards":{"total":1,"successful":1,"skipped":0,"failed":0},"hits":{"total":{"value":1,"relation":"eq"},"max_score":0,"hits":[{"_index":"poly","_id":"G7PRMXQBgyyZ-h5iYibj","_score":0,"_source":{"coordinates":{"coordinates":[[[-106.171875,36.59788913307022],[-50.625,-22.91792293614603],[4.921875,42.8115217450979],[-33.046875,63.54855223203644],[-66.796875,63.860035895395306],[-106.171875,36.59788913307022]]],"type":"polygon"}}}]}} diff --git a/x-pack/plugins/maps/server/mvt/__fixtures__/tile_es_responses.ts b/x-pack/plugins/maps/server/mvt/__fixtures__/tile_es_responses.ts deleted file mode 100644 index 9fbaba21e71d5..0000000000000 --- a/x-pack/plugins/maps/server/mvt/__fixtures__/tile_es_responses.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 * as path from 'path'; -import * as fs from 'fs'; - -export const TILE_SEARCHES = { - '0.0.0': { - countResponse: { - count: 1, - _shards: { - total: 1, - successful: 1, - skipped: 0, - failed: 0, - }, - }, - searchResponse: loadJson('./json/0_0_0_search.json'), - }, -}; - -export const TILE_GRIDAGGS = { - '0.0.0': { - gridAggResponse: loadJson('./json/0_0_0_gridagg.json'), - }, -}; - -function loadJson(filePath: string) { - const absolutePath = path.resolve(__dirname, filePath); - const rawContents = fs.readFileSync(absolutePath); - return JSON.parse((rawContents as unknown) as string); -} diff --git a/x-pack/plugins/maps/server/mvt/get_tile.test.ts b/x-pack/plugins/maps/server/mvt/get_tile.test.ts deleted file mode 100644 index c959d03c6ef87..0000000000000 --- a/x-pack/plugins/maps/server/mvt/get_tile.test.ts +++ /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 { getGridTile, getTile } from './get_tile'; -import { TILE_GRIDAGGS, TILE_SEARCHES } from './__fixtures__/tile_es_responses'; -import { Logger } from 'src/core/server'; -import { - ES_GEO_FIELD_TYPE, - KBN_IS_CENTROID_FEATURE, - MVT_SOURCE_LAYER_NAME, - RENDER_AS, -} from '../../common/constants'; - -// @ts-expect-error -import { VectorTile, VectorTileLayer } from '@mapbox/vector-tile'; -// @ts-expect-error -import Protobuf from 'pbf'; - -interface ITileLayerJsonExpectation { - version: number; - name: string; - extent: number; - features: Array<{ - id: string | number | undefined; - type: number; - properties: object; - extent: number; - pointArrays: object; - }>; -} - -describe('getTile', () => { - const mockCallElasticsearch = jest.fn(); - - const requestBody = { - _source: { excludes: [] }, - docvalue_fields: [], - query: { bool: { filter: [{ match_all: {} }], must: [], must_not: [], should: [] } }, - script_fields: {}, - size: 10000, - stored_fields: ['*'], - }; - const geometryFieldName = 'coordinates'; - - beforeEach(() => { - mockCallElasticsearch.mockReset(); - }); - - test('0.0.0 - under limit', async () => { - mockCallElasticsearch.mockImplementation((type) => { - if (type === 'count') { - return TILE_SEARCHES['0.0.0'].countResponse; - } else if (type === 'search') { - return TILE_SEARCHES['0.0.0'].searchResponse; - } else { - throw new Error(`${type} not recognized`); - } - }); - - const pbfTile = await getTile({ - x: 0, - y: 0, - z: 0, - index: 'world_countries', - requestBody, - geometryFieldName, - logger: ({ - info: () => {}, - } as unknown) as Logger, - callElasticsearch: mockCallElasticsearch, - geoFieldType: ES_GEO_FIELD_TYPE.GEO_SHAPE, - }); - - const jsonTile = new VectorTile(new Protobuf(pbfTile)); - compareJsonTiles(jsonTile, { - version: 2, - name: 'source_layer', - extent: 4096, - features: [ - { - id: undefined, - type: 3, - properties: { - __kbn__feature_id__: 'poly:G7PRMXQBgyyZ-h5iYibj:0', - _id: 'G7PRMXQBgyyZ-h5iYibj', - _index: 'poly', - }, - extent: 4096, - pointArrays: [ - [ - { x: 840, y: 1600 }, - { x: 1288, y: 1096 }, - { x: 1672, y: 1104 }, - { x: 2104, y: 1508 }, - { x: 1472, y: 2316 }, - { x: 840, y: 1600 }, - ], - ], - }, - { - id: undefined, - type: 1, - properties: { - __kbn__feature_id__: 'poly:G7PRMXQBgyyZ-h5iYibj:0', - _id: 'G7PRMXQBgyyZ-h5iYibj', - _index: 'poly', - [KBN_IS_CENTROID_FEATURE]: true, - }, - extent: 4096, - pointArrays: [[{ x: 1470, y: 1702 }]], - }, - ], - }); - }); -}); - -describe('getGridTile', () => { - const mockCallElasticsearch = jest.fn(); - - const geometryFieldName = 'geometry'; - - // For mock-purposes only. The ES-call response is mocked in 0_0_0_gridagg.json file - const requestBody = { - _source: { excludes: [] }, - aggs: { - gridSplit: { - aggs: { - // eslint-disable-next-line @typescript-eslint/naming-convention - avg_of_TOTAL_AV: { avg: { field: 'TOTAL_AV' } }, - gridCentroid: { geo_centroid: { field: geometryFieldName } }, - }, - geotile_grid: { - bounds: null, - field: geometryFieldName, - precision: null, - shard_size: 65535, - size: 65535, - }, - }, - }, - docvalue_fields: [], - query: { - bool: { - filter: [], - }, - }, - script_fields: {}, - size: 0, - stored_fields: ['*'], - }; - - beforeEach(() => { - mockCallElasticsearch.mockReset(); - mockCallElasticsearch.mockImplementation((type) => { - return TILE_GRIDAGGS['0.0.0'].gridAggResponse; - }); - }); - - const defaultParams = { - x: 0, - y: 0, - z: 0, - index: 'manhattan', - requestBody, - geometryFieldName, - logger: ({ - info: () => {}, - } as unknown) as Logger, - callElasticsearch: mockCallElasticsearch, - requestType: RENDER_AS.POINT, - geoFieldType: ES_GEO_FIELD_TYPE.GEO_POINT, - }; - - test('0.0.0 tile (clusters)', async () => { - const pbfTile = await getGridTile(defaultParams); - const jsonTile = new VectorTile(new Protobuf(pbfTile)); - compareJsonTiles(jsonTile, { - version: 2, - name: 'source_layer', - extent: 4096, - features: [ - { - id: undefined, - type: 1, - properties: { - ['avg_of_TOTAL_AV']: 5398920.390458991, - doc_count: 42637, - }, - extent: 4096, - pointArrays: [[{ x: 1206, y: 1539 }]], - }, - ], - }); - }); - - test('0.0.0 tile (grids)', async () => { - const pbfTile = await getGridTile({ ...defaultParams, requestType: RENDER_AS.GRID }); - const jsonTile = new VectorTile(new Protobuf(pbfTile)); - compareJsonTiles(jsonTile, { - version: 2, - name: 'source_layer', - extent: 4096, - features: [ - { - id: undefined, - type: 3, - properties: { - ['avg_of_TOTAL_AV']: 5398920.390458991, - doc_count: 42637, - }, - extent: 4096, - pointArrays: [ - [ - { x: 1216, y: 1536 }, - { x: 1216, y: 1568 }, - { x: 1184, y: 1568 }, - { x: 1184, y: 1536 }, - { x: 1216, y: 1536 }, - ], - ], - }, - { - id: undefined, - type: 1, - properties: { - ['avg_of_TOTAL_AV']: 5398920.390458991, - doc_count: 42637, - [KBN_IS_CENTROID_FEATURE]: true, - }, - extent: 4096, - pointArrays: [[{ x: 1200, y: 1552 }]], - }, - ], - }); - }); -}); - -/** - * Verifies JSON-representation of tile-contents - * @param actualTileJson - * @param expectedLayer - */ -function compareJsonTiles(actualTileJson: VectorTile, expectedLayer: ITileLayerJsonExpectation) { - const actualLayer: VectorTileLayer = actualTileJson.layers[MVT_SOURCE_LAYER_NAME]; - expect(actualLayer.version).toEqual(expectedLayer.version); - expect(actualLayer.extent).toEqual(expectedLayer.extent); - expect(actualLayer.name).toEqual(expectedLayer.name); - expect(actualLayer.length).toEqual(expectedLayer.features.length); - - expectedLayer.features.forEach((expectedFeature, index) => { - const actualFeature = actualLayer.feature(index); - expect(actualFeature.type).toEqual(expectedFeature.type); - expect(actualFeature.extent).toEqual(expectedFeature.extent); - expect(actualFeature.id).toEqual(expectedFeature.id); - expect(actualFeature.properties).toEqual(expectedFeature.properties); - expect(actualFeature.loadGeometry()).toEqual(expectedFeature.pointArrays); - }); -} diff --git a/x-pack/plugins/maps/server/mvt/get_tile.ts b/x-pack/plugins/maps/server/mvt/get_tile.ts index ee45849042715..30a22bfb6164a 100644 --- a/x-pack/plugins/maps/server/mvt/get_tile.ts +++ b/x-pack/plugins/maps/server/mvt/get_tile.ts @@ -9,6 +9,7 @@ import geojsonvt from 'geojson-vt'; // @ts-expect-error import vtpbf from 'vt-pbf'; import { Logger } from 'src/core/server'; +import type { DataRequestHandlerContext } from 'src/plugins/data/server'; import { Feature, FeatureCollection, Polygon } from 'geojson'; import { ES_GEO_FIELD_TYPE, @@ -28,7 +29,7 @@ import { getCentroidFeatures } from '../../common/get_centroid_features'; export async function getGridTile({ logger, - callElasticsearch, + context, index, geometryFieldName, x, @@ -37,17 +38,19 @@ export async function getGridTile({ requestBody = {}, requestType = RENDER_AS.POINT, geoFieldType = ES_GEO_FIELD_TYPE.GEO_POINT, + searchSessionId, }: { x: number; y: number; z: number; geometryFieldName: string; index: string; - callElasticsearch: (type: string, ...args: any[]) => Promise; + context: DataRequestHandlerContext; logger: Logger; requestBody: any; requestType: RENDER_AS; geoFieldType: ES_GEO_FIELD_TYPE; + searchSessionId?: string; }): Promise { const esBbox: ESBounds = tileToESBbox(x, y, z); try { @@ -79,13 +82,20 @@ export async function getGridTile({ ); requestBody.aggs[GEOTILE_GRID_AGG_NAME].geotile_grid.bounds = esBbox; - const esGeotileGridQuery = { - index, - body: requestBody, - }; - - const gridAggResult = await callElasticsearch('search', esGeotileGridQuery); - const features: Feature[] = convertRegularRespToGeoJson(gridAggResult, requestType); + const response = await context + .search!.search( + { + params: { + index, + body: requestBody, + }, + }, + { + sessionId: searchSessionId, + } + ) + .toPromise(); + const features: Feature[] = convertRegularRespToGeoJson(response.rawResponse, requestType); const featureCollection: FeatureCollection = { features, type: 'FeatureCollection', @@ -100,7 +110,7 @@ export async function getGridTile({ export async function getTile({ logger, - callElasticsearch, + context, index, geometryFieldName, x, @@ -108,112 +118,121 @@ export async function getTile({ z, requestBody = {}, geoFieldType, + searchSessionId, }: { x: number; y: number; z: number; geometryFieldName: string; index: string; - callElasticsearch: (type: string, ...args: any[]) => Promise; + context: DataRequestHandlerContext; logger: Logger; requestBody: any; geoFieldType: ES_GEO_FIELD_TYPE; + searchSessionId?: string; }): Promise { - const geojsonBbox = tileToGeoJsonPolygon(x, y, z); - - let resultFeatures: Feature[]; + let features: Feature[]; try { - let result; - try { - const geoShapeFilter = { - geo_shape: { - [geometryFieldName]: { - shape: geojsonBbox, - relation: 'INTERSECTS', - }, + requestBody.query.bool.filter.push({ + geo_shape: { + [geometryFieldName]: { + shape: tileToGeoJsonPolygon(x, y, z), + relation: 'INTERSECTS', }, - }; - requestBody.query.bool.filter.push(geoShapeFilter); + }, + }); - const esSearchQuery = { - index, - body: requestBody, - }; + const searchOptions = { + sessionId: searchSessionId, + }; - const esCountQuery = { - index, - body: { - query: requestBody.query, + const countResponse = await context + .search!.search( + { + params: { + index, + body: { + size: 0, + query: requestBody.query, + }, + }, }, - }; - - const countResult = await callElasticsearch('count', esCountQuery); + searchOptions + ) + .toPromise(); - // @ts-expect-error - if (countResult.count > requestBody.size) { - // Generate "too many features"-bounds - const bboxAggName = 'data_bounds'; - const bboxQuery = { - index, - body: { - size: 0, - query: requestBody.query, - aggs: { - [bboxAggName]: { - geo_bounds: { - field: geometryFieldName, + if (countResponse.rawResponse.hits.total > requestBody.size) { + // Generate "too many features"-bounds + const bboxResponse = await context + .search!.search( + { + params: { + index, + body: { + size: 0, + query: requestBody.query, + aggs: { + data_bounds: { + geo_bounds: { + field: geometryFieldName, + }, + }, }, }, }, }, - }; - - const bboxResult = await callElasticsearch('search', bboxQuery); - - // @ts-expect-error - const bboxForData = esBboxToGeoJsonPolygon(bboxResult.aggregations[bboxAggName].bounds); + searchOptions + ) + .toPromise(); - resultFeatures = [ + features = [ + { + type: 'Feature', + properties: { + [KBN_TOO_MANY_FEATURES_PROPERTY]: true, + }, + geometry: esBboxToGeoJsonPolygon( + bboxResponse.rawResponse.aggregations.data_bounds.bounds + ), + }, + ]; + } else { + const documentsResponse = await context + .search!.search( { - type: 'Feature', - properties: { - [KBN_TOO_MANY_FEATURES_PROPERTY]: true, + params: { + index, + body: requestBody, }, - geometry: bboxForData, }, - ]; - } else { - result = await callElasticsearch('search', esSearchQuery); + searchOptions + ) + .toPromise(); - // Todo: pass in epochMillies-fields - const featureCollection = hitsToGeoJson( - // @ts-expect-error - result.hits.hits, - (hit: Record) => { - return flattenHit(geometryFieldName, hit); - }, - geometryFieldName, - geoFieldType, - [] - ); + // Todo: pass in epochMillies-fields + const featureCollection = hitsToGeoJson( + documentsResponse.rawResponse.hits.hits, + (hit: Record) => { + return flattenHit(geometryFieldName, hit); + }, + geometryFieldName, + geoFieldType, + [] + ); - resultFeatures = featureCollection.features; + features = featureCollection.features; - // Correct system-fields. - for (let i = 0; i < resultFeatures.length; i++) { - const props = resultFeatures[i].properties; - if (props !== null) { - props[FEATURE_ID_PROPERTY_NAME] = resultFeatures[i].id; - } + // Correct system-fields. + for (let i = 0; i < features.length; i++) { + const props = features[i].properties; + if (props !== null) { + props[FEATURE_ID_PROPERTY_NAME] = features[i].id; } } - } catch (e) { - logger.warn(e.message); - throw e; } const featureCollection: FeatureCollection = { - features: resultFeatures, + features, type: 'FeatureCollection', }; diff --git a/x-pack/plugins/maps/server/mvt/mvt_routes.ts b/x-pack/plugins/maps/server/mvt/mvt_routes.ts index fc298f73b04a5..cf56000d61451 100644 --- a/x-pack/plugins/maps/server/mvt/mvt_routes.ts +++ b/x-pack/plugins/maps/server/mvt/mvt_routes.ts @@ -6,13 +6,9 @@ import rison from 'rison-node'; import { schema } from '@kbn/config-schema'; -import { - KibanaRequest, - KibanaResponseFactory, - Logger, - RequestHandlerContext, -} from 'src/core/server'; +import { KibanaRequest, KibanaResponseFactory, Logger } from 'src/core/server'; import { IRouter } from 'src/core/server'; +import type { DataRequestHandlerContext } from 'src/plugins/data/server'; import { MVT_GETTILE_API_PATH, API_ROOT_PATH, @@ -24,7 +20,13 @@ import { getGridTile, getTile } from './get_tile'; const CACHE_TIMEOUT = 0; // Todo. determine good value. Unsure about full-implications (e.g. wrt. time-based data). -export function initMVTRoutes({ router, logger }: { logger: Logger; router: IRouter }) { +export function initMVTRoutes({ + router, + logger, +}: { + router: IRouter; + logger: Logger; +}) { router.get( { path: `${API_ROOT_PATH}/${MVT_GETTILE_API_PATH}`, @@ -37,11 +39,12 @@ export function initMVTRoutes({ router, logger }: { logger: Logger; router: IRou requestBody: schema.string(), index: schema.string(), geoFieldType: schema.string(), + searchSessionId: schema.maybe(schema.string()), }), }, }, async ( - context: RequestHandlerContext, + context: DataRequestHandlerContext, request: KibanaRequest, unknown>, response: KibanaResponseFactory ) => { @@ -50,7 +53,7 @@ export function initMVTRoutes({ router, logger }: { logger: Logger; router: IRou const tile = await getTile({ logger, - callElasticsearch: makeCallElasticsearch(context), + context, geometryFieldName: query.geometryFieldName as string, x: query.x as number, y: query.y as number, @@ -58,6 +61,7 @@ export function initMVTRoutes({ router, logger }: { logger: Logger; router: IRou index: query.index as string, requestBody: requestBodyDSL as any, geoFieldType: query.geoFieldType as ES_GEO_FIELD_TYPE, + searchSessionId: query.searchSessionId, }); return sendResponse(response, tile); @@ -77,11 +81,12 @@ export function initMVTRoutes({ router, logger }: { logger: Logger; router: IRou index: schema.string(), requestType: schema.string(), geoFieldType: schema.string(), + searchSessionId: schema.maybe(schema.string()), }), }, }, async ( - context: RequestHandlerContext, + context: DataRequestHandlerContext, request: KibanaRequest, unknown>, response: KibanaResponseFactory ) => { @@ -90,7 +95,7 @@ export function initMVTRoutes({ router, logger }: { logger: Logger; router: IRou const tile = await getGridTile({ logger, - callElasticsearch: makeCallElasticsearch(context), + context, geometryFieldName: query.geometryFieldName as string, x: query.x as number, y: query.y as number, @@ -99,6 +104,7 @@ export function initMVTRoutes({ router, logger }: { logger: Logger; router: IRou requestBody: requestBodyDSL as any, requestType: query.requestType as RENDER_AS, geoFieldType: query.geoFieldType as ES_GEO_FIELD_TYPE, + searchSessionId: query.searchSessionId, }); return sendResponse(response, tile); @@ -125,9 +131,3 @@ function sendResponse(response: KibanaResponseFactory, tile: any) { }); } } - -function makeCallElasticsearch(context: RequestHandlerContext) { - return async (type: string, ...args: any[]): Promise => { - return context.core.elasticsearch.legacy.client.callAsCurrentUser(type, ...args); - }; -} diff --git a/x-pack/plugins/maps_file_upload/public/util/indexing_service.js b/x-pack/plugins/maps_file_upload/public/util/indexing_service.js index 28cdb602455b5..14d02ce881cda 100644 --- a/x-pack/plugins/maps_file_upload/public/util/indexing_service.js +++ b/x-pack/plugins/maps_file_upload/public/util/indexing_service.js @@ -119,7 +119,7 @@ async function writeToIndex(indexingDetails) { const { appName, index, data, settings, mappings, ingestPipeline } = indexingDetails; return await httpService({ - url: `/api/fileupload/import`, + url: `/api/maps/fileupload/import`, method: 'POST', ...(query ? { query } : {}), data: { diff --git a/x-pack/plugins/maps_file_upload/server/routes/file_upload.js b/x-pack/plugins/maps_file_upload/server/routes/file_upload.js index 3935d4ca5fe8e..0323f23a51df5 100644 --- a/x-pack/plugins/maps_file_upload/server/routes/file_upload.js +++ b/x-pack/plugins/maps_file_upload/server/routes/file_upload.js @@ -9,7 +9,7 @@ import { updateTelemetry } from '../telemetry/telemetry'; import { MAX_BYTES } from '../../common/constants/file_import'; import { schema } from '@kbn/config-schema'; -export const IMPORT_ROUTE = '/api/fileupload/import'; +export const IMPORT_ROUTE = '/api/maps/fileupload/import'; export const querySchema = schema.maybe( schema.object({ diff --git a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts index 9b41193ea540d..699eac07aca94 100644 --- a/x-pack/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts +++ b/x-pack/plugins/ml/common/types/anomaly_detection_jobs/summary_job.ts @@ -32,6 +32,7 @@ export interface MlSummaryJob { deleting?: boolean; latestTimestampSortValue?: number; earliestStartTimestampMs?: number; + awaitingNodeAssignment: boolean; } export interface AuditMessage { diff --git a/x-pack/plugins/ml/common/types/file_datavisualizer.ts b/x-pack/plugins/ml/common/types/file_datavisualizer.ts index b1967cfe83f3c..8ba30111c4c8c 100644 --- a/x-pack/plugins/ml/common/types/file_datavisualizer.ts +++ b/x-pack/plugins/ml/common/types/file_datavisualizer.ts @@ -68,52 +68,3 @@ export interface FindFileStructureResponse { timestamp_field?: string; should_trim_fields?: boolean; } - -export interface ImportResponse { - success: boolean; - id: string; - index?: string; - pipelineId?: string; - docCount: number; - failures: ImportFailure[]; - error?: any; - ingestError?: boolean; -} - -export interface ImportFailure { - item: number; - reason: string; - doc: ImportDoc; -} - -export interface Doc { - message: string; -} - -export type ImportDoc = Doc | string; - -export interface Settings { - pipeline?: string; - index: string; - body: any[]; - [key: string]: any; -} - -export interface Mappings { - _meta?: { - created_by: string; - }; - properties: { - [key: string]: any; - }; -} - -export interface IngestPipelineWrapper { - id: string; - pipeline: IngestPipeline; -} - -export interface IngestPipeline { - description: string; - processors: any[]; -} diff --git a/x-pack/plugins/ml/common/types/ml_server_info.ts b/x-pack/plugins/ml/common/types/ml_server_info.ts index 66142f53add3a..caa8147067505 100644 --- a/x-pack/plugins/ml/common/types/ml_server_info.ts +++ b/x-pack/plugins/ml/common/types/ml_server_info.ts @@ -31,3 +31,8 @@ export interface MlInfoResponse { upgrade_mode: boolean; cloudId?: string; } + +export interface MlNodeCount { + count: number; + lazyNodeCount: number; +} diff --git a/x-pack/plugins/ml/kibana.json b/x-pack/plugins/ml/kibana.json index 1c47512e0b3de..ede6b8abbd09c 100644 --- a/x-pack/plugins/ml/kibana.json +++ b/x-pack/plugins/ml/kibana.json @@ -10,8 +10,8 @@ "data", "cloud", "features", + "fileUpload", "licensing", - "usageCollection", "share", "embeddable", "uiActions", diff --git a/x-pack/plugins/ml/public/application/components/jobs_awaiting_node_warning/index.ts b/x-pack/plugins/ml/public/application/components/jobs_awaiting_node_warning/index.ts new file mode 100644 index 0000000000000..a43d9640bf194 --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/jobs_awaiting_node_warning/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 { JobsAwaitingNodeWarning } from './jobs_awaiting_node_warning'; +export { NewJobAwaitingNodeWarning } from './new_job_awaiting_node'; diff --git a/x-pack/plugins/ml/public/application/components/jobs_awaiting_node_warning/jobs_awaiting_node_warning.tsx b/x-pack/plugins/ml/public/application/components/jobs_awaiting_node_warning/jobs_awaiting_node_warning.tsx new file mode 100644 index 0000000000000..8eef232dc6729 --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/jobs_awaiting_node_warning/jobs_awaiting_node_warning.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 React, { Fragment, FC } from 'react'; + +import { EuiCallOut, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { isCloud } from '../../services/ml_server_info'; + +interface Props { + jobCount: number; +} + +export const JobsAwaitingNodeWarning: FC = ({ jobCount }) => { + if (isCloud() === false || jobCount === 0) { + return null; + } + + return ( + + + } + color="primary" + iconType="iInCircle" + > +
+ +
+
+ +
+ ); +}; diff --git a/x-pack/plugins/ml/public/application/components/jobs_awaiting_node_warning/new_job_awaiting_node.tsx b/x-pack/plugins/ml/public/application/components/jobs_awaiting_node_warning/new_job_awaiting_node.tsx new file mode 100644 index 0000000000000..dde1b449858d3 --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/jobs_awaiting_node_warning/new_job_awaiting_node.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, { Fragment, FC } from 'react'; + +import { EuiCallOut, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { JobType } from '../../../../common/types/saved_objects'; + +interface Props { + jobType: JobType; +} + +export const NewJobAwaitingNodeWarning: FC = () => { + return ( + + + } + color="primary" + iconType="iInCircle" + > +
+ +
+
+ +
+ ); +}; diff --git a/x-pack/plugins/ml/public/application/components/node_available_warning/node_available_warning.tsx b/x-pack/plugins/ml/public/application/components/node_available_warning/node_available_warning.tsx index f9f9ee347fd7f..f44c1801d6f80 100644 --- a/x-pack/plugins/ml/public/application/components/node_available_warning/node_available_warning.tsx +++ b/x-pack/plugins/ml/public/application/components/node_available_warning/node_available_warning.tsx @@ -41,7 +41,7 @@ export const NodeAvailableWarning: FC = () => { defaultMessage="You will not be able to create or run jobs." />
- {isCloud && id !== null && ( + {isCloud() && id !== null && (
= ({ jobId, jobType, showProgress }) => const [currentProgress, setCurrentProgress] = useState( undefined ); + const [showJobAssignWarning, setShowJobAssignWarning] = useState(false); const { services: { notifications }, @@ -58,6 +60,10 @@ export const CreateStepFooter: FC = ({ jobId, jobType, showProgress }) => ? analyticsStats.data_frame_analytics[0] : undefined; + setShowJobAssignWarning( + jobStats?.state === DATA_FRAME_TASK_STATE.STARTING && jobStats?.node === undefined + ); + if (jobStats !== undefined) { const progressStats = getDataFrameAnalyticsProgressPhase(jobStats); @@ -106,25 +112,28 @@ export const CreateStepFooter: FC = ({ jobId, jobType, showProgress }) => }, [initialized]); return ( - - - {showProgress && ( - - )} - - - - - - - - {jobFinished === true && ( + <> + {showJobAssignWarning && } + + + {showProgress && ( + + )} + + + + - + - )} - - - + {jobFinished === true && ( + + + + )} + + + + ); }; diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx index f4cd64aa8c497..a2469dd9d826a 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/analytics_list/analytics_list.tsx @@ -36,6 +36,7 @@ import { AnalyticsEmptyPrompt } from './empty_prompt'; import { useTableSettings } from './use_table_settings'; import { RefreshAnalyticsListButton } from '../refresh_analytics_list_button'; import { ListingPageUrlState } from '../../../../../../../common/types/common'; +import { JobsAwaitingNodeWarning } from '../../../../../components/jobs_awaiting_node_warning'; const filters: EuiSearchBarProps['filters'] = [ { @@ -114,6 +115,7 @@ export const DataFrameAnalyticsList: FC = ({ ); const [expandedRowItemIds, setExpandedRowItemIds] = useState([]); const [errorMessage, setErrorMessage] = useState(undefined); + const [jobsAwaitingNodeCount, setJobsAwaitingNodeCount] = useState(0); const disabled = !checkPermission('canCreateDataFrameAnalytics') || @@ -124,6 +126,7 @@ export const DataFrameAnalyticsList: FC = ({ setAnalyticsStats, setErrorMessage, setIsInitialized, + setJobsAwaitingNodeCount, blockRefresh, isManagementTable ); @@ -261,6 +264,7 @@ export const DataFrameAnalyticsList: FC = ({
{modals} {!isManagementTable && } + {!isManagementTable && stats} {isManagementTable && managementStats} diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.ts index 2d251d94e9ca7..d99c2d4e18830 100644 --- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.ts +++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.ts @@ -26,6 +26,7 @@ import { } from '../../components/analytics_list/common'; import { AnalyticStatsBarStats } from '../../../../../components/stats_bar'; import { DataFrameAnalysisConfigType } from '../../../../../../../common/types/data_frame_analytics'; +import { DATA_FRAME_TASK_STATE } from '../../../../../../../common/constants/data_frame_analytics'; export const isGetDataFrameAnalyticsStatsResponseOk = ( arg: any @@ -106,6 +107,7 @@ export const getAnalyticsFactory = ( React.SetStateAction >, setIsInitialized: React.Dispatch>, + setJobsAwaitingNodeCount: React.Dispatch>, blockRefresh: boolean, isManagementTable: boolean ): GetAnalytics => { @@ -134,6 +136,8 @@ export const getAnalyticsFactory = ( ? getAnalyticsJobsStats(analyticsStats) : undefined; + let jobsAwaitingNodeCount = 0; + const tableRows = analyticsConfigs.data_frame_analytics.reduce( (reducedtableRows, config) => { const stats = isGetDataFrameAnalyticsStatsResponseOk(analyticsStats) @@ -146,6 +150,10 @@ export const getAnalyticsFactory = ( return reducedtableRows; } + if (stats.state === DATA_FRAME_TASK_STATE.STARTING && stats.node === undefined) { + jobsAwaitingNodeCount++; + } + // Table with expandable rows requires `id` on the outer most level reducedtableRows.push({ checkpointing: {}, @@ -166,6 +174,7 @@ export const getAnalyticsFactory = ( setAnalyticsStats(analyticsStatsResult); setErrorMessage(undefined); setIsInitialized(true); + setJobsAwaitingNodeCount(jobsAwaitingNodeCount); refreshAnalyticsList$.next(REFRESH_ANALYTICS_LIST_STATE.IDLE); } catch (e) { // An error is followed immediately by setting the state to idle. diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/combined_fields/utils.ts b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/combined_fields/utils.ts index 1cc513e778b2f..25d5373b6dc7c 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/combined_fields/utils.ts +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/combined_fields/utils.ts @@ -8,11 +8,8 @@ import { i18n } from '@kbn/i18n'; import { cloneDeep } from 'lodash'; import uuid from 'uuid/v4'; import { CombinedField } from './types'; -import { - FindFileStructureResponse, - IngestPipeline, - Mappings, -} from '../../../../../../common/types/file_datavisualizer'; +import { FindFileStructureResponse } from '../../../../../../common/types/file_datavisualizer'; +import { IngestPipeline, Mappings } from '../../../../../../../file_upload/common'; const COMMON_LAT_NAMES = ['latitude', 'lat']; const COMMON_LON_NAMES = ['longitude', 'long', 'lon']; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_error_callouts.tsx b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_error_callouts.tsx index d869676e48827..0c853493293ca 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_error_callouts.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/file_error_callouts.tsx @@ -11,7 +11,7 @@ import { EuiCallOut, EuiSpacer, EuiButtonEmpty, EuiHorizontalRule } from '@elast import numeral from '@elastic/numeral'; import { ErrorResponse } from '../../../../../../common/types/errors'; -import { FILE_SIZE_DISPLAY_FORMAT } from '../../../../../../common/constants/file_datavisualizer'; +import { FILE_SIZE_DISPLAY_FORMAT } from '../../../../../../../file_upload/common'; interface FileTooLargeProps { fileSize: number; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer.ts b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer.ts index 718587ad15ad5..ab0e83846661f 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer.ts +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/importer.ts @@ -15,7 +15,7 @@ import { Mappings, Settings, IngestPipeline, -} from '../../../../../../../common/types/file_datavisualizer'; +} from '../../../../../../../../file_upload/common'; const CHUNK_SIZE = 5000; const MAX_CHUNK_CHAR_COUNT = 1000000; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/message_importer.ts b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/message_importer.ts index 65be24d9e7be4..a74249ea758a8 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/message_importer.ts +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/import_view/importer/message_importer.ts @@ -5,10 +5,8 @@ */ import { Importer, ImportConfig, CreateDocsResponse } from './importer'; -import { - Doc, - FindFileStructureResponse, -} from '../../../../../../../common/types/file_datavisualizer'; +import { FindFileStructureResponse } from '../../../../../../../common/types/file_datavisualizer'; +import { Doc } from '../../../../../../../../file_upload/common'; export class MessageImporter extends Importer { private _excludeLinesRegex: RegExp | null; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts index 781f400180b10..ce15fb9a03fca 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/utils/utils.ts @@ -14,7 +14,7 @@ import { MAX_FILE_SIZE_BYTES, ABSOLUTE_MAX_FILE_SIZE_BYTES, FILE_SIZE_DISPLAY_FORMAT, -} from '../../../../../../common/constants/file_datavisualizer'; +} from '../../../../../../../file_upload/common'; import { getUiSettings } from '../../../../util/dependency_cache'; import { FILE_DATA_VISUALIZER_MAX_FILE_SIZE } from '../../../../../../common/constants/settings'; diff --git a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js index ab8fd3d8ab44e..21542bf43c6aa 100644 --- a/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js +++ b/x-pack/plugins/ml/public/application/jobs/jobs_list/components/jobs_list_view/jobs_list_view.js @@ -32,6 +32,7 @@ import { MultiJobActions } from '../multi_job_actions'; import { NewJobButton } from '../new_job_button'; import { JobStatsBar } from '../jobs_stats_bar'; import { NodeAvailableWarning } from '../../../../components/node_available_warning'; +import { JobsAwaitingNodeWarning } from '../../../../components/jobs_awaiting_node_warning'; import { SavedObjectsWarning } from '../../../../components/saved_objects_warning'; import { DatePickerWrapper } from '../../../../components/navigation_menu/date_picker_wrapper'; import { UpgradeWarning } from '../../../../components/upgrade'; @@ -56,6 +57,7 @@ export class JobsListView extends Component { itemIdToExpandedRowMap: {}, filterClauses: [], deletingJobIds: [], + jobsAwaitingNodeCount: 0, }; this.spacesEnabled = props.spacesEnabled ?? false; @@ -272,6 +274,7 @@ export class JobsListView extends Component { spaces = allSpaces['anomaly-detector']; } + let jobsAwaitingNodeCount = 0; const jobs = await ml.jobs.jobsSummary(expandedJobsIds); const fullJobsList = {}; const jobsSummaryList = jobs.map((job) => { @@ -287,11 +290,21 @@ export class JobsListView extends Component { spaces[job.id] !== undefined ? spaces[job.id] : []; + + if (job.awaitingNodeAssignment === true) { + jobsAwaitingNodeCount++; + } return job; }); const filteredJobsSummaryList = filterJobs(jobsSummaryList, this.state.filterClauses); this.setState( - { jobsSummaryList, filteredJobsSummaryList, fullJobsList, loading: false }, + { + jobsSummaryList, + filteredJobsSummaryList, + fullJobsList, + loading: false, + jobsAwaitingNodeCount, + }, () => { this.refreshSelectedJobs(); } @@ -407,7 +420,7 @@ export class JobsListView extends Component { } renderJobsListComponents() { - const { isRefreshing, loading, jobsSummaryList } = this.state; + const { isRefreshing, loading, jobsSummaryList, jobsAwaitingNodeCount } = this.state; const jobIds = jobsSummaryList.map((j) => j.id); return ( @@ -440,6 +453,7 @@ export class JobsListView extends Component { + diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_runner/job_runner.ts b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_runner/job_runner.ts index 9afc6e5bfca93..bd5ae8e02b0b5 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/common/job_runner/job_runner.ts +++ b/x-pack/plugins/ml/public/application/jobs/new_job/common/job_runner/job_runner.ts @@ -11,12 +11,14 @@ import { JobCreator } from '../job_creator'; import { DatafeedId, JobId } from '../../../../../../common/types/anomaly_detection_jobs'; import { DATAFEED_STATE } from '../../../../../../common/constants/states'; -const REFRESH_INTERVAL_MS = 100; +const REFRESH_INTERVAL_MS = 250; +const NODE_ASSIGNMENT_CHECK_REFRESH_INTERVAL_MS = 2000; const TARGET_PROGRESS_DELTA = 2; const REFRESH_RATE_ADJUSTMENT_DELAY_MS = 2000; type Progress = number; export type ProgressSubscriber = (progress: number) => void; +export type JobAssignmentSubscriber = (assigned: boolean) => void; export class JobRunner { private _jobId: JobId; @@ -35,6 +37,8 @@ export class JobRunner { private _datafeedStartTime: number = 0; private _performRefreshRateAdjustment: boolean = false; + private _jobAssignedToNode: boolean = false; + private _jobAssignedToNode$: BehaviorSubject; constructor(jobCreator: JobCreator) { this._jobId = jobCreator.jobId; @@ -45,6 +49,7 @@ export class JobRunner { this._stopRefreshPoll = jobCreator.stopAllRefreshPolls; this._progress$ = new BehaviorSubject(this._percentageComplete); + this._jobAssignedToNode$ = new BehaviorSubject(this._jobAssignedToNode); this._subscribers = jobCreator.subscribers; } @@ -62,7 +67,9 @@ export class JobRunner { private async openJob(): Promise { try { - await mlJobService.openJob(this._jobId); + const { node }: { node?: string } = await mlJobService.openJob(this._jobId); + this._jobAssignedToNode = node !== undefined && node.length > 0; + this._jobAssignedToNode$.next(this._jobAssignedToNode); } catch (error) { throw error; } @@ -94,7 +101,7 @@ export class JobRunner { this._datafeedState = DATAFEED_STATE.STARTED; this._percentageComplete = 0; - const check = async () => { + const checkProgress = async () => { const { isRunning, progress: prog, isJobClosed } = await this.getProgress(); // if the progress has reached 100% but the job is still running, @@ -109,7 +116,9 @@ export class JobRunner { if ((isRunning === true || isJobClosed === false) && this._stopRefreshPoll.stop === false) { setTimeout(async () => { - await check(); + if (this._stopRefreshPoll.stop === false) { + await checkProgress(); + } }, this._refreshInterval); } else { // job has finished running, set progress to 100% @@ -121,11 +130,30 @@ export class JobRunner { subscriptions.forEach((s) => s.unsubscribe()); } }; + + const checkJobIsAssigned = async () => { + this._jobAssignedToNode = await this._isJobAssigned(); + this._jobAssignedToNode$.next(this._jobAssignedToNode); + if (this._jobAssignedToNode === true) { + await checkProgress(); + } else { + setTimeout(async () => { + if (this._stopRefreshPoll.stop === false) { + await checkJobIsAssigned(); + } + }, NODE_ASSIGNMENT_CHECK_REFRESH_INTERVAL_MS); + } + }; // wait for the first check to run and then return success. // all subsequent checks will update the observable if (pollProgress === true) { - await check(); + if (this._jobAssignedToNode === true) { + await checkProgress(); + } else { + await checkJobIsAssigned(); + } } + return started; } catch (error) { throw error; @@ -159,6 +187,11 @@ export class JobRunner { } } + private async _isJobAssigned(): Promise { + const { jobs } = await ml.getJobStats({ jobId: this._jobId }); + return jobs.length > 0 && jobs[0].node !== undefined; + } + public async startDatafeed() { return await this._startDatafeed(this._start, this._end, true); } @@ -185,4 +218,12 @@ export class JobRunner { const { isRunning } = await this.getProgress(); return isRunning; } + + public isJobAssignedToNode() { + return this._jobAssignedToNode; + } + + public subscribeToJobAssignment(func: JobAssignmentSubscriber) { + return this._jobAssignedToNode$.subscribe(func); + } } diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/summary.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/summary.tsx index 021039c06e320..ea805541297a9 100644 --- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/summary.tsx +++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/summary.tsx @@ -5,6 +5,7 @@ */ import React, { Fragment, FC, useContext, useState, useEffect } from 'react'; +import { Subscription } from 'rxjs'; import { EuiButton, EuiButtonEmpty, @@ -29,6 +30,7 @@ import { DetectorChart } from './components/detector_chart'; import { JobProgress } from './components/job_progress'; import { PostSaveOptions } from './components/post_save_options'; import { StartDatafeedSwitch } from './components/start_datafeed_switch'; +import { NewJobAwaitingNodeWarning } from '../../../../../components/jobs_awaiting_node_warning'; import { toastNotificationServiceProvider } from '../../../../../services/toast_notification_service'; import { convertToAdvancedJob, @@ -55,6 +57,7 @@ export const SummaryStep: FC = ({ setCurrentStep, isCurrentStep }) => const [isValid, setIsValid] = useState(jobValidator.validationSummary.basic); const [jobRunner, setJobRunner] = useState(null); const [startDatafeed, setStartDatafeed] = useState(true); + const [showJobAssignWarning, setShowJobAssignWarning] = useState(false); const isAdvanced = isAdvancedJobCreator(jobCreator); const jsonEditorMode = isAdvanced ? EDITOR_MODE.EDITABLE : EDITOR_MODE.READONLY; @@ -63,6 +66,20 @@ export const SummaryStep: FC = ({ setCurrentStep, isCurrentStep }) => jobCreator.subscribeToProgress(setProgress); }, []); + useEffect(() => { + let s: Subscription | null = null; + if (jobRunner !== null) { + s = jobRunner.subscribeToJobAssignment((assigned: boolean) => + setShowJobAssignWarning(!assigned) + ); + } + return () => { + if (s !== null) { + s?.unsubscribe(); + } + }; + }, [jobRunner]); + async function start() { setCreatingJob(true); if (isAdvanced) { @@ -155,6 +172,7 @@ export const SummaryStep: FC = ({ setCurrentStep, isCurrentStep }) => )} + {showJobAssignWarning && } {progress < 100 && ( diff --git a/x-pack/plugins/ml/public/application/ml_nodes_check/check_ml_nodes.ts b/x-pack/plugins/ml/public/application/ml_nodes_check/check_ml_nodes.ts index d0cfd16d7562f..d403197b68bac 100644 --- a/x-pack/plugins/ml/public/application/ml_nodes_check/check_ml_nodes.ts +++ b/x-pack/plugins/ml/public/application/ml_nodes_check/check_ml_nodes.ts @@ -5,14 +5,16 @@ */ import { ml } from '../services/ml_api_service'; +import { MlNodeCount } from '../../../common/types/ml_server_info'; let mlNodeCount: number = 0; +let lazyMlNodeCount: number = 0; let userHasPermissionToViewMlNodeCount: boolean = false; export async function checkMlNodesAvailable(redirectToJobsManagementPage: () => Promise) { try { - const nodes = await getMlNodeCount(); - if (nodes.count !== undefined && nodes.count > 0) { + const { count, lazyNodeCount } = await getMlNodeCount(); + if (count > 0 || lazyNodeCount > 0) { Promise.resolve(); } else { throw Error('Cannot load count of ML nodes'); @@ -25,23 +27,24 @@ export async function checkMlNodesAvailable(redirectToJobsManagementPage: () => } } -export async function getMlNodeCount() { +export async function getMlNodeCount(): Promise { try { const nodes = await ml.mlNodeCount(); mlNodeCount = nodes.count; + lazyMlNodeCount = nodes.lazyNodeCount; userHasPermissionToViewMlNodeCount = true; - return Promise.resolve(nodes); + return nodes; } catch (error) { mlNodeCount = 0; if (error.statusCode === 403) { userHasPermissionToViewMlNodeCount = false; } - return Promise.resolve({ count: 0 }); + return { count: 0, lazyNodeCount: 0 }; } } export function mlNodesAvailable() { - return mlNodeCount !== 0; + return mlNodeCount !== 0 || lazyMlNodeCount !== 0; } export function permissionToViewMlNodeCount() { diff --git a/x-pack/plugins/ml/public/application/overview/components/analytics_panel/analytics_panel.tsx b/x-pack/plugins/ml/public/application/overview/components/analytics_panel/analytics_panel.tsx index 5748e4bba95c9..9b8bac24845db 100644 --- a/x-pack/plugins/ml/public/application/overview/components/analytics_panel/analytics_panel.tsx +++ b/x-pack/plugins/ml/public/application/overview/components/analytics_panel/analytics_panel.tsx @@ -28,8 +28,9 @@ import { ML_PAGES } from '../../../../../common/constants/ml_url_generator'; interface Props { jobCreationDisabled: boolean; + setLazyJobCount: React.Dispatch>; } -export const AnalyticsPanel: FC = ({ jobCreationDisabled }) => { +export const AnalyticsPanel: FC = ({ jobCreationDisabled, setLazyJobCount }) => { const [analytics, setAnalytics] = useState([]); const [analyticsStats, setAnalyticsStats] = useState( undefined @@ -52,6 +53,7 @@ export const AnalyticsPanel: FC = ({ jobCreationDisabled }) => { setAnalyticsStats, setErrorMessage, setIsInitialized, + setLazyJobCount, false, false ); diff --git a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx index 1cb6bab7fd768..dbf0fcc1874fc 100644 --- a/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx +++ b/x-pack/plugins/ml/public/application/overview/components/anomaly_detection_panel/anomaly_detection_panel.tsx @@ -51,9 +51,10 @@ function getDefaultAnomalyScores(groups: Group[]): MaxScoresByGroup { interface Props { jobCreationDisabled: boolean; + setLazyJobCount: React.Dispatch>; } -export const AnomalyDetectionPanel: FC = ({ jobCreationDisabled }) => { +export const AnomalyDetectionPanel: FC = ({ jobCreationDisabled, setLazyJobCount }) => { const { services: { notifications }, } = useMlKibana(); @@ -84,10 +85,14 @@ export const AnomalyDetectionPanel: FC = ({ jobCreationDisabled }) => { const loadJobs = async () => { setIsLoading(true); + let lazyJobCount = 0; try { const jobsResult: MlSummaryJobs = await ml.jobs.jobsSummary([]); const jobsSummaryList = jobsResult.map((job: MlSummaryJob) => { job.latestTimestampSortValue = job.latestTimestampMs || 0; + if (job.awaitingNodeAssignment) { + lazyJobCount++; + } return job; }); const { groups: jobsGroups, count } = getGroupsFromJobs(jobsSummaryList); @@ -100,6 +105,7 @@ export const AnomalyDetectionPanel: FC = ({ jobCreationDisabled }) => { setGroups(jobsGroups); setJobsList(jobsWithTimerange); loadMaxAnomalyScores(jobsGroups); + setLazyJobCount(lazyJobCount); } catch (e) { setErrorMessage(e.message !== undefined ? e.message : JSON.stringify(e)); setIsLoading(false); diff --git a/x-pack/plugins/ml/public/application/overview/components/content.tsx b/x-pack/plugins/ml/public/application/overview/components/content.tsx index 8d2e4865ee6f4..18e7d7c00986f 100644 --- a/x-pack/plugins/ml/public/application/overview/components/content.tsx +++ b/x-pack/plugins/ml/public/application/overview/components/content.tsx @@ -12,20 +12,30 @@ import { AnalyticsPanel } from './analytics_panel'; interface Props { createAnomalyDetectionJobDisabled: boolean; createAnalyticsJobDisabled: boolean; + setAdLazyJobCount: React.Dispatch>; + setDfaLazyJobCount: React.Dispatch>; } // Fetch jobs and determine what to show export const OverviewContent: FC = ({ createAnomalyDetectionJobDisabled, createAnalyticsJobDisabled, + setAdLazyJobCount, + setDfaLazyJobCount, }) => ( - + - + diff --git a/x-pack/plugins/ml/public/application/overview/overview_page.tsx b/x-pack/plugins/ml/public/application/overview/overview_page.tsx index bf293ee38ea1a..5feb414f1ffdf 100644 --- a/x-pack/plugins/ml/public/application/overview/overview_page.tsx +++ b/x-pack/plugins/ml/public/application/overview/overview_page.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, FC } from 'react'; +import React, { Fragment, FC, useState } from 'react'; import { EuiFlexGroup, EuiPage, EuiPageBody } from '@elastic/eui'; import { checkPermission } from '../capabilities/check_capabilities'; import { mlNodesAvailable } from '../ml_nodes_check/check_ml_nodes'; @@ -12,6 +12,7 @@ import { NavigationMenu } from '../components/navigation_menu'; import { OverviewSideBar } from './components/sidebar'; import { OverviewContent } from './components/content'; import { NodeAvailableWarning } from '../components/node_available_warning'; +import { JobsAwaitingNodeWarning } from '../components/jobs_awaiting_node_warning'; import { SavedObjectsWarning } from '../components/saved_objects_warning'; import { UpgradeWarning } from '../components/upgrade'; import { HelpMenu } from '../components/help_menu'; @@ -27,12 +28,17 @@ export const OverviewPage: FC = () => { services: { docLinks }, } = useMlKibana(); const helpLink = docLinks.links.ml.guide; + + const [adLazyJobCount, setAdLazyJobCount] = useState(0); + const [dfaLazyJobCount, setDfaLazyJobCount] = useState(0); + return ( + @@ -41,6 +47,8 @@ export const OverviewPage: FC = () => { diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/data_frame_analytics.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/data_frame_analytics.ts index 98a8e4c9cbf2d..f8771e9e19d8c 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/data_frame_analytics.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/data_frame_analytics.ts @@ -34,18 +34,19 @@ export type GetDataFrameAnalyticsStatsResponse = | GetDataFrameAnalyticsStatsResponseOk | GetDataFrameAnalyticsStatsResponseError; -interface GetDataFrameAnalyticsResponse { +export interface GetDataFrameAnalyticsResponse { count: number; data_frame_analytics: DataFrameAnalyticsConfig[]; } -interface DeleteDataFrameAnalyticsWithIndexResponse { +export interface DeleteDataFrameAnalyticsWithIndexResponse { acknowledged: boolean; analyticsJobDeleted: DeleteDataFrameAnalyticsWithIndexStatus; destIndexDeleted: DeleteDataFrameAnalyticsWithIndexStatus; destIndexPatternDeleted: DeleteDataFrameAnalyticsWithIndexStatus; } -interface JobsExistsResponse { + +export interface JobsExistsResponse { results: { [jobId: string]: boolean; }; diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/datavisualizer.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/datavisualizer.ts index 20332546d9cde..27d9b78725bef 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/datavisualizer.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/datavisualizer.ts @@ -7,7 +7,7 @@ import { http } from '../http_service'; import { basePath } from './index'; -import { ImportResponse } from '../../../../common/types/file_datavisualizer'; +import { ImportResponse } from '../../../../../file_upload/common'; export const fileDatavisualizer = { analyzeFile(file: string, params: Record = {}) { @@ -45,7 +45,7 @@ export const fileDatavisualizer = { }); return http({ - path: `${basePath()}/file_data_visualizer/import`, + path: `/api/file_upload/import`, method: 'POST', query, body, diff --git a/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts b/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts index b67b5015dbd6c..ea09801d8a236 100644 --- a/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts +++ b/x-pack/plugins/ml/public/application/services/ml_api_service/index.ts @@ -15,12 +15,17 @@ import { resultsApiProvider } from './results'; import { jobsApiProvider } from './jobs'; import { fileDatavisualizer } from './datavisualizer'; import { savedObjectsApiProvider } from './saved_objects'; -import { MlServerDefaults, MlServerLimits } from '../../../../common/types/ml_server_info'; +import { + MlServerDefaults, + MlServerLimits, + MlNodeCount, +} from '../../../../common/types/ml_server_info'; import { MlCapabilitiesResponse } from '../../../../common/types/capabilities'; import { Calendar, CalendarId, UpdateCalendar } from '../../../../common/types/calendars'; import { Job, + JobStats, Datafeed, CombinedJob, Detector, @@ -116,14 +121,14 @@ export function mlApiServicesProvider(httpService: HttpService) { return { getJobs(obj?: { jobId?: string }) { const jobId = obj && obj.jobId ? `/${obj.jobId}` : ''; - return httpService.http({ + return httpService.http<{ jobs: Job[]; count: number }>({ path: `${basePath()}/anomaly_detectors${jobId}`, }); }, getJobStats(obj: { jobId?: string }) { const jobId = obj && obj.jobId ? `/${obj.jobId}` : ''; - return httpService.http({ + return httpService.http<{ jobs: JobStats[]; count: number }>({ path: `${basePath()}/anomaly_detectors${jobId}/_stats`, }); }, @@ -614,7 +619,7 @@ export function mlApiServicesProvider(httpService: HttpService) { }, mlNodeCount() { - return httpService.http<{ count: number }>({ + return httpService.http({ path: `${basePath()}/ml_node_count`, method: 'GET', }); diff --git a/x-pack/plugins/ml/public/plugin.ts b/x-pack/plugins/ml/public/plugin.ts index 3ba79e0eb9187..ef3de1a5ce65c 100644 --- a/x-pack/plugins/ml/public/plugin.ts +++ b/x-pack/plugins/ml/public/plugin.ts @@ -21,7 +21,6 @@ import type { SharePluginStart, UrlGeneratorContract, } from 'src/plugins/share/public'; -import type { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import type { DataPublicPluginStart } from 'src/plugins/data/public'; import type { HomePublicPluginSetup } from 'src/plugins/home/public'; import type { IndexPatternManagementSetup } from 'src/plugins/index_pattern_management/public'; @@ -60,7 +59,6 @@ export interface MlSetupDependencies { security?: SecurityPluginSetup; licensing: LicensingPluginSetup; management?: ManagementSetup; - usageCollection: UsageCollectionSetup; licenseManagement?: LicenseManagementUIPluginSetup; home?: HomePublicPluginSetup; embeddable: EmbeddableSetup; @@ -102,7 +100,6 @@ export class MlPlugin implements Plugin { security: pluginsSetup.security, licensing: pluginsSetup.licensing, management: pluginsSetup.management, - usageCollection: pluginsSetup.usageCollection, licenseManagement: pluginsSetup.licenseManagement, home: pluginsSetup.home, embeddable: { ...pluginsSetup.embeddable, ...pluginsStart.embeddable }, diff --git a/x-pack/plugins/ml/server/lib/node_utils.ts b/x-pack/plugins/ml/server/lib/node_utils.ts new file mode 100644 index 0000000000000..729c8c0448d20 --- /dev/null +++ b/x-pack/plugins/ml/server/lib/node_utils.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 { IScopedClusterClient } from 'kibana/server'; +import { MlNodeCount } from '../../common/types/ml_server_info'; + +export async function getMlNodeCount(client: IScopedClusterClient): Promise { + const { body } = await client.asInternalUser.nodes.info({ + filter_path: 'nodes.*.attributes', + }); + + let count = 0; + if (typeof body.nodes === 'object') { + Object.keys(body.nodes).forEach((k) => { + if (body.nodes[k].attributes !== undefined) { + const maxOpenJobs = body.nodes[k].attributes['ml.max_open_jobs']; + if (maxOpenJobs !== null && maxOpenJobs > 0) { + count++; + } + } + }); + } + + const lazyNodeCount = await getLazyMlNodeCount(client); + + return { count, lazyNodeCount }; +} + +export async function getLazyMlNodeCount(client: IScopedClusterClient) { + const { body } = await client.asInternalUser.cluster.getSettings({ + include_defaults: true, + filter_path: '**.xpack.ml.max_lazy_ml_nodes', + }); + + const lazyMlNodesString: string | undefined = ( + body.defaults || + body.persistent || + body.transient || + {} + ).xpack?.ml.max_lazy_ml_nodes; + + const count = lazyMlNodesString === undefined ? 0 : +lazyMlNodesString; + + if (count === 0 || isNaN(count)) { + return 0; + } + + return count; +} + +export async function countJobsLazyStarting( + client: IScopedClusterClient, + startingJobsCount: number +) { + const lazyMlNodesCount = await getLazyMlNodeCount(client); + const { count: currentMlNodeCount } = await getMlNodeCount(client); + + const availableLazyMlNodes = lazyMlNodesCount ?? lazyMlNodesCount - currentMlNodeCount; + + let lazilyStartingJobsCount = startingJobsCount; + + if (startingJobsCount > availableLazyMlNodes) { + if (lazyMlNodesCount > currentMlNodeCount) { + lazilyStartingJobsCount = availableLazyMlNodes; + } + } + + const response = { + availableLazyMlNodes, + currentMlNodeCount, + lazilyStartingJobsCount, + totalStartingJobs: startingJobsCount, + }; + + return response; +} diff --git a/x-pack/plugins/ml/server/lib/register_settings.ts b/x-pack/plugins/ml/server/lib/register_settings.ts index 0cdaaadf7f172..4db2f80633480 100644 --- a/x-pack/plugins/ml/server/lib/register_settings.ts +++ b/x-pack/plugins/ml/server/lib/register_settings.ts @@ -14,7 +14,7 @@ import { DEFAULT_AD_RESULTS_TIME_FILTER, DEFAULT_ENABLE_AD_RESULTS_TIME_FILTER, } from '../../common/constants/settings'; -import { MAX_FILE_SIZE } from '../../common/constants/file_datavisualizer'; +import { MAX_FILE_SIZE } from '../../../file_upload/common'; export function registerKibanaSettings(coreSetup: CoreSetup) { coreSetup.uiSettings.register({ diff --git a/x-pack/plugins/ml/server/models/data_frame_analytics/analytics_manager.ts b/x-pack/plugins/ml/server/models/data_frame_analytics/analytics_manager.ts index ea41fb3ae427b..5632b4266def4 100644 --- a/x-pack/plugins/ml/server/models/data_frame_analytics/analytics_manager.ts +++ b/x-pack/plugins/ml/server/models/data_frame_analytics/analytics_manager.ts @@ -18,7 +18,7 @@ import { DataFrameAnalyticsStats, MapElements, } from '../../../common/types/data_frame_analytics'; -import { INDEX_META_DATA_CREATED_BY } from '../../../common/constants/file_datavisualizer'; +import { INDEX_META_DATA_CREATED_BY } from '../../../../file_upload/common'; import { getAnalysisType } from '../../../common/util/analytics_utils'; import { ExtendAnalyticsMapArgs, @@ -35,12 +35,12 @@ import { import type { MlClient } from '../../lib/ml_client'; export class AnalyticsManager { - private _client: IScopedClusterClient['asInternalUser']; + private _client: IScopedClusterClient; private _mlClient: MlClient; private _inferenceModels: TrainedModelConfigResponse[]; private _jobStats: DataFrameAnalyticsStats[]; - constructor(mlClient: MlClient, client: IScopedClusterClient['asInternalUser']) { + constructor(mlClient: MlClient, client: IScopedClusterClient) { this._client = client; this._mlClient = mlClient; this._inferenceModels = []; @@ -112,7 +112,9 @@ export class AnalyticsManager { } private async getAnalyticsStats() { - const resp = await this._mlClient.getDataFrameAnalyticsStats({ size: 1000 }); + const resp = await this._mlClient.getDataFrameAnalyticsStats<{ + data_frame_analytics: DataFrameAnalyticsStats[]; + }>({ size: 1000 }); const stats = resp?.body?.data_frame_analytics; return stats; } @@ -142,15 +144,14 @@ export class AnalyticsManager { } private async getIndexData(index: string) { - const indexData = await this._client.indices.get({ + const indexData = await this._client.asInternalUser.indices.get({ index, }); - return indexData?.body; } private async getTransformData(transformId: string) { - const transform = await this._client.transform.getTransform({ + const transform = await this._client.asInternalUser.transform.getTransform({ transform_id: transformId, }); const transformData = transform?.body?.transforms[0]; diff --git a/x-pack/plugins/ml/server/models/file_data_visualizer/index.ts b/x-pack/plugins/ml/server/models/file_data_visualizer/index.ts index f8a27fdcd7e1a..aa699694e52a3 100644 --- a/x-pack/plugins/ml/server/models/file_data_visualizer/index.ts +++ b/x-pack/plugins/ml/server/models/file_data_visualizer/index.ts @@ -5,5 +5,3 @@ */ export { fileDataVisualizerProvider, InputData } from './file_data_visualizer'; - -export { importDataProvider } from './import_data'; diff --git a/x-pack/plugins/ml/server/models/job_service/error_utils.ts b/x-pack/plugins/ml/server/models/job_service/error_utils.ts index dc871a9dce805..6afce76cdc6c2 100644 --- a/x-pack/plugins/ml/server/models/job_service/error_utils.ts +++ b/x-pack/plugins/ml/server/models/job_service/error_utils.ts @@ -14,7 +14,7 @@ export function isRequestTimeout(error: { name: string }) { return error.name === REQUEST_TIMEOUT_NAME; } -interface Results { +export interface Results { [id: string]: { [status: string]: any; error?: any; diff --git a/x-pack/plugins/ml/server/models/job_service/groups.ts b/x-pack/plugins/ml/server/models/job_service/groups.ts index 81b0494cbdf27..ca7bfe997451c 100644 --- a/x-pack/plugins/ml/server/models/job_service/groups.ts +++ b/x-pack/plugins/ml/server/models/job_service/groups.ts @@ -10,13 +10,13 @@ import { Job } from '../../../common/types/anomaly_detection_jobs'; import { MlJobsResponse } from '../../../common/types/job_service'; import type { MlClient } from '../../lib/ml_client'; -interface Group { +export interface Group { id: string; jobIds: string[]; calendarIds: string[]; } -interface Results { +export interface Results { [id: string]: { success: boolean; error?: any; diff --git a/x-pack/plugins/ml/server/models/job_service/jobs.ts b/x-pack/plugins/ml/server/models/job_service/jobs.ts index 6ab4af63004b4..80bc723952a66 100644 --- a/x-pack/plugins/ml/server/models/job_service/jobs.ts +++ b/x-pack/plugins/ml/server/models/job_service/jobs.ts @@ -204,6 +204,7 @@ export function jobsProvider(client: IScopedClusterClient, mlClient: MlClient) { isNotSingleMetricViewerJobMessage: errorMessage, nodeName: job.node ? job.node.name : undefined, deleting: job.deleting || undefined, + awaitingNodeAssignment: isJobAwaitingNodeAssignment(job), }; if (jobIds.find((j) => j === tempJob.id)) { tempJob.fullJob = job; @@ -519,6 +520,10 @@ export function jobsProvider(client: IScopedClusterClient, mlClient: MlClient) { return false; } + function isJobAwaitingNodeAssignment(job: CombinedJobWithStats) { + return job.node === undefined && job.state === JOB_STATE.OPENING; + } + return { forceDeleteJob, deleteJobs, diff --git a/x-pack/plugins/ml/server/models/job_service/new_job/line_chart.ts b/x-pack/plugins/ml/server/models/job_service/new_job/line_chart.ts index 93f7c81c10023..76404b0aa5b6f 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job/line_chart.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/line_chart.ts @@ -17,7 +17,7 @@ interface Result { value: Value; } -interface ProcessedResults { +export interface ProcessedResults { success: boolean; results: Record; totalResults: number; diff --git a/x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts b/x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts index e614f887e29bc..94b25ac3f0d88 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job/population_chart.ts @@ -23,7 +23,7 @@ interface Result { values: Thing[]; } -interface ProcessedResults { +export interface ProcessedResults { success: boolean; results: Record; totalResults: number; diff --git a/x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.ts b/x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.ts index 73d35efd66c8b..98c6ff3d56060 100644 --- a/x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.ts +++ b/x-pack/plugins/ml/server/models/job_service/new_job_caps/new_job_caps.ts @@ -9,7 +9,7 @@ import { _DOC_COUNT } from '../../../../common/constants/field_types'; import { Aggregation, Field, NewJobCaps } from '../../../../common/types/fields'; import { fieldServiceProvider } from './field_service'; -interface NewJobCapsResponse { +export interface NewJobCapsResponse { [indexPattern: string]: NewJobCaps; } diff --git a/x-pack/plugins/ml/server/plugin.ts b/x-pack/plugins/ml/server/plugin.ts index e48983c1c5365..3c82f2131e25f 100644 --- a/x-pack/plugins/ml/server/plugin.ts +++ b/x-pack/plugins/ml/server/plugin.ts @@ -23,7 +23,6 @@ import { SpacesPluginSetup } from '../../spaces/server'; import { PLUGIN_ID } from '../common/constants/app'; import { MlCapabilities } from '../common/types/capabilities'; -import { initMlTelemetry } from './lib/telemetry'; import { initMlServerLog } from './lib/log'; import { initSampleDataSets } from './lib/sample_data_sets'; @@ -190,7 +189,6 @@ export class MlServerPlugin trainedModelsRoutes(routeInit); initMlServerLog({ log: this.log }); - initMlTelemetry(coreSetup, plugins.usageCollection); return { ...createSharedServices( diff --git a/x-pack/plugins/ml/server/routes/apidoc.json b/x-pack/plugins/ml/server/routes/apidoc.json index 5dc9a3107af86..1b2eb612fda1c 100644 --- a/x-pack/plugins/ml/server/routes/apidoc.json +++ b/x-pack/plugins/ml/server/routes/apidoc.json @@ -43,7 +43,6 @@ "FileDataVisualizer", "AnalyzeFile", - "ImportFile", "ResultsService", "GetAnomaliesTableData", diff --git a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts index 4d504f4f2ef20..c0dba1a882546 100644 --- a/x-pack/plugins/ml/server/routes/data_frame_analytics.ts +++ b/x-pack/plugins/ml/server/routes/data_frame_analytics.ts @@ -44,7 +44,7 @@ function getAnalyticsMap( client: IScopedClusterClient, idOptions: GetAnalyticsMapArgs ) { - const analytics = new AnalyticsManager(mlClient, client.asInternalUser); + const analytics = new AnalyticsManager(mlClient, client); return analytics.getAnalyticsMap(idOptions); } @@ -53,7 +53,7 @@ function getExtendedMap( client: IScopedClusterClient, idOptions: ExtendAnalyticsMapArgs ) { - const analytics = new AnalyticsManager(mlClient, client.asInternalUser); + const analytics = new AnalyticsManager(mlClient, client); return analytics.extendAnalyticsMapForAnalyticsJob(idOptions); } diff --git a/x-pack/plugins/ml/server/routes/file_data_visualizer.ts b/x-pack/plugins/ml/server/routes/file_data_visualizer.ts index c4c449a9e2cb4..9ee19efef13f5 100644 --- a/x-pack/plugins/ml/server/routes/file_data_visualizer.ts +++ b/x-pack/plugins/ml/server/routes/file_data_visualizer.ts @@ -5,28 +5,13 @@ */ import { schema } from '@kbn/config-schema'; -import { IScopedClusterClient } from 'kibana/server'; -import { MAX_FILE_SIZE_BYTES } from '../../common/constants/file_datavisualizer'; -import { - InputOverrides, - Settings, - IngestPipelineWrapper, - Mappings, -} from '../../common/types/file_datavisualizer'; +import { MAX_FILE_SIZE_BYTES } from '../../../file_upload/common'; +import { InputOverrides } from '../../common/types/file_datavisualizer'; import { wrapError } from '../client/error_wrapper'; -import { - InputData, - fileDataVisualizerProvider, - importDataProvider, -} from '../models/file_data_visualizer'; +import { InputData, fileDataVisualizerProvider } from '../models/file_data_visualizer'; import { RouteInitialization } from '../types'; -import { updateTelemetry } from '../lib/telemetry'; -import { - analyzeFileQuerySchema, - importFileBodySchema, - importFileQuerySchema, -} from './schemas/file_data_visualizer_schema'; +import { analyzeFileQuerySchema } from './schemas/file_data_visualizer_schema'; import type { MlClient } from '../lib/ml_client'; function analyzeFiles(mlClient: MlClient, data: InputData, overrides: InputOverrides) { @@ -34,19 +19,6 @@ function analyzeFiles(mlClient: MlClient, data: InputData, overrides: InputOverr return analyzeFile(data, overrides); } -function importData( - client: IScopedClusterClient, - id: string, - index: string, - settings: Settings, - mappings: Mappings, - ingestPipeline: IngestPipelineWrapper, - data: InputData -) { - const { importData: importDataFunc } = importDataProvider(client); - return importDataFunc(id, index, settings, mappings, ingestPipeline, data); -} - /** * Routes for the file data visualizer. */ @@ -84,57 +56,4 @@ export function fileDataVisualizerRoutes({ router, routeGuard }: RouteInitializa } }) ); - - /** - * @apiGroup FileDataVisualizer - * - * @api {post} /api/ml/file_data_visualizer/import Import file data - * @apiName ImportFile - * @apiDescription Imports file data into elasticsearch index. - * - * @apiSchema (query) importFileQuerySchema - * @apiSchema (body) importFileBodySchema - */ - router.post( - { - path: '/api/ml/file_data_visualizer/import', - validate: { - query: importFileQuerySchema, - body: importFileBodySchema, - }, - options: { - body: { - accepts: ['application/json'], - maxBytes: MAX_FILE_SIZE_BYTES, - }, - tags: ['access:ml:canFindFileStructure'], - }, - }, - routeGuard.basicLicenseAPIGuard(async ({ client, request, response }) => { - try { - const { id } = request.query; - const { index, data, settings, mappings, ingestPipeline } = request.body; - - // `id` being `undefined` tells us that this is a new import due to create a new index. - // follow-up import calls to just add additional data will include the `id` of the created - // index, we'll ignore those and don't increment the counter. - if (id === undefined) { - await updateTelemetry(); - } - - const result = await importData( - client, - id, - index, - settings, - mappings, - ingestPipeline, - data - ); - return response.ok({ body: result }); - } catch (e) { - return response.customError(wrapError(e)); - } - }) - ); } diff --git a/x-pack/plugins/ml/server/routes/schemas/file_data_visualizer_schema.ts b/x-pack/plugins/ml/server/routes/schemas/file_data_visualizer_schema.ts index 9a80cf795cabf..685f06f839ee3 100644 --- a/x-pack/plugins/ml/server/routes/schemas/file_data_visualizer_schema.ts +++ b/x-pack/plugins/ml/server/routes/schemas/file_data_visualizer_schema.ts @@ -24,20 +24,3 @@ export const analyzeFileQuerySchema = schema.maybe( timestamp_format: schema.maybe(schema.string()), }) ); - -export const importFileQuerySchema = schema.object({ - id: schema.maybe(schema.string()), -}); - -export const importFileBodySchema = schema.object({ - index: schema.maybe(schema.string()), - data: schema.arrayOf(schema.any()), - settings: schema.maybe(schema.any()), - /** Mappings */ - mappings: schema.any(), - /** Ingest pipeline definition */ - ingestPipeline: schema.object({ - id: schema.maybe(schema.string()), - pipeline: schema.maybe(schema.any()), - }), -}); diff --git a/x-pack/plugins/ml/server/routes/system.ts b/x-pack/plugins/ml/server/routes/system.ts index 8802f51d938e3..78574c3c9ea60 100644 --- a/x-pack/plugins/ml/server/routes/system.ts +++ b/x-pack/plugins/ml/server/routes/system.ts @@ -5,12 +5,13 @@ */ import { schema } from '@kbn/config-schema'; -import { IScopedClusterClient } from 'kibana/server'; + import { wrapError } from '../client/error_wrapper'; import { mlLog } from '../lib/log'; import { capabilitiesProvider } from '../lib/capabilities'; import { spacesUtilsProvider } from '../lib/spaces_utils'; import { RouteInitialization, SystemRouteDeps } from '../types'; +import { getMlNodeCount } from '../lib/node_utils'; /** * System routes @@ -19,25 +20,6 @@ export function systemRoutes( { router, mlLicense, routeGuard }: RouteInitialization, { getSpaces, cloud, resolveMlCapabilities }: SystemRouteDeps ) { - async function getNodeCount(client: IScopedClusterClient) { - const { body } = await client.asInternalUser.nodes.info({ - filter_path: 'nodes.*.attributes', - }); - - let count = 0; - if (typeof body.nodes === 'object') { - Object.keys(body.nodes).forEach((k) => { - if (body.nodes[k].attributes !== undefined) { - const maxOpenJobs = body.nodes[k].attributes['ml.max_open_jobs']; - if (maxOpenJobs !== null && maxOpenJobs > 0) { - count++; - } - } - }); - } - return { count }; - } - /** * @apiGroup SystemRoutes * @@ -156,7 +138,7 @@ export function systemRoutes( routeGuard.basicLicenseAPIGuard(async ({ client, response }) => { try { return response.ok({ - body: await getNodeCount(client), + body: await getMlNodeCount(client), }); } catch (e) { return response.customError(wrapError(e)); diff --git a/x-pack/plugins/ml/server/saved_objects/checks.ts b/x-pack/plugins/ml/server/saved_objects/checks.ts index 9258b143c9747..90771f0938d20 100644 --- a/x-pack/plugins/ml/server/saved_objects/checks.ts +++ b/x-pack/plugins/ml/server/saved_objects/checks.ts @@ -33,7 +33,7 @@ interface JobStatus { }; } -interface StatusResponse { +export interface StatusResponse { savedObjects: { [type in JobType]: JobSavedObjectStatus[]; }; diff --git a/x-pack/plugins/ml/server/types.ts b/x-pack/plugins/ml/server/types.ts index 780a4284312e7..f3e3ee22f3817 100644 --- a/x-pack/plugins/ml/server/types.ts +++ b/x-pack/plugins/ml/server/types.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import type { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import type { HomeServerPluginSetup } from 'src/plugins/home/server'; import type { IRouter } from 'kibana/server'; import type { CloudSetup } from '../../cloud/server'; @@ -43,7 +42,6 @@ export interface PluginsSetup { licensing: LicensingPluginSetup; security?: SecurityPluginSetup; spaces?: SpacesPluginSetup; - usageCollection: UsageCollectionSetup; } export interface PluginsStart { diff --git a/x-pack/plugins/ml/tsconfig.json b/x-pack/plugins/ml/tsconfig.json new file mode 100644 index 0000000000000..113bcbe71047f --- /dev/null +++ b/x-pack/plugins/ml/tsconfig.json @@ -0,0 +1,34 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "common/**/*", + "public/**/*", + "server/**/*", + "__mocks__/**/*", + "shared_imports.ts", + "../../../typings/**/*", + // have to declare *.json explicitly due to https://github.com/microsoft/TypeScript/issues/25636 + "public/**/*.json", + "server/**/*.json", + ], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + { "path": "../../../src/plugins/embeddable/tsconfig.json" }, + { "path": "../../../src/plugins/index_pattern_management/tsconfig.json" }, + { "path": "../cloud/tsconfig.json" }, + { "path": "../features/tsconfig.json" }, + { "path": "../file_upload/tsconfig.json" }, + { "path": "../license_management/tsconfig.json" }, + { "path": "../licensing/tsconfig.json" }, + { "path": "../maps/tsconfig.json" }, + { "path": "../security/tsconfig.json" }, + { "path": "../spaces/tsconfig.json" }, + ] +} diff --git a/x-pack/plugins/monitoring/common/constants.ts b/x-pack/plugins/monitoring/common/constants.ts index c8b3ae0be631a..fda49c370293c 100644 --- a/x-pack/plugins/monitoring/common/constants.ts +++ b/x-pack/plugins/monitoring/common/constants.ts @@ -252,6 +252,7 @@ export const ALERT_MISSING_MONITORING_DATA = `${ALERT_PREFIX}alert_missing_monit export const ALERT_THREAD_POOL_SEARCH_REJECTIONS = `${ALERT_PREFIX}alert_thread_pool_search_rejections`; export const ALERT_THREAD_POOL_WRITE_REJECTIONS = `${ALERT_PREFIX}alert_thread_pool_write_rejections`; export const ALERT_CCR_READ_EXCEPTIONS = `${ALERT_PREFIX}ccr_read_exceptions`; +export const ALERT_LARGE_SHARD_SIZE = `${ALERT_PREFIX}shard_size`; /** * Legacy alerts details/label for server and public use @@ -471,6 +472,30 @@ export const ALERT_DETAILS = { defaultMessage: 'Alert if any CCR read exceptions have been detected.', }), }, + [ALERT_LARGE_SHARD_SIZE]: { + paramDetails: { + threshold: { + label: i18n.translate('xpack.monitoring.alerts.shardSize.paramDetails.threshold.label', { + defaultMessage: `Notify when a shard exceeds this size`, + }), + type: AlertParamType.Number, + append: 'GB', + }, + indexPattern: { + label: i18n.translate('xpack.monitoring.alerts.shardSize.paramDetails.indexPattern.label', { + defaultMessage: `Check the following index patterns`, + }), + placeholder: 'eg: data-*, *prod-data, -.internal-data*', + type: AlertParamType.TextField, + }, + }, + label: i18n.translate('xpack.monitoring.alerts.shardSize.label', { + defaultMessage: 'Shard size', + }), + description: i18n.translate('xpack.monitoring.alerts.shardSize.description', { + defaultMessage: 'Alert if an index (primary) shard is oversize.', + }), + }, }; export const ALERT_PANEL_MENU = [ @@ -494,6 +519,7 @@ export const ALERT_PANEL_MENU = [ { alertName: ALERT_CPU_USAGE }, { alertName: ALERT_DISK_USAGE }, { alertName: ALERT_MEMORY_USAGE }, + { alertName: ALERT_LARGE_SHARD_SIZE }, ], }, { @@ -527,6 +553,7 @@ export const ALERTS = [ ALERT_THREAD_POOL_SEARCH_REJECTIONS, ALERT_THREAD_POOL_WRITE_REJECTIONS, ALERT_CCR_READ_EXCEPTIONS, + ALERT_LARGE_SHARD_SIZE, ]; /** diff --git a/x-pack/plugins/monitoring/common/enums.ts b/x-pack/plugins/monitoring/common/enums.ts index b373428bb279b..e0a770a8b7e04 100644 --- a/x-pack/plugins/monitoring/common/enums.ts +++ b/x-pack/plugins/monitoring/common/enums.ts @@ -23,6 +23,7 @@ export enum AlertMessageTokenType { } export enum AlertParamType { + TextField = 'textfield', Duration = 'duration', Percentage = 'percentage', Number = 'number', diff --git a/x-pack/plugins/monitoring/common/es_glob_patterns.ts b/x-pack/plugins/monitoring/common/es_glob_patterns.ts new file mode 100644 index 0000000000000..aa21622167efa --- /dev/null +++ b/x-pack/plugins/monitoring/common/es_glob_patterns.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. + */ + +export interface RegExPatterns { + contains?: string | RegExp; + negate?: string | RegExp; +} + +const valid = /.*/; + +export class ESGlobPatterns { + public static createRegExPatterns(globPattern: string) { + if (globPattern === '*') { + return { contains: valid, negate: valid }; + } + + globPattern = globPattern.toLowerCase(); + globPattern = globPattern.replace(/[ \\\/?"<>|#]/g, ''); + const patternsArr = globPattern.split(','); + const containPatterns: string[] = []; + const negatePatterns: string[] = []; + patternsArr.forEach((pattern) => { + if (pattern.charAt(0) === '-') { + negatePatterns.push(ESGlobPatterns.createESGlobRegExStr(pattern.slice(1))); + } else { + containPatterns.push(ESGlobPatterns.createESGlobRegExStr(pattern)); + } + }); + const contains = containPatterns.length ? new RegExp(containPatterns.join('|'), 'gi') : valid; + const negate = negatePatterns.length + ? new RegExp(`^((?!(${negatePatterns.join('|')})).)*$`, 'gi') + : valid; + return { contains, negate }; + } + + public static isValid(value: string, patterns: RegExPatterns) { + const { contains = valid, negate = valid } = patterns; + return new RegExp(contains).test(value) && new RegExp(negate).test(value); + } + + private static createESGlobRegExStr(pattern: string) { + const patternsArr = pattern.split('*'); + const firstItem = patternsArr.shift(); + const lastItem = patternsArr.pop(); + const start = firstItem?.length ? `(^${ESGlobPatterns.escapeStr(firstItem)})` : ''; + const mid = patternsArr.map((group) => `(.*${ESGlobPatterns.escapeStr(group)})`); + const end = lastItem?.length ? `(.*${ESGlobPatterns.escapeStr(lastItem)}$)` : ''; + const regExArr = ['(^', start, ...mid, end, ')']; + return regExArr.join(''); + } + + private static escapeStr(str: string) { + return str.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + } +} diff --git a/x-pack/plugins/monitoring/common/types/alerts.ts b/x-pack/plugins/monitoring/common/types/alerts.ts index df6e169f37f7a..cf358a4c5c9be 100644 --- a/x-pack/plugins/monitoring/common/types/alerts.ts +++ b/x-pack/plugins/monitoring/common/types/alerts.ts @@ -28,6 +28,7 @@ export interface CommonAlertFilter { export interface CommonAlertParamDetail { label: string; type?: AlertParamType; + [name: string]: unknown | undefined; } export interface CommonAlertParamDetails { @@ -38,6 +39,7 @@ export interface CommonAlertParams { duration: string; threshold?: number; limit?: string; + [key: string]: unknown; } export interface ThreadPoolRejectionsAlertParams { @@ -182,6 +184,18 @@ export interface CCRReadExceptionsUIMeta extends CCRReadExceptionsStats { itemLabel: string; } +export interface IndexShardSizeStats extends AlertNodeStats { + shardIndex: string; + shardSize: number; +} + +export interface IndexShardSizeUIMeta extends IndexShardSizeStats { + shardIndex: string; + shardSize: number; + instanceId: string; + itemLabel: string; +} + export interface AlertData { nodeName?: string; nodeId?: string; diff --git a/x-pack/plugins/monitoring/common/types/es.ts b/x-pack/plugins/monitoring/common/types/es.ts index 728cd3d73a34c..613cde2f32138 100644 --- a/x-pack/plugins/monitoring/common/types/es.ts +++ b/x-pack/plugins/monitoring/common/types/es.ts @@ -97,6 +97,29 @@ export interface ElasticsearchNodeStats { }; } +export interface ElasticsearchIndexStats { + index?: string; + primaries?: { + docs?: { + count?: number; + }; + store?: { + size_in_bytes?: number; + }; + indexing?: { + index_total?: number; + }; + }; + total?: { + store?: { + size_in_bytes?: number; + }; + search?: { + query_total?: number; + }; + }; +} + export interface ElasticsearchLegacySource { timestamp: string; cluster_uuid: string; @@ -243,28 +266,7 @@ export interface ElasticsearchLegacySource { name?: string; }; }; - index_stats?: { - index?: string; - primaries?: { - docs?: { - count?: number; - }; - store?: { - size_in_bytes?: number; - }; - indexing?: { - index_total?: number; - }; - }; - total?: { - store?: { - size_in_bytes?: number; - }; - search?: { - query_total?: number; - }; - }; - }; + index_stats?: ElasticsearchIndexStats; node_stats?: ElasticsearchNodeStats; service?: { address?: string; diff --git a/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx index e656c0ab253e0..fab6cc35ad61e 100644 --- a/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/ccr_read_exceptions_alert/index.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { Expression, Props } from '../components/duration/expression'; +import { Expression, Props } from '../components/param_details_form/expression'; import { AlertTypeModel, ValidationResult } from '../../../../triggers_actions_ui/public'; import { ALERT_CCR_READ_EXCEPTIONS, ALERT_DETAILS } from '../../../common/constants'; import { AlertTypeParams } from '../../../../alerts/common'; diff --git a/x-pack/plugins/monitoring/public/alerts/components/duration/expression.tsx b/x-pack/plugins/monitoring/public/alerts/components/param_details_form/expression.tsx similarity index 83% rename from x-pack/plugins/monitoring/public/alerts/components/duration/expression.tsx rename to x-pack/plugins/monitoring/public/alerts/components/param_details_form/expression.tsx index 26593fdd6e7b0..762f3ac5a3bf6 100644 --- a/x-pack/plugins/monitoring/public/alerts/components/duration/expression.tsx +++ b/x-pack/plugins/monitoring/public/alerts/components/param_details_form/expression.tsx @@ -11,6 +11,7 @@ import { AlertParamDuration } from '../../flyout_expressions/alert_param_duratio import { AlertParamType } from '../../../../common/enums'; import { AlertParamPercentage } from '../../flyout_expressions/alert_param_percentage'; import { AlertParamNumber } from '../../flyout_expressions/alert_param_number'; +import { AlertParamTextField } from '../../flyout_expressions/alert_param_textfield'; export interface Props { alertParams: { [property: string]: any }; @@ -23,7 +24,7 @@ export interface Props { export const Expression: React.FC = (props) => { const { alertParams, paramDetails, setAlertParams, errors } = props; - const alertParamsUi = Object.keys(alertParams).map((alertParamName) => { + const alertParamsUi = Object.keys(paramDetails).map((alertParamName) => { const details = paramDetails[alertParamName]; const value = alertParams[alertParamName]; @@ -53,6 +54,17 @@ export const Expression: React.FC = (props) => { case AlertParamType.Number: return ( + ); + case AlertParamType.TextField: + return ( + { return { diff --git a/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx index aeb9bab2aae9a..5d4db215eede4 100644 --- a/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/disk_usage_alert/index.tsx @@ -5,8 +5,8 @@ */ import React from 'react'; -import { validate, MonitoringAlertTypeParams } from '../components/duration/validation'; -import { Expression, Props } from '../components/duration/expression'; +import { validate, MonitoringAlertTypeParams } from '../components/param_details_form/validation'; +import { Expression, Props } from '../components/param_details_form/expression'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types'; diff --git a/x-pack/plugins/monitoring/public/alerts/flyout_expressions/alert_param_number.tsx b/x-pack/plugins/monitoring/public/alerts/flyout_expressions/alert_param_number.tsx index 82d44413806c6..ce2c949b540ef 100644 --- a/x-pack/plugins/monitoring/public/alerts/flyout_expressions/alert_param_number.tsx +++ b/x-pack/plugins/monitoring/public/alerts/flyout_expressions/alert_param_number.tsx @@ -10,18 +10,19 @@ import { EuiFormRow, EuiFieldNumber } from '@elastic/eui'; interface Props { name: string; value: number; - label: string; + details: { [key: string]: unknown }; errors: string[]; setAlertParams: (property: string, value: number) => void; } export const AlertParamNumber: React.FC = (props: Props) => { - const { name, label, setAlertParams, errors } = props; + const { name, details, setAlertParams, errors } = props; const [value, setValue] = useState(props.value); return ( - 0}> + 0}> { let newValue = Number(e.target.value); if (isNaN(newValue)) { diff --git a/x-pack/plugins/monitoring/public/alerts/flyout_expressions/alert_param_textfield.tsx b/x-pack/plugins/monitoring/public/alerts/flyout_expressions/alert_param_textfield.tsx new file mode 100644 index 0000000000000..b30c15a37d7e8 --- /dev/null +++ b/x-pack/plugins/monitoring/public/alerts/flyout_expressions/alert_param_textfield.tsx @@ -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 React, { useState } from 'react'; +import { EuiFormRow, EuiFieldText } from '@elastic/eui'; + +interface Props { + name: string; + value: string; + placeholder?: string; + label: string; + errors: string[]; + setAlertParams: (property: string, value: string) => void; +} +export const AlertParamTextField: React.FC = (props: Props) => { + const { name, label, setAlertParams, errors, placeholder } = props; + const [value, setValue] = useState(props.value); + return ( + 0}> + { + const newValue = e.target.value; + setValue(newValue); + setAlertParams(name, newValue); + }} + /> + + ); +}; diff --git a/x-pack/plugins/monitoring/public/alerts/large_shard_size_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/large_shard_size_alert/index.tsx new file mode 100644 index 0000000000000..7bfa3cb151a22 --- /dev/null +++ b/x-pack/plugins/monitoring/public/alerts/large_shard_size_alert/index.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { Expression, Props } from '../components/param_details_form/expression'; +import { AlertTypeModel, ValidationResult } from '../../../../triggers_actions_ui/public'; +import { ALERT_LARGE_SHARD_SIZE, ALERT_DETAILS } from '../../../common/constants'; +import { AlertTypeParams } from '../../../../alerts/common'; + +interface ValidateOptions extends AlertTypeParams { + indexPattern: string; +} + +const validate = (inputValues: ValidateOptions): ValidationResult => { + const validationResult = { errors: {} }; + const errors: { [key: string]: string[] } = { + indexPattern: [], + }; + if (!inputValues.indexPattern) { + errors.indexPattern.push( + i18n.translate('xpack.monitoring.alerts.validation.indexPattern', { + defaultMessage: 'A valid index pattern/s is required.', + }) + ); + } + validationResult.errors = errors; + return validationResult; +}; + +export function createLargeShardSizeAlertType(): AlertTypeModel { + return { + id: ALERT_LARGE_SHARD_SIZE, + description: ALERT_DETAILS[ALERT_LARGE_SHARD_SIZE].description, + iconClass: 'bell', + documentationUrl(docLinks) { + return `${docLinks.links.monitoring.alertsKibana}`; + }, + alertParamsExpression: (props: Props) => ( + + ), + validate, + defaultActionMessage: '{{context.internalFullMessage}}', + requiresAppContext: true, + }; +} diff --git a/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx index b484cd9a975fd..534e410d9edf7 100644 --- a/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/memory_usage_alert/index.tsx @@ -5,8 +5,8 @@ */ import React from 'react'; -import { validate, MonitoringAlertTypeParams } from '../components/duration/validation'; -import { Expression, Props } from '../components/duration/expression'; +import { validate, MonitoringAlertTypeParams } from '../components/param_details_form/validation'; +import { Expression, Props } from '../components/param_details_form/expression'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types'; diff --git a/x-pack/plugins/monitoring/public/alerts/thread_pool_rejections_alert/index.tsx b/x-pack/plugins/monitoring/public/alerts/thread_pool_rejections_alert/index.tsx index 2c57857070d32..c5e0d3307936d 100644 --- a/x-pack/plugins/monitoring/public/alerts/thread_pool_rejections_alert/index.tsx +++ b/x-pack/plugins/monitoring/public/alerts/thread_pool_rejections_alert/index.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { EuiSpacer } from '@elastic/eui'; -import { Expression, Props } from '../components/duration/expression'; +import { Expression, Props } from '../components/param_details_form/expression'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { AlertTypeModel } from '../../../../triggers_actions_ui/public/types'; import { CommonAlertParamDetails } from '../../../common/types/alerts'; diff --git a/x-pack/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js b/x-pack/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js index 8849fb05fcf3c..c19c8c5b8b063 100644 --- a/x-pack/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js +++ b/x-pack/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js @@ -48,6 +48,7 @@ import { ALERT_ELASTICSEARCH_VERSION_MISMATCH, ALERT_MISSING_MONITORING_DATA, ALERT_CCR_READ_EXCEPTIONS, + ALERT_LARGE_SHARD_SIZE, } from '../../../../common/constants'; import { AlertsBadge } from '../../../alerts/badge'; import { shouldShowAlertBadge } from '../../../alerts/lib/should_show_alert_badge'; @@ -177,6 +178,8 @@ const NODES_PANEL_ALERTS = [ ALERT_MISSING_MONITORING_DATA, ]; +const INDICES_PANEL_ALERTS = [ALERT_LARGE_SHARD_SIZE]; + export function ElasticsearchPanel(props) { const clusterStats = props.cluster_stats || {}; const nodes = clusterStats.nodes; @@ -301,6 +304,16 @@ export function ElasticsearchPanel(props) { ); } + let indicesAlertStatus = null; + if (shouldShowAlertBadge(alerts, INDICES_PANEL_ALERTS, setupModeContext)) { + const alertsList = INDICES_PANEL_ALERTS.map((alertType) => alerts[alertType]); + indicesAlertStatus = ( + + + + ); + } + return ( @@ -433,29 +446,36 @@ export function ElasticsearchPanel(props) { - -

- - - -

-
+ + + +

+ + + +

+
+
+ {indicesAlertStatus} +
diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/index/advanced.js b/x-pack/plugins/monitoring/public/components/elasticsearch/index/advanced.js index a0a339fabef92..78934bffb1a44 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/index/advanced.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/index/advanced.js @@ -18,8 +18,9 @@ import { import { IndexDetailStatus } from '../index_detail_status'; import { MonitoringTimeseriesContainer } from '../../chart'; import { FormattedMessage } from '@kbn/i18n/react'; +import { AlertsCallout } from '../../../alerts/callout'; -export const AdvancedIndex = ({ indexSummary, metrics, ...props }) => { +export const AdvancedIndex = ({ indexSummary, metrics, alerts, ...props }) => { const metricsToShow = [ metrics.index_1, metrics.index_2, @@ -46,9 +47,11 @@ export const AdvancedIndex = ({ indexSummary, metrics, ...props }) => { - + + + {metricsToShow.map((metric, index) => ( diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/index/index.js b/x-pack/plugins/monitoring/public/components/elasticsearch/index/index.js index 9fdddd712e86d..7a3069ad106e7 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/index/index.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/index/index.js @@ -18,8 +18,18 @@ import { IndexDetailStatus } from '../index_detail_status'; import { MonitoringTimeseriesContainer } from '../../chart'; import { ShardAllocation } from '../shard_allocation/shard_allocation'; import { Logs } from '../../logs'; +import { AlertsCallout } from '../../../alerts/callout'; -export const Index = ({ scope, indexSummary, metrics, clusterUuid, indexUuid, logs, ...props }) => { +export const Index = ({ + scope, + indexSummary, + metrics, + clusterUuid, + indexUuid, + logs, + alerts, + ...props +}) => { const metricsToShow = [ metrics.index_mem, metrics.index_size, @@ -33,9 +43,11 @@ export const Index = ({ scope, indexSummary, metrics, clusterUuid, indexUuid, lo - + + + {metricsToShow.map((metric, index) => ( diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/index_detail_status/index.js b/x-pack/plugins/monitoring/public/components/elasticsearch/index_detail_status/index.js index aece36d48a809..344a1dd64267e 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/index_detail_status/index.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/index_detail_status/index.js @@ -10,11 +10,18 @@ import { ElasticsearchStatusIcon } from '../status_icon'; import { formatMetric } from '../../../lib/format_number'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; +import { AlertsStatus } from '../../../alerts/status'; -export function IndexDetailStatus({ stats }) { +export function IndexDetailStatus({ stats, alerts = {} }) { const { dataSize, documents: documentCount, totalShards, unassignedShards, status } = stats; const metrics = [ + { + label: i18n.translate('xpack.monitoring.elasticsearch.indexDetailStatus.alerts', { + defaultMessage: 'Alerts', + }), + value: , + }, { label: i18n.translate('xpack.monitoring.elasticsearch.indexDetailStatus.totalTitle', { defaultMessage: 'Total', diff --git a/x-pack/plugins/monitoring/public/components/elasticsearch/indices/indices.js b/x-pack/plugins/monitoring/public/components/elasticsearch/indices/indices.js index a22487800c692..703f142c3a6f8 100644 --- a/x-pack/plugins/monitoring/public/components/elasticsearch/indices/indices.js +++ b/x-pack/plugins/monitoring/public/components/elasticsearch/indices/indices.js @@ -24,88 +24,107 @@ import { } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; +import { AlertsStatus } from '../../../alerts/status'; import './indices.scss'; -const columns = [ - { - name: i18n.translate('xpack.monitoring.elasticsearch.indices.nameTitle', { - defaultMessage: 'Name', - }), - field: 'name', - width: '350px', - sortable: true, - render: (value) => ( -
- - {value} - -
- ), - }, - { - name: i18n.translate('xpack.monitoring.elasticsearch.indices.statusTitle', { - defaultMessage: 'Status', - }), - field: 'status', - sortable: true, - render: (value) => ( -
- -   - {capitalize(value)} -
- ), - }, - { - name: i18n.translate('xpack.monitoring.elasticsearch.indices.documentCountTitle', { - defaultMessage: 'Document Count', - }), - field: 'doc_count', - sortable: true, - render: (value) => ( -
{formatMetric(value, LARGE_ABBREVIATED)}
- ), - }, - { - name: i18n.translate('xpack.monitoring.elasticsearch.indices.dataTitle', { - defaultMessage: 'Data', - }), - field: 'data_size', - sortable: true, - render: (value) =>
{formatMetric(value, LARGE_BYTES)}
, - }, - { - name: i18n.translate('xpack.monitoring.elasticsearch.indices.indexRateTitle', { - defaultMessage: 'Index Rate', - }), - field: 'index_rate', - sortable: true, - render: (value) => ( -
{formatMetric(value, LARGE_FLOAT, '/s')}
- ), - }, - { - name: i18n.translate('xpack.monitoring.elasticsearch.indices.searchRateTitle', { - defaultMessage: 'Search Rate', - }), - field: 'search_rate', - sortable: true, - render: (value) => ( -
{formatMetric(value, LARGE_FLOAT, '/s')}
- ), - }, - { - name: i18n.translate('xpack.monitoring.elasticsearch.indices.unassignedShardsTitle', { - defaultMessage: 'Unassigned Shards', - }), - field: 'unassigned_shards', - sortable: true, - render: (value) =>
{formatMetric(value, '0')}
, - }, -]; +const getColumns = (alerts) => { + return [ + { + name: i18n.translate('xpack.monitoring.elasticsearch.indices.nameTitle', { + defaultMessage: 'Name', + }), + field: 'name', + width: '350px', + sortable: true, + render: (value) => ( +
+ + {value} + +
+ ), + }, + { + name: i18n.translate('xpack.monitoring.elasticsearch.indices.alertsColumnTitle', { + defaultMessage: 'Alerts', + }), + field: 'alerts', + sortable: true, + render: (_field, index) => { + return ( + state.meta.shardIndex === index.name} + /> + ); + }, + }, + { + name: i18n.translate('xpack.monitoring.elasticsearch.indices.statusTitle', { + defaultMessage: 'Status', + }), + field: 'status', + sortable: true, + render: (value) => ( +
+ +   + {capitalize(value)} +
+ ), + }, + { + name: i18n.translate('xpack.monitoring.elasticsearch.indices.documentCountTitle', { + defaultMessage: 'Document Count', + }), + field: 'doc_count', + sortable: true, + render: (value) => ( +
{formatMetric(value, LARGE_ABBREVIATED)}
+ ), + }, + { + name: i18n.translate('xpack.monitoring.elasticsearch.indices.dataTitle', { + defaultMessage: 'Data', + }), + field: 'data_size', + sortable: true, + render: (value) =>
{formatMetric(value, LARGE_BYTES)}
, + }, + { + name: i18n.translate('xpack.monitoring.elasticsearch.indices.indexRateTitle', { + defaultMessage: 'Index Rate', + }), + field: 'index_rate', + sortable: true, + render: (value) => ( +
{formatMetric(value, LARGE_FLOAT, '/s')}
+ ), + }, + { + name: i18n.translate('xpack.monitoring.elasticsearch.indices.searchRateTitle', { + defaultMessage: 'Search Rate', + }), + field: 'search_rate', + sortable: true, + render: (value) => ( +
{formatMetric(value, LARGE_FLOAT, '/s')}
+ ), + }, + { + name: i18n.translate('xpack.monitoring.elasticsearch.indices.unassignedShardsTitle', { + defaultMessage: 'Unassigned Shards', + }), + field: 'unassigned_shards', + sortable: true, + render: (value) =>
{formatMetric(value, '0')}
, + }, + ]; +}; const getNoDataMessage = () => { return ( @@ -134,6 +153,7 @@ export const ElasticsearchIndices = ({ onTableChange, toggleShowSystemIndices, showSystemIndices, + alerts, }) => { return ( @@ -147,7 +167,7 @@ export const ElasticsearchIndices = ({ - + @@ -165,7 +185,7 @@ export const ElasticsearchIndices = ({ state.nodeId === node.resolver} + stateFilter={(state) => (state.nodeId || state.nodeUuid) === node.resolver.uuid} /> ); }, diff --git a/x-pack/plugins/monitoring/public/plugin.ts b/x-pack/plugins/monitoring/public/plugin.ts index 52f8d07f4fdb6..fc38de414661c 100644 --- a/x-pack/plugins/monitoring/public/plugin.ts +++ b/x-pack/plugins/monitoring/public/plugin.ts @@ -27,6 +27,15 @@ import { ALERT_DETAILS, } from '../common/constants'; +import { createCpuUsageAlertType } from './alerts/cpu_usage_alert'; +import { createMissingMonitoringDataAlertType } from './alerts/missing_monitoring_data_alert'; +import { createLegacyAlertTypes } from './alerts/legacy_alert'; +import { createDiskUsageAlertType } from './alerts/disk_usage_alert'; +import { createThreadPoolRejectionsAlertType } from './alerts/thread_pool_rejections_alert'; +import { createMemoryUsageAlertType } from './alerts/memory_usage_alert'; +import { createCCRReadExceptionsAlertType } from './alerts/ccr_read_exceptions_alert'; +import { createLargeShardSizeAlertType } from './alerts/large_shard_size_alert'; + interface MonitoringSetupPluginDependencies { home?: HomePublicPluginSetup; cloud?: { isCloudEnabled: boolean }; @@ -72,7 +81,7 @@ export class MonitoringPlugin }); } - await this.registerAlertsAsync(plugins); + this.registerAlerts(plugins); const app: App = { id, @@ -135,19 +144,7 @@ export class MonitoringPlugin ]; } - private registerAlertsAsync = async (plugins: MonitoringSetupPluginDependencies) => { - const { createCpuUsageAlertType } = await import('./alerts/cpu_usage_alert'); - const { createMissingMonitoringDataAlertType } = await import( - './alerts/missing_monitoring_data_alert' - ); - const { createLegacyAlertTypes } = await import('./alerts/legacy_alert'); - const { createDiskUsageAlertType } = await import('./alerts/disk_usage_alert'); - const { createThreadPoolRejectionsAlertType } = await import( - './alerts/thread_pool_rejections_alert' - ); - const { createMemoryUsageAlertType } = await import('./alerts/memory_usage_alert'); - const { createCCRReadExceptionsAlertType } = await import('./alerts/ccr_read_exceptions_alert'); - + private registerAlerts(plugins: MonitoringSetupPluginDependencies) { const { triggersActionsUi: { alertTypeRegistry }, } = plugins; @@ -168,9 +165,10 @@ export class MonitoringPlugin ) ); alertTypeRegistry.register(createCCRReadExceptionsAlertType()); + alertTypeRegistry.register(createLargeShardSizeAlertType()); const legacyAlertTypes = createLegacyAlertTypes(); for (const legacyAlertType of legacyAlertTypes) { alertTypeRegistry.register(legacyAlertType); } - }; + } } diff --git a/x-pack/plugins/monitoring/public/services/clusters.js b/x-pack/plugins/monitoring/public/services/clusters.js index e94bf990b090c..3475eb8f51ba9 100644 --- a/x-pack/plugins/monitoring/public/services/clusters.js +++ b/x-pack/plugins/monitoring/public/services/clusters.js @@ -22,7 +22,6 @@ function formatCluster(cluster) { } let once = false; -let inTransit = false; export function monitoringClustersProvider($injector) { return async (clusterUuid, ccs, codePaths) => { @@ -88,18 +87,16 @@ export function monitoringClustersProvider($injector) { } } - if (!once && !inTransit) { - inTransit = true; + if (!once) { + once = true; const clusters = await getClusters(); if (clusters.length) { try { const [{ data }] = await Promise.all([ensureAlertsEnabled(), ensureMetricbeatEnabled()]); showAlertsToast(data); - once = true; } catch (_err) { // Intentionally swallow the error as this will retry the next page load } - inTransit = false; } return clusters; } diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/index/advanced/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/index/advanced/index.js index cfc36e360709d..adc75f438098c 100644 --- a/x-pack/plugins/monitoring/public/views/elasticsearch/index/advanced/index.js +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/index/advanced/index.js @@ -16,7 +16,13 @@ import template from './index.html'; import { Legacy } from '../../../../legacy_shims'; import { AdvancedIndex } from '../../../../components/elasticsearch/index/advanced'; import { MonitoringViewBaseController } from '../../../base_controller'; -import { CODE_PATH_ELASTICSEARCH } from '../../../../../common/constants'; +import { + CODE_PATH_ELASTICSEARCH, + ALERT_LARGE_SHARD_SIZE, + ELASTICSEARCH_SYSTEM_ID, +} from '../../../../../common/constants'; +import { SetupModeContext } from '../../../../components/setup_mode/setup_mode_context'; +import { SetupModeRenderer } from '../../../../components/renderers'; function getPageData($injector) { const globalState = $injector.get('globalState'); @@ -70,6 +76,17 @@ uiRoutes.when('/elasticsearch/indices/:index/advanced', { reactNodeId: 'monitoringElasticsearchAdvancedIndexApp', $scope, $injector, + alerts: { + shouldFetch: true, + options: { + alertTypeIds: [ALERT_LARGE_SHARD_SIZE], + filters: [ + { + shardIndex: $route.current.pathParams.index, + }, + ], + }, + }, }); this.indexName = indexName; @@ -78,11 +95,25 @@ uiRoutes.when('/elasticsearch/indices/:index/advanced', { () => this.data, (data) => { this.renderReact( - ( + + {flyoutComponent} + + {bottomBarComponent} + + )} /> ); } diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/index/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/index/index.js index 76628a0a02e42..a4d516f917abb 100644 --- a/x-pack/plugins/monitoring/public/views/elasticsearch/index/index.js +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/index/index.js @@ -18,7 +18,13 @@ import { labels } from '../../../components/elasticsearch/shard_allocation/lib/l import { indicesByNodes } from '../../../components/elasticsearch/shard_allocation/transformers/indices_by_nodes'; import { Index } from '../../../components/elasticsearch/index/index'; import { MonitoringViewBaseController } from '../../base_controller'; -import { CODE_PATH_ELASTICSEARCH } from '../../../../common/constants'; +import { + CODE_PATH_ELASTICSEARCH, + ALERT_LARGE_SHARD_SIZE, + ELASTICSEARCH_SYSTEM_ID, +} from '../../../../common/constants'; +import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context'; +import { SetupModeRenderer } from '../../../components/renderers'; function getPageData($injector) { const $http = $injector.get('$http'); @@ -78,6 +84,17 @@ uiRoutes.when('/elasticsearch/indices/:index', { reactNodeId: 'monitoringElasticsearchIndexApp', $scope, $injector, + alerts: { + shouldFetch: true, + options: { + alertTypeIds: [ALERT_LARGE_SHARD_SIZE], + filters: [ + { + shardIndex: $route.current.pathParams.index, + }, + ], + }, + }, }); this.indexName = indexName; @@ -101,13 +118,26 @@ uiRoutes.when('/elasticsearch/indices/:index', { } this.renderReact( - ( + + {flyoutComponent} + + {bottomBarComponent} + + )} /> ); } diff --git a/x-pack/plugins/monitoring/public/views/elasticsearch/indices/index.js b/x-pack/plugins/monitoring/public/views/elasticsearch/indices/index.js index 3d32ffad2c3d0..1936c80f4330b 100644 --- a/x-pack/plugins/monitoring/public/views/elasticsearch/indices/index.js +++ b/x-pack/plugins/monitoring/public/views/elasticsearch/indices/index.js @@ -12,7 +12,13 @@ import { routeInitProvider } from '../../../lib/route_init'; import { MonitoringViewBaseEuiTableController } from '../../'; import { ElasticsearchIndices } from '../../../components'; import template from './index.html'; -import { CODE_PATH_ELASTICSEARCH } from '../../../../common/constants'; +import { + CODE_PATH_ELASTICSEARCH, + ELASTICSEARCH_SYSTEM_ID, + ALERT_LARGE_SHARD_SIZE, +} from '../../../../common/constants'; +import { SetupModeRenderer } from '../../../components/renderers'; +import { SetupModeContext } from '../../../components/setup_mode/setup_mode_context'; uiRoutes.when('/elasticsearch/indices', { template, @@ -50,6 +56,12 @@ uiRoutes.when('/elasticsearch/indices', { $injector, $scope, $injector, + alerts: { + shouldFetch: true, + options: { + alertTypeIds: [ALERT_LARGE_SHARD_SIZE], + }, + }, }); this.isCcrEnabled = $scope.cluster.isCcrEnabled; @@ -67,14 +79,26 @@ uiRoutes.when('/elasticsearch/indices', { const renderComponent = () => { const { clusterStatus, indices } = this.data; this.renderReact( - ( + + {flyoutComponent} + + {bottomBarComponent} + + )} /> ); }; diff --git a/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts b/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts index 64b7148d87d9e..93d2b52c91a35 100644 --- a/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts +++ b/x-pack/plugins/monitoring/server/alerts/alerts_factory.ts @@ -5,6 +5,7 @@ */ import { + LargeShardSizeAlert, CCRReadExceptionsAlert, CpuUsageAlert, MissingMonitoringDataAlert, @@ -34,6 +35,7 @@ import { ALERT_KIBANA_VERSION_MISMATCH, ALERT_ELASTICSEARCH_VERSION_MISMATCH, ALERT_CCR_READ_EXCEPTIONS, + ALERT_LARGE_SHARD_SIZE, } from '../../common/constants'; import { AlertsClient } from '../../../alerts/server'; import { Alert } from '../../../alerts/common'; @@ -52,6 +54,7 @@ const BY_TYPE = { [ALERT_KIBANA_VERSION_MISMATCH]: KibanaVersionMismatchAlert, [ALERT_ELASTICSEARCH_VERSION_MISMATCH]: ElasticsearchVersionMismatchAlert, [ALERT_CCR_READ_EXCEPTIONS]: CCRReadExceptionsAlert, + [ALERT_LARGE_SHARD_SIZE]: LargeShardSizeAlert, }; export class AlertsFactory { diff --git a/x-pack/plugins/monitoring/server/alerts/base_alert.ts b/x-pack/plugins/monitoring/server/alerts/base_alert.ts index 2a5d956b04f7c..7b5370e11bbf2 100644 --- a/x-pack/plugins/monitoring/server/alerts/base_alert.ts +++ b/x-pack/plugins/monitoring/server/alerts/base_alert.ts @@ -60,7 +60,7 @@ interface AlertOptions { throttle?: string | null; interval?: string; legacy?: LegacyOptions; - defaultParams?: CommonAlertParams; + defaultParams?: Partial; actionVariables: Array<{ name: string; description: string }>; fetchClustersRange?: number; accessorKey?: string; @@ -89,8 +89,13 @@ export class BaseAlert { public rawAlert?: SanitizedAlert, public alertOptions: AlertOptions = defaultAlertOptions() ) { - this.alertOptions = { ...defaultAlertOptions(), ...this.alertOptions }; - this.scopedLogger = Globals.app.getLogger(alertOptions.id!); + const defaultOptions = defaultAlertOptions(); + defaultOptions.defaultParams = { + ...defaultOptions.defaultParams, + ...this.alertOptions.defaultParams, + }; + this.alertOptions = { ...defaultOptions, ...this.alertOptions }; + this.scopedLogger = Globals.app.getLogger(alertOptions.id); } public getAlertType(): AlertType { diff --git a/x-pack/plugins/monitoring/server/alerts/index.ts b/x-pack/plugins/monitoring/server/alerts/index.ts index b58476a01dc14..79e95f3ff8cd6 100644 --- a/x-pack/plugins/monitoring/server/alerts/index.ts +++ b/x-pack/plugins/monitoring/server/alerts/index.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +export { LargeShardSizeAlert } from './large_shard_size_alert'; export { CCRReadExceptionsAlert } from './ccr_read_exceptions_alert'; export { BaseAlert } from './base_alert'; export { CpuUsageAlert } from './cpu_usage_alert'; diff --git a/x-pack/plugins/monitoring/server/alerts/large_shard_size_alert.ts b/x-pack/plugins/monitoring/server/alerts/large_shard_size_alert.ts new file mode 100644 index 0000000000000..4389e23736b1c --- /dev/null +++ b/x-pack/plugins/monitoring/server/alerts/large_shard_size_alert.ts @@ -0,0 +1,231 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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 { BaseAlert } from './base_alert'; +import { + AlertData, + AlertCluster, + AlertState, + AlertMessage, + IndexShardSizeUIMeta, + AlertMessageTimeToken, + AlertMessageLinkToken, + AlertInstanceState, + CommonAlertParams, + CommonAlertFilter, + IndexShardSizeStats, +} from '../../common/types/alerts'; +import { AlertInstance } from '../../../alerts/server'; +import { + INDEX_PATTERN_ELASTICSEARCH, + ALERT_LARGE_SHARD_SIZE, + ALERT_DETAILS, +} from '../../common/constants'; +import { fetchIndexShardSize } from '../lib/alerts/fetch_index_shard_size'; +import { getCcsIndexPattern } from '../lib/alerts/get_ccs_index_pattern'; +import { AlertMessageTokenType, AlertSeverity } from '../../common/enums'; +import { SanitizedAlert, RawAlertInstance } from '../../../alerts/common'; +import { AlertingDefaults, createLink } from './alert_helpers'; +import { appendMetricbeatIndex } from '../lib/alerts/append_mb_index'; +import { Globals } from '../static_globals'; + +const MAX_INDICES_LIST = 10; +export class LargeShardSizeAlert extends BaseAlert { + constructor(public rawAlert?: SanitizedAlert) { + super(rawAlert, { + id: ALERT_LARGE_SHARD_SIZE, + name: ALERT_DETAILS[ALERT_LARGE_SHARD_SIZE].label, + throttle: '12h', + defaultParams: { indexPattern: '*', threshold: 55 }, + actionVariables: [ + { + name: 'shardIndices', + description: i18n.translate( + 'xpack.monitoring.alerts.shardSize.actionVariables.shardIndex', + { + defaultMessage: 'List of indices which are experiencing large shard size.', + } + ), + }, + ...Object.values(AlertingDefaults.ALERT_TYPE.context), + ], + }); + } + + protected async fetchData( + params: CommonAlertParams & { indexPattern: string }, + callCluster: any, + clusters: AlertCluster[], + availableCcs: string[] + ): Promise { + let esIndexPattern = appendMetricbeatIndex(Globals.app.config, INDEX_PATTERN_ELASTICSEARCH); + if (availableCcs) { + esIndexPattern = getCcsIndexPattern(esIndexPattern, availableCcs); + } + const { threshold, indexPattern: shardIndexPatterns } = params; + + const stats = await fetchIndexShardSize( + callCluster, + clusters, + esIndexPattern, + threshold!, + shardIndexPatterns, + Globals.app.config.ui.max_bucket_size + ); + + return stats.map((stat) => { + const { shardIndex, shardSize, clusterUuid, ccs } = stat; + return { + shouldFire: true, + severity: AlertSeverity.Danger, + meta: { + shardIndex, + shardSize, + instanceId: `${clusterUuid}:${shardIndex}`, + itemLabel: shardIndex, + }, + clusterUuid, + ccs, + }; + }); + } + + protected getUiMessage(alertState: AlertState, item: AlertData): AlertMessage { + const { shardIndex, shardSize } = item.meta as IndexShardSizeUIMeta; + return { + text: i18n.translate('xpack.monitoring.alerts.shardSize.ui.firingMessage', { + defaultMessage: `The following index: #start_link{shardIndex}#end_link has a large shard size of: {shardSize}GB at #absolute`, + values: { + shardIndex, + shardSize, + }, + }), + nextSteps: [ + createLink( + i18n.translate('xpack.monitoring.alerts.shardSize.ui.nextSteps.investigateIndex', { + defaultMessage: '#start_linkInvestigate detailed index stats#end_link', + }), + `elasticsearch/indices/${shardIndex}/advanced`, + AlertMessageTokenType.Link + ), + createLink( + i18n.translate('xpack.monitoring.alerts.shardSize.ui.nextSteps.sizeYourShards', { + defaultMessage: '#start_linkHow to size your shards (Docs)#end_link', + }), + `{elasticWebsiteUrl}guide/en/elasticsearch/reference/current/size-your-shards.html` + ), + createLink( + i18n.translate('xpack.monitoring.alerts.shardSize.ui.nextSteps.shardSizingBlog', { + defaultMessage: '#start_linkShard sizing tips (Blog)#end_link', + }), + `{elasticWebsiteUrl}blog/how-many-shards-should-i-have-in-my-elasticsearch-cluster` + ), + ], + tokens: [ + { + startToken: '#absolute', + type: AlertMessageTokenType.Time, + isAbsolute: true, + isRelative: false, + timestamp: alertState.ui.triggeredMS, + } as AlertMessageTimeToken, + { + startToken: '#start_link', + endToken: '#end_link', + type: AlertMessageTokenType.Link, + url: `elasticsearch/indices/${shardIndex}`, + } as AlertMessageLinkToken, + ], + }; + } + + protected filterAlertInstance( + alertInstance: RawAlertInstance, + filters: Array + ) { + const alertInstanceStates = alertInstance.state?.alertStates as AlertState[]; + const alertFilter = filters?.find((filter) => filter.shardIndex); + if (!filters || !filters.length || !alertInstanceStates?.length || !alertFilter?.shardIndex) { + return alertInstance; + } + const alertStates = alertInstanceStates.filter( + ({ meta }) => (meta as IndexShardSizeStats).shardIndex === alertFilter.shardIndex + ); + return { state: { alertStates } }; + } + + protected executeActions( + instance: AlertInstance, + { alertStates }: AlertInstanceState, + item: AlertData | null, + cluster: AlertCluster + ) { + let sortedAlertStates = alertStates.slice(0).sort((alertStateA, alertStateB) => { + const { meta: metaA } = alertStateA as { meta?: IndexShardSizeUIMeta }; + const { meta: metaB } = alertStateB as { meta?: IndexShardSizeUIMeta }; + return metaB!.shardSize - metaA!.shardSize; + }); + + let suffix = ''; + if (sortedAlertStates.length > MAX_INDICES_LIST) { + const diff = sortedAlertStates.length - MAX_INDICES_LIST; + sortedAlertStates = sortedAlertStates.slice(0, MAX_INDICES_LIST); + suffix = `, and ${diff} more`; + } + + const shardIndices = + sortedAlertStates + .map((alertState) => (alertState.meta as IndexShardSizeUIMeta).shardIndex) + .join(', ') + suffix; + + const shortActionText = i18n.translate('xpack.monitoring.alerts.shardSize.shortAction', { + defaultMessage: 'Investigate indices with large shard sizes.', + }); + const fullActionText = i18n.translate('xpack.monitoring.alerts.shardSize.fullAction', { + defaultMessage: 'View index shard size stats', + }); + + const ccs = alertStates.find((state) => state.ccs)?.ccs; + const globalStateLink = this.createGlobalStateLink( + 'elasticsearch/indices', + cluster.clusterUuid, + ccs + ); + + const action = `[${fullActionText}](${globalStateLink})`; + const internalShortMessage = i18n.translate( + 'xpack.monitoring.alerts.shardSize.firing.internalShortMessage', + { + defaultMessage: `Large shard size alert is firing for the following indices: {shardIndices}. {shortActionText}`, + values: { + shardIndices, + shortActionText, + }, + } + ); + const internalFullMessage = i18n.translate( + 'xpack.monitoring.alerts.shardSize.firing.internalFullMessage', + { + defaultMessage: `Large shard size alert is firing for the following indices: {shardIndices}. {action}`, + values: { + action, + shardIndices, + }, + } + ); + + instance.scheduleActions('default', { + internalShortMessage, + internalFullMessage, + state: AlertingDefaults.ALERT_STATE.firing, + shardIndices, + clusterName: cluster.clusterName, + action, + actionPlain: shortActionText, + }); + } +} diff --git a/x-pack/plugins/monitoring/server/lib/alerts/disable_watcher_cluster_alerts.ts b/x-pack/plugins/monitoring/server/lib/alerts/disable_watcher_cluster_alerts.ts index 5dc0b6d0faaa4..e36bd3d488a3e 100644 --- a/x-pack/plugins/monitoring/server/lib/alerts/disable_watcher_cluster_alerts.ts +++ b/x-pack/plugins/monitoring/server/lib/alerts/disable_watcher_cluster_alerts.ts @@ -20,12 +20,19 @@ interface DisableWatchesResponse { >; } -async function callMigrationApi(callCluster: LegacyAPICaller) { - return await callCluster('monitoring.disableWatches'); +async function callMigrationApi(callCluster: LegacyAPICaller, logger: Logger) { + try { + return await callCluster('monitoring.disableWatches'); + } catch (err) { + logger.warn( + `Unable to call migration api to disable cluster alert watches. Message=${err.message}` + ); + return undefined; + } } export async function disableWatcherClusterAlerts(callCluster: LegacyAPICaller, logger: Logger) { - const response: DisableWatchesResponse = await callMigrationApi(callCluster); + const response: DisableWatchesResponse = await callMigrationApi(callCluster, logger); if (!response || response.exporters.length === 0) { return true; } diff --git a/x-pack/plugins/monitoring/server/lib/alerts/fetch_index_shard_size.ts b/x-pack/plugins/monitoring/server/lib/alerts/fetch_index_shard_size.ts new file mode 100644 index 0000000000000..fd653657f8117 --- /dev/null +++ b/x-pack/plugins/monitoring/server/lib/alerts/fetch_index_shard_size.ts @@ -0,0 +1,157 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AlertCluster, IndexShardSizeStats } from '../../../common/types/alerts'; +import { ElasticsearchIndexStats, ElasticsearchResponseHit } from '../../../common/types/es'; +import { ESGlobPatterns, RegExPatterns } from '../../../common/es_glob_patterns'; +import { Globals } from '../../static_globals'; + +interface SourceNode { + name: string; + uuid: string; +} +type TopHitType = ElasticsearchResponseHit & { + _source: { index_stats: Partial; source_node: SourceNode }; +}; + +const memoizedIndexPatterns = (globPatterns: string) => { + const createRegExPatterns = () => ESGlobPatterns.createRegExPatterns(globPatterns); + return Globals.app.getKeyStoreValue( + `large_shard_size_alert::${globPatterns}`, + createRegExPatterns + ) as RegExPatterns; +}; + +const gbMultiplier = 1000000000; + +export async function fetchIndexShardSize( + callCluster: any, + clusters: AlertCluster[], + index: string, + threshold: number, + shardIndexPatterns: string, + size: number +): Promise { + const params = { + index, + filterPath: ['aggregations.clusters.buckets'], + body: { + size: 0, + query: { + bool: { + must: [ + { + match: { + type: 'index_stats', + }, + }, + { + range: { + timestamp: { + gte: 'now-5m', + }, + }, + }, + ], + }, + }, + aggs: { + clusters: { + terms: { + include: clusters.map((cluster) => cluster.clusterUuid), + field: 'cluster_uuid', + size, + }, + aggs: { + over_threshold: { + filter: { + range: { + 'index_stats.primaries.store.size_in_bytes': { + gt: threshold * gbMultiplier, + }, + }, + }, + aggs: { + index: { + terms: { + field: 'index_stats.index', + size, + }, + aggs: { + hits: { + top_hits: { + sort: [ + { + timestamp: { + order: 'desc', + unmapped_type: 'long', + }, + }, + ], + _source: { + includes: [ + '_index', + 'index_stats.primaries.store.size_in_bytes', + 'source_node.name', + 'source_node.uuid', + ], + }, + size: 1, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }; + + const response = await callCluster('search', params); + const stats: IndexShardSizeStats[] = []; + const { buckets: clusterBuckets = [] } = response.aggregations.clusters; + const validIndexPatterns = memoizedIndexPatterns(shardIndexPatterns); + + if (!clusterBuckets.length) { + return stats; + } + + for (const clusterBucket of clusterBuckets) { + const indexBuckets = clusterBucket.over_threshold.index.buckets; + const clusterUuid = clusterBucket.key; + + for (const indexBucket of indexBuckets) { + const shardIndex = indexBucket.key; + const topHit = indexBucket.hits?.hits?.hits[0] as TopHitType; + if ( + !topHit || + shardIndex.charAt() === '.' || + !ESGlobPatterns.isValid(shardIndex, validIndexPatterns) + ) { + continue; + } + const { + _index: monitoringIndexName, + _source: { source_node: sourceNode, index_stats: indexStats }, + } = topHit; + + const { size_in_bytes: shardSizeBytes } = indexStats?.primaries?.store!; + const { name: nodeName, uuid: nodeId } = sourceNode; + const shardSize = +(shardSizeBytes! / gbMultiplier).toFixed(2); + stats.push({ + shardIndex, + shardSize, + clusterUuid, + nodeName, + nodeId, + ccs: monitoringIndexName.includes(':') ? monitoringIndexName.split(':')[0] : undefined, + }); + } + } + return stats; +} diff --git a/x-pack/plugins/monitoring/server/static_globals.ts b/x-pack/plugins/monitoring/server/static_globals.ts index afa26f25919f9..19d3263b66cdb 100644 --- a/x-pack/plugins/monitoring/server/static_globals.ts +++ b/x-pack/plugins/monitoring/server/static_globals.ts @@ -17,8 +17,22 @@ interface IAppGlobals { monitoringCluster: ILegacyCustomClusterClient; config: MonitoringConfig; getLogger: GetLogger; + getKeyStoreValue: (key: string, storeValueMethod?: () => unknown) => unknown; } +interface KeyStoreData { + [key: string]: unknown; +} + +const keyStoreData: KeyStoreData = {}; +const getKeyStoreValue = (key: string, storeValueMethod?: () => unknown) => { + const value = keyStoreData[key]; + if ((value === undefined || value == null) && typeof storeValueMethod === 'function') { + keyStoreData[key] = storeValueMethod(); + } + return keyStoreData[key]; +}; + export class Globals { private static _app: IAppGlobals; @@ -37,6 +51,7 @@ export class Globals { monitoringCluster, config, getLogger, + getKeyStoreValue, }; } diff --git a/x-pack/plugins/observability/server/lib/annotations/create_annotations_client.ts b/x-pack/plugins/observability/server/lib/annotations/create_annotations_client.ts index 76890cbd587e9..3ab645be28c60 100644 --- a/x-pack/plugins/observability/server/lib/annotations/create_annotations_client.ts +++ b/x-pack/plugins/observability/server/lib/annotations/create_annotations_client.ts @@ -37,7 +37,7 @@ interface IndexDocumentResponse { result: string; } -interface GetResponse { +export interface GetResponse { _id: string; _index: string; _source: Annotation; diff --git a/x-pack/plugins/observability/server/utils/unwrap_es_response.ts b/x-pack/plugins/observability/server/utils/unwrap_es_response.ts index 418ceeb64cc87..f9be40e49553c 100644 --- a/x-pack/plugins/observability/server/utils/unwrap_es_response.ts +++ b/x-pack/plugins/observability/server/utils/unwrap_es_response.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PromiseValueType } from '../../../apm/typings/common'; +import type { UnwrapPromise } from '@kbn/utility-types'; export function unwrapEsResponse>( responsePromise: T -): Promise['body']> { +): Promise['body']> { return responsePromise.then((res) => res.body); } diff --git a/x-pack/plugins/observability/tsconfig.json b/x-pack/plugins/observability/tsconfig.json new file mode 100644 index 0000000000000..62aecc1e0899f --- /dev/null +++ b/x-pack/plugins/observability/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": ["common/**/*", "public/**/*", "server/**/*", "typings/**/*"], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + { "path": "../../../src/plugins/data/tsconfig.json" }, + { "path": "../../../src/plugins/home/tsconfig.json" }, + { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, + { "path": "../../../src/plugins/kibana_utils/tsconfig.json" }, + { "path": "../../../src/plugins/usage_collection/tsconfig.json" }, + { "path": "../alerts/tsconfig.json" }, + { "path": "../licensing/tsconfig.json" }, + { "path": "../translations/tsconfig.json" } + ] +} diff --git a/x-pack/plugins/runtime_fields/tsconfig.json b/x-pack/plugins/runtime_fields/tsconfig.json new file mode 100644 index 0000000000000..a1efe4c9cf2dd --- /dev/null +++ b/x-pack/plugins/runtime_fields/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "public/**/*", + "../../../typings/**/*", + ], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + { "path": "../../../src/plugins/es_ui_shared/tsconfig.json" }, + { "path": "../../../src/plugins/kibana_legacy/tsconfig.json"} + ] +} diff --git a/x-pack/plugins/security_solution/common/endpoint/types/index.ts b/x-pack/plugins/security_solution/common/endpoint/types/index.ts index f72373a6544a0..16d30d2153abf 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/index.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/index.ts @@ -425,13 +425,8 @@ export type HostInfo = Immutable<{ query_strategy_version: MetadataQueryStrategyVersions; }>; -export type HostMetadataDetails = Immutable<{ - agent: { - id: string; - }; - HostDetails: HostMetadata; -}>; - +// HostMetadataDetails is now just HostMetadata +// HostDetails is also just HostMetadata export type HostMetadata = Immutable<{ '@timestamp': number; event: { diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/index.ts index 6bb9461995974..9dca1558d5a65 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/index.ts @@ -11,5 +11,6 @@ export * from './last_event_time'; export enum TimelineEventsQueries { all = 'eventsAll', details = 'eventsDetails', + kpi = 'eventsKpi', lastEventTime = 'eventsLastEventTime', } diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/last_event_time/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/last_event_time/index.ts index 10750503fc807..d6c1be0594c0a 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/timeline/events/last_event_time/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/last_event_time/index.ts @@ -25,6 +25,15 @@ export interface TimelineEventsLastEventTimeStrategyResponse extends IEsSearchRe inspect?: Maybe; } +export interface TimelineKpiStrategyResponse extends IEsSearchResponse { + destinationIpCount: number; + inspect?: Maybe; + hostCount: number; + processCount: number; + sourceIpCount: number; + userCount: number; +} + export interface TimelineEventsLastEventTimeRequestOptions extends Omit { indexKey: LastEventIndexKey; diff --git a/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts index d3ec2763f9396..f6b937b516fd6 100644 --- a/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts +++ b/x-pack/plugins/security_solution/common/search_strategy/timeline/index.ts @@ -13,6 +13,7 @@ import { TimelineEventsDetailsStrategyResponse, TimelineEventsLastEventTimeRequestOptions, TimelineEventsLastEventTimeStrategyResponse, + TimelineKpiStrategyResponse, } from './events'; import { DocValueFields, PaginationInputPaginated, TimerangeInput, SortField } from '../common'; @@ -44,6 +45,8 @@ export type TimelineStrategyResponseType< ? TimelineEventsAllStrategyResponse : T extends TimelineEventsQueries.details ? TimelineEventsDetailsStrategyResponse + : T extends TimelineEventsQueries.kpi + ? TimelineKpiStrategyResponse : T extends TimelineEventsQueries.lastEventTime ? TimelineEventsLastEventTimeStrategyResponse : never; @@ -54,6 +57,8 @@ export type TimelineStrategyRequestType< ? TimelineEventsAllRequestOptions : T extends TimelineEventsQueries.details ? TimelineEventsDetailsRequestOptions + : T extends TimelineEventsQueries.kpi + ? TimelineRequestBasicOptions : T extends TimelineEventsQueries.lastEventTime ? TimelineEventsLastEventTimeRequestOptions : never; diff --git a/x-pack/plugins/security_solution/common/test/index.ts b/x-pack/plugins/security_solution/common/test/index.ts index f0735bd9717e5..d7813f97d400c 100644 --- a/x-pack/plugins/security_solution/common/test/index.ts +++ b/x-pack/plugins/security_solution/common/test/index.ts @@ -15,5 +15,3 @@ export enum ROLES { platform_engineer = 'platform_engineer', detections_admin = 'detections_admin', } - -export type RolesType = keyof typeof ROLES; diff --git a/x-pack/plugins/security_solution/cypress/tasks/login.ts b/x-pack/plugins/security_solution/cypress/tasks/login.ts index 6aa60f25c12b8..01a77f7b59b78 100644 --- a/x-pack/plugins/security_solution/cypress/tasks/login.ts +++ b/x-pack/plugins/security_solution/cypress/tasks/login.ts @@ -7,7 +7,7 @@ import * as yaml from 'js-yaml'; import Url, { UrlObject } from 'url'; -import { RolesType } from '../../common/test'; +import { ROLES } from '../../common/test'; import { TIMELINE_FLYOUT_BODY } from '../screens/timeline'; /** @@ -53,7 +53,7 @@ const LOGIN_API_ENDPOINT = '/internal/security/login'; * @param role string role/user to log in with * @param route string route to visit */ -export const getUrlWithRoute = (role: RolesType, route: string) => { +export const getUrlWithRoute = (role: ROLES, route: string) => { const theUrl = `${Url.format({ auth: `${role}:changeme`, username: role, @@ -73,7 +73,7 @@ export const getCurlScriptEnvVars = () => ({ KIBANA_URL: Cypress.env('KIBANA_URL'), }); -export const postRoleAndUser = (role: RolesType) => { +export const postRoleAndUser = (role: ROLES) => { const env = getCurlScriptEnvVars(); const detectionsRoleScriptPath = `./server/lib/detection_engine/scripts/roles_users/${role}/post_detections_role.sh`; const detectionsRoleJsonPath = `./server/lib/detection_engine/scripts/roles_users/${role}/detections_role.json`; @@ -91,7 +91,7 @@ export const postRoleAndUser = (role: RolesType) => { }); }; -export const deleteRoleAndUser = (role: RolesType) => { +export const deleteRoleAndUser = (role: ROLES) => { const env = getCurlScriptEnvVars(); const detectionsUserDeleteScriptPath = `./server/lib/detection_engine/scripts/roles_users/${role}/delete_detections_user.sh`; @@ -101,7 +101,7 @@ export const deleteRoleAndUser = (role: RolesType) => { }); }; -export const loginWithRole = async (role: RolesType) => { +export const loginWithRole = async (role: ROLES) => { postRoleAndUser(role); const theUrl = Url.format({ auth: `${role}:changeme`, @@ -136,7 +136,7 @@ export const loginWithRole = async (role: RolesType) => { * To speed the execution of tests, prefer this non-interactive authentication, * which is faster than authentication via Kibana's interactive login page. */ -export const login = (role?: RolesType) => { +export const login = (role?: ROLES) => { if (role != null) { loginWithRole(role); } else if (credentialsProvidedByEnvironment()) { @@ -217,7 +217,7 @@ const loginViaConfig = () => { * Authenticates with Kibana, visits the specified `url`, and waits for the * Kibana global nav to be displayed before continuing */ -export const loginAndWaitForPage = (url: string, role?: RolesType) => { +export const loginAndWaitForPage = (url: string, role?: ROLES) => { login(role); cy.visit( `${url}?timerange=(global:(linkTo:!(timeline),timerange:(from:1547914976217,fromStr:'2019-01-19T16:22:56.217Z',kind:relative,to:1579537385745,toStr:now)),timeline:(linkTo:!(global),timerange:(from:1547914976217,fromStr:'2019-01-19T16:22:56.217Z',kind:relative,to:1579537385745,toStr:now)))` @@ -225,13 +225,13 @@ export const loginAndWaitForPage = (url: string, role?: RolesType) => { cy.get('[data-test-subj="headerGlobalNav"]'); }; -export const loginAndWaitForPageWithoutDateRange = (url: string, role?: RolesType) => { +export const loginAndWaitForPageWithoutDateRange = (url: string, role?: ROLES) => { login(role); cy.visit(role ? getUrlWithRoute(role, url) : url); cy.get('[data-test-subj="headerGlobalNav"]', { timeout: 120000 }); }; -export const loginAndWaitForTimeline = (timelineId: string, role?: RolesType) => { +export const loginAndWaitForTimeline = (timelineId: string, role?: ROLES) => { const route = `/app/security/timelines?timeline=(id:'${timelineId}',isOpen:!t)`; login(role); @@ -240,7 +240,7 @@ export const loginAndWaitForTimeline = (timelineId: string, role?: RolesType) => cy.get(TIMELINE_FLYOUT_BODY).should('be.visible'); }; -export const waitForPageWithoutDateRange = (url: string, role?: RolesType) => { +export const waitForPageWithoutDateRange = (url: string, role?: ROLES) => { cy.visit(role ? getUrlWithRoute(role, url) : url); cy.get('[data-test-subj="headerGlobalNav"]', { timeout: 120000 }); }; diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx index c64cb2087252d..737143c7a3efd 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.test.tsx @@ -144,7 +144,7 @@ describe('CaseView ', () => { jest.spyOn(routeData, 'useLocation').mockReturnValue(mockLocation); useGetCaseUserActionsMock.mockImplementation(() => defaultUseGetCaseUserActions); usePostPushToServiceMock.mockImplementation(() => ({ isLoading: false, postPushToService })); - useConnectorsMock.mockImplementation(() => ({ connectors: connectorsMock, isLoading: false })); + useConnectorsMock.mockImplementation(() => ({ connectors: connectorsMock, loading: false })); useQueryAlertsMock.mockImplementation(() => ({ loading: false, data: { hits: { hits: alertsHit } }, @@ -705,4 +705,38 @@ describe('CaseView ', () => { expect(updateObject.updateValue).toEqual({ syncAlerts: false }); }); }); + + describe('Callouts', () => { + it('it shows the danger callout when a connector has been deleted', async () => { + useConnectorsMock.mockImplementation(() => ({ connectors: [], loading: false })); + const wrapper = mount( + + + + + + ); + + await waitFor(() => { + wrapper.update(); + expect(wrapper.find('.euiCallOut--danger').first().exists()).toBeTruthy(); + }); + }); + + it('it does NOT shows the danger callout when connectors are loading', async () => { + useConnectorsMock.mockImplementation(() => ({ connectors: [], loading: true })); + const wrapper = mount( + + + + + + ); + + await waitFor(() => { + wrapper.update(); + expect(wrapper.find('.euiCallOut--danger').first().exists()).toBeFalsy(); + }); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx index 8d5201e683712..58858fd71481f 100644 --- a/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/case_view/index.tsx @@ -295,7 +295,7 @@ export const CaseComponent = React.memo( connectors, updateCase: handleUpdateCase, userCanCrud, - isValidConnector, + isValidConnector: isLoadingConnectors ? true : isValidConnector, alerts, }); diff --git a/x-pack/plugins/security_solution/public/cases/components/configure_cases/mapping.test.tsx b/x-pack/plugins/security_solution/public/cases/components/configure_cases/mapping.test.tsx index c7336d998c452..bc96aa65b82a7 100644 --- a/x-pack/plugins/security_solution/public/cases/components/configure_cases/mapping.test.tsx +++ b/x-pack/plugins/security_solution/public/cases/components/configure_cases/mapping.test.tsx @@ -40,7 +40,7 @@ describe('Mapping', () => { wrappingComponent: TestProviders, }); expect(wrapper.find('[data-test-subj="field-mapping-desc"]').first().text()).toBe( - 'Field mappings require an established connection to ServiceNow. Please check your connection credentials.' + 'Field mappings require an established connection to ServiceNow ITSM. Please check your connection credentials.' ); }); }); diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/config.ts b/x-pack/plugins/security_solution/public/cases/components/connectors/config.ts index 3aca186378820..a29531d89b405 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/config.ts +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/config.ts @@ -7,14 +7,14 @@ /* eslint-disable @kbn/eslint/no-restricted-paths */ import { - ServiceNowConnectorConfiguration, + ServiceNowITSMConnectorConfiguration, JiraConnectorConfiguration, ResilientConnectorConfiguration, } from '../../../../../triggers_actions_ui/public/common'; import { ConnectorConfiguration } from './types'; export const connectorsConfiguration: Record = { - '.servicenow': ServiceNowConnectorConfiguration as ConnectorConfiguration, + '.servicenow': ServiceNowITSMConnectorConfiguration as ConnectorConfiguration, '.jira': JiraConnectorConfiguration as ConnectorConfiguration, '.resilient': ResilientConnectorConfiguration as ConnectorConfiguration, }; diff --git a/x-pack/plugins/security_solution/public/cases/components/connectors/types.ts b/x-pack/plugins/security_solution/public/cases/components/connectors/types.ts index 5d83c226bfeca..00bc01b2ec0a7 100644 --- a/x-pack/plugins/security_solution/public/cases/components/connectors/types.ts +++ b/x-pack/plugins/security_solution/public/cases/components/connectors/types.ts @@ -22,5 +22,4 @@ export interface ThirdPartyField { export interface ConnectorConfiguration extends ActionType { logo: string; - fields: Record; } diff --git a/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_installed_security_jobs.test.ts b/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_installed_security_jobs.test.ts index 72690a1773926..5c31a12d07e50 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_installed_security_jobs.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml/hooks/use_installed_security_jobs.test.ts @@ -43,6 +43,7 @@ describe('useInstalledSecurityJobs', () => { expect(result.current.jobs).toEqual( expect.arrayContaining([ { + awaitingNodeAssignment: false, datafeedId: 'datafeed-siem-api-rare_process_linux_ecs', datafeedIndices: ['auditbeat-*'], datafeedState: 'stopped', diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/api.mock.ts b/x-pack/plugins/security_solution/public/common/components/ml_popover/api.mock.ts index 0e8f033ff0cf3..aeea5d1af3669 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/api.mock.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/api.mock.ts @@ -46,6 +46,7 @@ export const mockOpenedJob: MlSummaryJob = { memory_status: 'hard_limit', nodeName: 'siem-es', processed_record_count: 3425264, + awaitingNodeAssignment: false, }; export const mockJobsSummaryResponse: MlSummaryJob[] = [ @@ -64,6 +65,7 @@ export const mockJobsSummaryResponse: MlSummaryJob[] = [ latestTimestampMs: 1561402325194, earliestTimestampMs: 1554327458406, isSingleMetricViewerJob: true, + awaitingNodeAssignment: false, }, { id: 'siem-api-rare_process_linux_ecs', @@ -79,6 +81,7 @@ export const mockJobsSummaryResponse: MlSummaryJob[] = [ latestTimestampMs: 1557434782207, earliestTimestampMs: 1557353420495, isSingleMetricViewerJob: true, + awaitingNodeAssignment: false, }, { id: 'siem-api-rare_process_windows_ecs', @@ -92,6 +95,7 @@ export const mockJobsSummaryResponse: MlSummaryJob[] = [ datafeedIndices: ['winlogbeat-*'], datafeedState: 'stopped', isSingleMetricViewerJob: true, + awaitingNodeAssignment: false, }, { id: 'siem-api-suspicious_login_activity_ecs', @@ -105,6 +109,7 @@ export const mockJobsSummaryResponse: MlSummaryJob[] = [ datafeedIndices: ['auditbeat-*'], datafeedState: 'stopped', isSingleMetricViewerJob: true, + awaitingNodeAssignment: false, }, ]; @@ -513,6 +518,7 @@ export const mockSecurityJobs: SecurityJob[] = [ isCompatible: true, isInstalled: true, isElasticJob: true, + awaitingNodeAssignment: false, }, { id: 'rare_process_by_host_linux_ecs', @@ -531,6 +537,7 @@ export const mockSecurityJobs: SecurityJob[] = [ isCompatible: true, isInstalled: true, isElasticJob: true, + awaitingNodeAssignment: false, }, { datafeedId: '', @@ -549,5 +556,6 @@ export const mockSecurityJobs: SecurityJob[] = [ isCompatible: false, isInstalled: false, isElasticJob: true, + awaitingNodeAssignment: false, }, ]; diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs.test.ts b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs.test.ts index 80f50912a84f2..cfdadf4bc7613 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs.test.ts +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs.test.ts @@ -65,6 +65,7 @@ describe('useSecurityJobs', () => { memory_status: 'hard_limit', moduleId: '', processed_record_count: 582251, + awaitingNodeAssignment: false, }; const { result, waitForNextUpdate } = renderHook(() => useSecurityJobs(false)); diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs_helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs_helpers.test.tsx index 7fb4e6f59d9f7..b37839e50d1f3 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs_helpers.test.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs_helpers.test.tsx @@ -29,6 +29,7 @@ describe('useSecurityJobsHelpers', () => { false ); expect(securityJob).toEqual({ + awaitingNodeAssignment: false, datafeedId: '', datafeedIndices: [], datafeedState: '', diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs_helpers.tsx b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs_helpers.tsx index d0109fd29b5fb..6d9870aa427cc 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs_helpers.tsx +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/hooks/use_security_jobs_helpers.tsx @@ -42,6 +42,7 @@ export const moduleToSecurityJob = ( isCompatible, isInstalled: false, isElasticJob: true, + awaitingNodeAssignment: false, }; }; diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/__snapshots__/jobs_table.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/__snapshots__/jobs_table.test.tsx.snap index a40ccfa7cd1b5..d64fb474c1fb3 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/__snapshots__/jobs_table.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/__snapshots__/jobs_table.test.tsx.snap @@ -25,6 +25,7 @@ exports[`JobsTableComponent renders correctly against snapshot 1`] = ` items={ Array [ Object { + "awaitingNodeAssignment": false, "datafeedId": "datafeed-linux_anomalous_network_activity_ecs", "datafeedIndices": Array [ "auditbeat-*", @@ -52,6 +53,7 @@ exports[`JobsTableComponent renders correctly against snapshot 1`] = ` "processed_record_count": 32010, }, Object { + "awaitingNodeAssignment": false, "datafeedId": "datafeed-rare_process_by_host_linux_ecs", "datafeedIndices": Array [ "auditbeat-*", @@ -76,6 +78,7 @@ exports[`JobsTableComponent renders correctly against snapshot 1`] = ` "processed_record_count": 0, }, Object { + "awaitingNodeAssignment": false, "datafeedId": "", "datafeedIndices": Array [], "datafeedState": "", diff --git a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/__snapshots__/jobs_table_filters.test.tsx.snap b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/__snapshots__/jobs_table_filters.test.tsx.snap index 9bee321e9fbde..7367dbf7bdc0a 100644 --- a/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/__snapshots__/jobs_table_filters.test.tsx.snap +++ b/x-pack/plugins/security_solution/public/common/components/ml_popover/jobs_table/filters/__snapshots__/jobs_table_filters.test.tsx.snap @@ -28,6 +28,7 @@ exports[`JobsTableFilters renders correctly against snapshot 1`] = ` securityJobs={ Array [ Object { + "awaitingNodeAssignment": false, "datafeedId": "datafeed-linux_anomalous_network_activity_ecs", "datafeedIndices": Array [ "auditbeat-*", @@ -55,6 +56,7 @@ exports[`JobsTableFilters renders correctly against snapshot 1`] = ` "processed_record_count": 32010, }, Object { + "awaitingNodeAssignment": false, "datafeedId": "datafeed-rare_process_by_host_linux_ecs", "datafeedIndices": Array [ "auditbeat-*", @@ -79,6 +81,7 @@ exports[`JobsTableFilters renders correctly against snapshot 1`] = ` "processed_record_count": 0, }, Object { + "awaitingNodeAssignment": false, "datafeedId": "", "datafeedIndices": Array [], "datafeedState": "", diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/mock.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/mock.ts index dfe71568a1c3e..a7eaa1bde5b41 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/mock.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/mock.ts @@ -989,6 +989,7 @@ export const mockUserPrivilege: Privilege = { cluster: { monitor_ml: true, manage_ccr: true, + manage_api_key: true, manage_index_templates: true, monitor_watcher: true, monitor_transform: true, @@ -1033,6 +1034,7 @@ export const mockUserPrivilege: Privilege = { write: true, }, }, + application: {}, is_authenticated: true, has_encryption_key: true, }; diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/types.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/types.ts index a26ac23c7f5bc..258c0a3d25994 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/types.ts +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/types.ts @@ -57,6 +57,7 @@ export interface Privilege { monitor_watcher: boolean; monitor_transform: boolean; read_ilm: boolean; + manage_api_key: boolean; manage_security: boolean; manage_own_api_key: boolean; manage_saml: boolean; @@ -97,6 +98,7 @@ export interface Privilege { write: boolean; }; }; + application: {}; is_authenticated: boolean; has_encryption_key: boolean; } diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_privilege_user.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_privilege_user.test.tsx index 6fe8b279498a0..b6de0ef3b4513 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_privilege_user.test.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_privilege_user.test.tsx @@ -7,6 +7,7 @@ import { renderHook, act } from '@testing-library/react-hooks'; import { usePrivilegeUser, ReturnPrivilegeUser } from './use_privilege_user'; import * as api from './api'; +import { Privilege } from './types'; jest.mock('./api'); @@ -70,4 +71,156 @@ describe('usePrivilegeUser', () => { }); }); }); + + test('returns "hasIndexManage" is false if the privilege does not have cluster manage', async () => { + const privilege: Privilege = { + username: 'soc_manager', + has_all_requested: false, + cluster: { + monitor_ml: false, + manage_ccr: false, + manage_index_templates: false, + monitor_watcher: false, + monitor_transform: false, + read_ilm: false, + manage_api_key: false, + manage_security: false, + manage_own_api_key: false, + manage_saml: false, + all: false, + manage_ilm: false, + manage_ingest_pipelines: false, + read_ccr: false, + manage_rollup: false, + monitor: false, + manage_watcher: false, + manage: false, + manage_transform: false, + manage_token: false, + manage_ml: false, + manage_pipeline: false, + monitor_rollup: false, + transport_client: false, + create_snapshot: false, + }, + index: { + '.siem-signals-default': { + all: false, + manage_ilm: true, + read: true, + create_index: true, + read_cross_cluster: false, + index: true, + monitor: true, + delete: true, + manage: true, + delete_index: true, + create_doc: true, + view_index_metadata: true, + create: true, + manage_follow_index: true, + manage_leader_index: true, + maintenance: true, + write: true, + }, + }, + application: {}, + is_authenticated: true, + has_encryption_key: true, + }; + const spyOnGetUserPrivilege = jest.spyOn(api, 'getUserPrivilege'); + spyOnGetUserPrivilege.mockImplementation(() => Promise.resolve(privilege)); + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + usePrivilegeUser() + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + expect(result.current).toEqual({ + hasEncryptionKey: true, + hasIndexManage: false, + hasIndexMaintenance: true, + hasIndexWrite: true, + hasIndexUpdateDelete: true, + isAuthenticated: true, + loading: false, + }); + }); + }); + + test('returns "hasIndexManage" is true if the privilege has cluster manage', async () => { + const privilege: Privilege = { + username: 'soc_manager', + has_all_requested: false, + cluster: { + monitor_ml: false, + manage_ccr: false, + manage_index_templates: false, + monitor_watcher: false, + monitor_transform: false, + read_ilm: false, + manage_api_key: false, + manage_security: false, + manage_own_api_key: false, + manage_saml: false, + all: false, + manage_ilm: false, + manage_ingest_pipelines: false, + read_ccr: false, + manage_rollup: false, + monitor: false, + manage_watcher: false, + manage: true, + manage_transform: false, + manage_token: false, + manage_ml: false, + manage_pipeline: false, + monitor_rollup: false, + transport_client: false, + create_snapshot: false, + }, + index: { + '.siem-signals-default': { + all: false, + manage_ilm: true, + read: true, + create_index: true, + read_cross_cluster: false, + index: true, + monitor: true, + delete: true, + manage: true, + delete_index: true, + create_doc: true, + view_index_metadata: true, + create: true, + manage_follow_index: true, + manage_leader_index: true, + maintenance: true, + write: true, + }, + }, + application: {}, + is_authenticated: true, + has_encryption_key: true, + }; + const spyOnGetUserPrivilege = jest.spyOn(api, 'getUserPrivilege'); + spyOnGetUserPrivilege.mockImplementation(() => Promise.resolve(privilege)); + await act(async () => { + const { result, waitForNextUpdate } = renderHook(() => + usePrivilegeUser() + ); + await waitForNextUpdate(); + await waitForNextUpdate(); + expect(result.current).toEqual({ + hasEncryptionKey: true, + hasIndexManage: true, + hasIndexMaintenance: true, + hasIndexWrite: true, + hasIndexUpdateDelete: true, + isAuthenticated: true, + loading: false, + }); + }); + }); }); diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_privilege_user.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_privilege_user.tsx index 191c3955caa9b..c444702312ffb 100644 --- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_privilege_user.tsx +++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/use_privilege_user.tsx @@ -62,7 +62,7 @@ export const usePrivilegeUser = (): ReturnPrivilegeUser => { setPrivilegeUser({ isAuthenticated: privilege.is_authenticated, hasEncryptionKey: privilege.has_encryption_key, - hasIndexManage: privilege.index[indexName].manage, + hasIndexManage: privilege.index[indexName].manage && privilege.cluster.manage, hasIndexMaintenance: privilege.index[indexName].maintenance, hasIndexWrite: privilege.index[indexName].create || diff --git a/x-pack/plugins/security_solution/public/graphql/introspection.json b/x-pack/plugins/security_solution/public/graphql/introspection.json index 9e0cf10a54aa9..a2f1f222173dd 100644 --- a/x-pack/plugins/security_solution/public/graphql/introspection.json +++ b/x-pack/plugins/security_solution/public/graphql/introspection.json @@ -3365,6 +3365,24 @@ "description": "", "type": { "kind": "SCALAR", "name": "ID", "ofType": null }, "defaultValue": null + }, + { + "name": "templateTimelineId", + "description": "", + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "defaultValue": null + }, + { + "name": "templateTimelineVersion", + "description": "", + "type": { "kind": "SCALAR", "name": "Int", "ofType": null }, + "defaultValue": null + }, + { + "name": "timelineType", + "description": "", + "type": { "kind": "ENUM", "name": "TimelineType", "ofType": null }, + "defaultValue": null } ], "type": { @@ -4149,6 +4167,30 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "templateTimelineId", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "String", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "templateTimelineVersion", + "description": "", + "args": [], + "type": { "kind": "SCALAR", "name": "Int", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "timelineType", + "description": "", + "args": [], + "type": { "kind": "ENUM", "name": "TimelineType", "ofType": null }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "version", "description": "", diff --git a/x-pack/plugins/security_solution/public/graphql/types.ts b/x-pack/plugins/security_solution/public/graphql/types.ts index 435576a02b30e..a5e027c695464 100644 --- a/x-pack/plugins/security_solution/public/graphql/types.ts +++ b/x-pack/plugins/security_solution/public/graphql/types.ts @@ -835,6 +835,12 @@ export interface ResponseFavoriteTimeline { savedObjectId: string; + templateTimelineId?: Maybe; + + templateTimelineVersion?: Maybe; + + timelineType?: Maybe; + version: string; favorite?: Maybe; @@ -1691,6 +1697,12 @@ export interface PersistTimelineMutationArgs { } export interface PersistFavoriteMutationArgs { timelineId?: Maybe; + + templateTimelineId?: Maybe; + + templateTimelineVersion?: Maybe; + + timelineType?: Maybe; } export interface DeleteTimelineMutationArgs { id: string[]; @@ -2096,6 +2108,9 @@ export namespace DeleteTimelineMutation { export namespace PersistTimelineFavoriteMutation { export type Variables = { timelineId?: Maybe; + templateTimelineId?: Maybe; + templateTimelineVersion?: Maybe; + timelineType: TimelineType; }; export type Mutation = { @@ -2112,6 +2127,12 @@ export namespace PersistTimelineFavoriteMutation { version: string; favorite: Maybe; + + templateTimelineId: Maybe; + + templateTimelineVersion: Maybe; + + timelineType: Maybe; }; export type Favorite = { diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.test.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.test.tsx new file mode 100644 index 0000000000000..3a2f96e420254 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.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 { useKibana } from '../../../../common/lib/kibana'; +import { TestProviders, mockIndexNames, mockIndexPattern } from '../../../../common/mock'; +import { useTimelineKpis } from '../../../containers/kpis'; +import { FlyoutHeader } from '.'; +import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { mockBrowserFields, mockDocValueFields } from '../../../../common/containers/source/mock'; +import { useMountAppended } from '../../../../common/utils/use_mount_appended'; +import { getEmptyValue } from '../../../../common/components/empty_value'; + +const mockUseSourcererScope: jest.Mock = useSourcererScope as jest.Mock; +jest.mock('../../../../common/containers/sourcerer'); + +const mockUseTimelineKpis: jest.Mock = useTimelineKpis as jest.Mock; +jest.mock('../../../containers/kpis', () => ({ + useTimelineKpis: jest.fn(), +})); +const useKibanaMock = useKibana as jest.Mocked; +jest.mock('../../../../common/lib/kibana'); + +const mockUseTimelineKpiResponse = { + processCount: 1, + userCount: 1, + sourceIpCount: 1, + hostCount: 1, + destinationIpCount: 1, +}; +const defaultMocks = { + browserFields: mockBrowserFields, + docValueFields: mockDocValueFields, + indexPattern: mockIndexPattern, + loading: false, + selectedPatterns: mockIndexNames, +}; +describe('Timeline KPIs', () => { + const mount = useMountAppended(); + + beforeEach(() => { + // Mocking these services is required for the header component to render. + mockUseSourcererScope.mockImplementation(() => defaultMocks); + useKibanaMock().services.application.capabilities = { + navLinks: {}, + management: {}, + catalogue: {}, + actions: { show: true, crud: true }, + }; + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + describe('when the data is not loading and the response contains data', () => { + beforeEach(() => { + mockUseTimelineKpis.mockReturnValue([false, mockUseTimelineKpiResponse]); + }); + it('renders the component, labels and values succesfully', async () => { + const wrapper = mount( + + + + ); + expect(wrapper.find('[data-test-subj="siem-timeline-kpis"]').exists()).toEqual(true); + // label + expect(wrapper.find('[data-test-subj="siem-timeline-process-kpi"]').first().text()).toEqual( + expect.stringContaining('Processes') + ); + // value + expect(wrapper.find('[data-test-subj="siem-timeline-process-kpi"]').first().text()).toEqual( + expect.stringContaining('1') + ); + }); + }); + + describe('when the data is loading', () => { + beforeEach(() => { + mockUseTimelineKpis.mockReturnValue([true, mockUseTimelineKpiResponse]); + }); + it('renders a loading indicator for values', async () => { + const wrapper = mount( + + + + ); + expect(wrapper.find('[data-test-subj="siem-timeline-process-kpi"]').first().text()).toEqual( + expect.stringContaining('--') + ); + }); + }); + + describe('when the response is null and timeline is blank', () => { + beforeEach(() => { + mockUseTimelineKpis.mockReturnValue([false, null]); + }); + it('renders labels and the default empty string', async () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find('[data-test-subj="siem-timeline-process-kpi"]').first().text()).toEqual( + expect.stringContaining('Processes') + ); + expect(wrapper.find('[data-test-subj="siem-timeline-process-kpi"]').first().text()).toEqual( + expect.stringContaining(getEmptyValue()) + ); + }); + }); +}); diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx index 0e948afd5d7c6..6e77971b8553d 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/index.tsx @@ -15,25 +15,42 @@ import { } from '@elastic/eui'; import React, { useCallback, useMemo } from 'react'; import { isEmpty, get, pick } from 'lodash/fp'; -import { useDispatch } from 'react-redux'; +import { useDispatch, useSelector } from 'react-redux'; import styled from 'styled-components'; import { FormattedRelative } from '@kbn/i18n/react'; import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; -import { TimelineStatus, TimelineTabs, TimelineType } from '../../../../../common/types/timeline'; +import { + TimelineStatus, + TimelineTabs, + TimelineType, + TimelineId, +} from '../../../../../common/types/timeline'; +import { State } from '../../../../common/store'; import { timelineActions, timelineSelectors } from '../../../store/timeline'; import { timelineDefaults } from '../../../../timelines/store/timeline/defaults'; import { AddToFavoritesButton } from '../../timeline/properties/helpers'; - +import { TimerangeInput } from '../../../../../common/search_strategy'; import { AddToCaseButton } from '../add_to_case_button'; import { AddTimelineButton } from '../add_timeline_button'; import { SaveTimelineButton } from '../../timeline/header/save_timeline_button'; +import { useKibana } from '../../../../common/lib/kibana'; import { InspectButton } from '../../../../common/components/inspect'; +import { useTimelineKpis } from '../../../containers/kpis'; +import { esQuery } from '../../../../../../../../src/plugins/data/public'; +import { useSourcererScope } from '../../../../common/containers/sourcerer'; +import { TimelineModel } from '../../../../timelines/store/timeline/model'; +import { + startSelector, + endSelector, +} from '../../../../common/components/super_date_picker/selectors'; +import { combineQueries, focusActiveTimelineButton } from '../../timeline/helpers'; +import { SourcererScopeName } from '../../../../common/store/sourcerer/model'; import { ActiveTimelines } from './active_timelines'; import * as i18n from './translations'; import * as commonI18n from '../../timeline/properties/translations'; import { getTimelineStatusByIdSelector } from './selectors'; -import { focusActiveTimelineButton } from '../../timeline/helpers'; +import { TimelineKPIs } from './kpis'; // to hide side borders const StyledPanel = styled(EuiPanel)` @@ -227,38 +244,106 @@ const TimelineStatusInfoComponent: React.FC = ({ timelineId } const TimelineStatusInfo = React.memo(TimelineStatusInfoComponent); -const FlyoutHeaderComponent: React.FC = ({ timelineId }) => ( - - - - - - - - - - - - - - - - +const FlyoutHeaderComponent: React.FC = ({ timelineId }) => { + const { selectedPatterns, indexPattern, docValueFields, browserFields } = useSourcererScope( + SourcererScopeName.timeline + ); + const getStartSelector = useMemo(() => startSelector(), []); + const getEndSelector = useMemo(() => endSelector(), []); + const isActive = useMemo(() => timelineId === TimelineId.active, [timelineId]); + const timerange: TimerangeInput = useDeepEqualSelector((state) => { + if (isActive) { + return { + from: getStartSelector(state.inputs.timeline), + to: getEndSelector(state.inputs.timeline), + interval: '', + }; + } else { + return { + from: getStartSelector(state.inputs.global), + to: getEndSelector(state.inputs.global), + interval: '', + }; + } + }); + const { uiSettings } = useKibana().services; + const esQueryConfig = useMemo(() => esQuery.getEsQueryConfig(uiSettings), [uiSettings]); + const getTimeline = useMemo(() => timelineSelectors.getTimelineByIdSelector(), []); + const timeline: TimelineModel = useSelector( + (state: State) => getTimeline(state, timelineId) ?? timelineDefaults + ); + const { dataProviders, filters, timelineType, kqlMode, activeTab } = timeline; + const getKqlQueryTimeline = useMemo(() => timelineSelectors.getKqlFilterQuerySelector(), []); + const kqlQueryTimeline = useSelector((state: State) => getKqlQueryTimeline(state, timelineId)!); - {/* KPIs PLACEHOLDER */} + const kqlQueryExpression = + isEmpty(dataProviders) && isEmpty(kqlQueryTimeline) && timelineType === 'template' + ? ' ' + : kqlQueryTimeline; + const kqlQuery = useMemo(() => ({ query: kqlQueryExpression, language: 'kuery' }), [ + kqlQueryExpression, + ]); - - - - - - - - - - - -); + const isBlankTimeline: boolean = useMemo( + () => isEmpty(dataProviders) && isEmpty(filters) && isEmpty(kqlQuery.query), + [dataProviders, filters, kqlQuery] + ); + const combinedQueries = useMemo( + () => + combineQueries({ + config: esQueryConfig, + dataProviders, + indexPattern, + browserFields, + filters: filters ? filters : [], + kqlQuery, + kqlMode, + }), + [browserFields, dataProviders, esQueryConfig, filters, indexPattern, kqlMode, kqlQuery] + ); + const [loading, kpis] = useTimelineKpis({ + defaultIndex: selectedPatterns, + docValueFields, + timerange, + isBlankTimeline, + filterQuery: combinedQueries?.filterQuery ?? '', + }); + + return ( + + + + + + + + + + + + + + + + + + + {activeTab === TimelineTabs.query ? : null} + + + + + + + + + + + + + + ); +}; FlyoutHeaderComponent.displayName = 'FlyoutHeaderComponent'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/kpis.tsx b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/kpis.tsx new file mode 100644 index 0000000000000..b8dc10a878f89 --- /dev/null +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/kpis.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 from 'react'; + +import { EuiStat, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; +import { TimelineKpiStrategyResponse } from '../../../../../common/search_strategy'; +import { getEmptyValue } from '../../../../common/components/empty_value'; +import * as i18n from './translations'; + +export const TimelineKPIs = React.memo( + ({ kpis, isLoading }: { kpis: TimelineKpiStrategyResponse | null; isLoading: boolean }) => { + return ( + + + + + + + + + + + + + + + + + + ); + } +); + +TimelineKPIs.displayName = 'TimelineKPIs'; diff --git a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/translations.ts b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/translations.ts index 6492731cdeba7..8c4a0aa12ef8c 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/flyout/header/translations.ts +++ b/x-pack/plugins/security_solution/public/timelines/components/flyout/header/translations.ts @@ -31,6 +31,35 @@ export const INSPECT_TIMELINE_TITLE = i18n.translate( } ); +export const PROCESS_KPI_TITLE = i18n.translate( + 'xpack.securitySolution.timeline.kpis.processKpiTitle', + { + defaultMessage: 'Processes', + } +); + +export const HOST_KPI_TITLE = i18n.translate('xpack.securitySolution.timeline.kpis.hostKpiTitle', { + defaultMessage: 'Hosts', +}); + +export const SOURCE_IP_KPI_TITLE = i18n.translate( + 'xpack.securitySolution.timeline.kpis.sourceIpKpiTitle', + { + defaultMessage: 'Source IPs', + } +); + +export const DESTINATION_IP_KPI_TITLE = i18n.translate( + 'xpack.securitySolution.timeline.kpis.destinationKpiTitle', + { + defaultMessage: 'Destination IPs', + } +); + +export const USER_KPI_TITLE = i18n.translate('xpack.securitySolution.timeline.kpis.userKpiTitle', { + defaultMessage: 'Users', +}); + export const TIMELINE_TOGGLE_BUTTON_ARIA_LABEL = ({ isOpen, title, diff --git a/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/index.tsx index 92e0ac757cc39..10e8ca42f35ae 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/notes/add_note/index.tsx @@ -9,7 +9,6 @@ import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, - EuiFocusTrap, EuiScreenReaderOnly, } from '@elastic/eui'; import React, { useCallback } from 'react'; @@ -83,31 +82,29 @@ export const AddNote = React.memo<{ return ( - -
- -

{i18n.YOU_ARE_EDITING_A_NOTE}

-
- - - {onCancelAddNote != null ? ( - - - - ) : null} +
+ +

{i18n.YOU_ARE_EDITING_A_NOTE}

+
+ + + {onCancelAddNote != null ? ( - - {i18n.ADD_NOTE} - + - -
- + ) : null} + + + {i18n.ADD_NOTE} + + +
+
); }); diff --git a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx index d6ea611660eda..2c7b917d373cc 100644 --- a/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx +++ b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx @@ -18,6 +18,8 @@ import { timelineActions } from '../../../store/timeline'; import { NOTE_CONTENT_CLASS_NAME } from '../../timeline/body/helpers'; import * as i18n from './translations'; import { TimelineTabs } from '../../../../../common/types/timeline'; +import { useDeepEqualSelector } from '../../../../common/hooks/use_selector'; +import { sourcererSelectors } from '../../../../common/store'; export const NotePreviewsContainer = styled.section` padding-top: ${({ theme }) => `${theme.eui.euiSizeS}`}; @@ -35,6 +37,12 @@ const ToggleEventDetailsButtonComponent: React.FC timelineId, }) => { const dispatch = useDispatch(); + const existingIndexNamesSelector = useMemo( + () => sourcererSelectors.getAllExistingIndexNamesSelector(), + [] + ); + const existingIndexNames = useDeepEqualSelector(existingIndexNamesSelector); + const handleClick = useCallback(() => { dispatch( timelineActions.toggleExpandedEvent({ @@ -42,12 +50,11 @@ const ToggleEventDetailsButtonComponent: React.FC timelineId, event: { eventId, - // we don't store yet info about event index name in note - indexName: '', + indexName: existingIndexNames.join(','), }, }) ); - }, [dispatch, eventId, timelineId]); + }, [dispatch, eventId, existingIndexNames, timelineId]); return ( { + const { data, notifications } = useKibana().services; + const refetch = useRef(noop); + const abortCtrl = useRef(new AbortController()); + const didCancel = useRef(false); + const [loading, setLoading] = useState(false); + const [timelineKpiRequest, setTimelineKpiRequest] = useState( + null + ); + const [ + timelineKpiResponse, + setTimelineKpiResponse, + ] = useState(null); + const timelineKpiSearch = useCallback( + (request: TimelineRequestBasicOptions | null) => { + if (request == null) { + return; + } + didCancel.current = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + + const searchSubscription$ = data.search + .search(request, { + strategy: 'securitySolutionTimelineSearchStrategy', + abortSignal: abortCtrl.current.signal, + }) + .subscribe({ + next: (response) => { + if (isCompleteResponse(response)) { + if (!didCancel.current) { + setLoading(false); + setTimelineKpiResponse(response); + } + searchSubscription$.unsubscribe(); + } else if (isErrorResponse(response)) { + if (!didCancel.current) { + setLoading(false); + } + notifications.toasts.addWarning('An error has occurred'); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!didCancel.current) { + setLoading(false); + } + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger('Failed to load KPIs'); + } + }, + }); + }; + abortCtrl.current.abort(); + asyncSearch(); + refetch.current = asyncSearch; + }, + [data.search, notifications.toasts] + ); + + useEffect(() => { + setTimelineKpiRequest((prevRequest) => { + const myRequest = { + ...(prevRequest ?? {}), + docValueFields, + defaultIndex, + timerange, + filterQuery, + factoryQueryType: TimelineEventsQueries.kpi, + }; + if (!deepEqual(prevRequest, myRequest)) { + return myRequest; + } + return prevRequest; + }); + }, [docValueFields, defaultIndex, timerange, filterQuery]); + + useEffect(() => { + if (!isBlankTimeline) { + timelineKpiSearch(timelineKpiRequest); + } else { + setLoading(false); + setTimelineKpiResponse(null); + } + return () => { + didCancel.current = true; + abortCtrl.current.abort(); + }; + }, [isBlankTimeline, timelineKpiRequest, timelineKpiSearch]); + return [loading, timelineKpiResponse]; +}; diff --git a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_favorite.ts b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_favorite.ts index f99b940032583..ce14ae98afe06 100644 --- a/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_favorite.ts +++ b/x-pack/plugins/security_solution/public/timelines/store/timeline/epic_favorite.ts @@ -27,6 +27,7 @@ import { refetchQueries } from './refetch_queries'; import { myEpicTimelineId } from './my_epic_timeline_id'; import { ActionTimeline, TimelineById } from './types'; import { inputsModel } from '../../../common/store/inputs'; +import { TimelineType } from '../../../../common/types/timeline'; export const timelineFavoriteActionsType = [updateIsFavorite.type]; @@ -48,6 +49,9 @@ export const epicPersistTimelineFavorite = ( fetchPolicy: 'no-cache', variables: { timelineId: myEpicTimelineId.getTimelineId(), + templateTimelineId: timeline[action.payload.id].templateTimelineId, + templateTimelineVersion: timeline[action.payload.id].templateTimelineVersion, + timelineType: timeline[action.payload.id].timelineType ?? TimelineType.default, }, refetchQueries, }) @@ -96,6 +100,12 @@ export const epicPersistTimelineFavorite = ( myEpicTimelineId.setTimelineVersion( updatedTimeline[get('payload.id', checkAction)].version ); + myEpicTimelineId.setTemplateTimelineId( + updatedTimeline[get('payload.id', checkAction)].templateTimelineId + ); + myEpicTimelineId.setTemplateTimelineVersion( + updatedTimeline[get('payload.id', checkAction)].templateTimelineVersion + ); return true; } return false; diff --git a/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator_script.ts b/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator_script.ts index 9ad094086b632..de0cec3c06033 100644 --- a/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator_script.ts +++ b/x-pack/plugins/security_solution/scripts/endpoint/resolver_generator_script.ts @@ -5,9 +5,10 @@ */ /* eslint-disable no-console */ import yargs from 'yargs'; +import fs from 'fs'; import { Client, ClientOptions } from '@elastic/elasticsearch'; import { ResponseError } from '@elastic/elasticsearch/lib/errors'; -import { KbnClient, ToolingLog } from '@kbn/dev-utils'; +import { KbnClient, ToolingLog, CA_CERT_PATH } from '@kbn/dev-utils'; import { AxiosResponse } from 'axios'; import { indexHostsAndAlerts } from '../../common/endpoint/index_data'; import { ANCESTRY_LIMIT, EndpointDocGenerator } from '../../common/endpoint/generate_data'; @@ -202,15 +203,41 @@ async function main() { type: 'boolean', default: false, }, + ssl: { + alias: 'ssl', + describe: 'Use https for elasticsearch and kbn clients', + type: 'boolean', + default: false, + }, }).argv; + let ca: Buffer; + let kbnClient: KbnClientWithApiKeySupport; + let clientOptions: ClientOptions; - const kbnClient = new KbnClientWithApiKeySupport({ - log: new ToolingLog({ - level: 'info', - writeTo: process.stdout, - }), - url: argv.kibana, - }); + if (argv.ssl) { + ca = fs.readFileSync(CA_CERT_PATH); + const url = argv.kibana.replace('http:', 'https:'); + const node = argv.node.replace('http:', 'https:'); + kbnClient = new KbnClientWithApiKeySupport({ + log: new ToolingLog({ + level: 'info', + writeTo: process.stdout, + }), + url, + certificateAuthorities: [ca], + }); + clientOptions = { node, ssl: { ca: [ca] } }; + } else { + kbnClient = new KbnClientWithApiKeySupport({ + log: new ToolingLog({ + level: 'info', + writeTo: process.stdout, + }), + url: argv.kibana, + }); + clientOptions = { node: argv.node }; + } + const client = new Client(clientOptions); try { await doIngestSetup(kbnClient); @@ -219,9 +246,6 @@ async function main() { process.exit(1); } - const clientOptions: ClientOptions = { node: argv.node }; - const client = new Client(clientOptions); - if (argv.delete) { await deleteIndices( [argv.eventIndex, argv.metadataIndex, argv.policyIndex, argv.alertIndex], diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts index 5049104a96401..038f8f7867e4d 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts @@ -252,16 +252,14 @@ describe('test endpoint route', () => { ); expect(mockScopedClient.callAsCurrentUser).toHaveBeenCalledTimes(1); - expect(mockScopedClient.callAsCurrentUser.mock.calls[0][1]?.body?.query).toEqual({ - bool: { - must_not: { - terms: { - 'HostDetails.elastic.agent.id': [ - '00000000-0000-0000-0000-000000000000', - '11111111-1111-1111-1111-111111111111', - ], - }, - }, + expect( + mockScopedClient.callAsCurrentUser.mock.calls[0][1]?.body?.query.bool.must_not + ).toContainEqual({ + terms: { + 'elastic.agent.id': [ + '00000000-0000-0000-0000-000000000000', + '11111111-1111-1111-1111-111111111111', + ], }, }); expect(routeConfig.options).toEqual({ @@ -311,35 +309,46 @@ describe('test endpoint route', () => { ); expect(mockScopedClient.callAsCurrentUser).toBeCalled(); - expect(mockScopedClient.callAsCurrentUser.mock.calls[0][1]?.body?.query).toEqual({ + expect( + // KQL filter to be passed through + mockScopedClient.callAsCurrentUser.mock.calls[0][1]?.body?.query.bool.must + ).toContainEqual({ bool: { - must: [ - { - bool: { - must_not: { - terms: { - 'HostDetails.elastic.agent.id': [ - '00000000-0000-0000-0000-000000000000', - '11111111-1111-1111-1111-111111111111', - ], + must_not: { + bool: { + should: [ + { + match: { + 'host.ip': '10.140.73.246', }, }, + ], + minimum_should_match: 1, + }, + }, + }, + }); + expect( + mockScopedClient.callAsCurrentUser.mock.calls[0][1]?.body?.query.bool.must + ).toContainEqual({ + bool: { + must_not: [ + { + terms: { + 'elastic.agent.id': [ + '00000000-0000-0000-0000-000000000000', + '11111111-1111-1111-1111-111111111111', + ], }, }, { - bool: { - must_not: { - bool: { - should: [ - { - match: { - 'host.ip': '10.140.73.246', - }, - }, - ], - minimum_should_match: 1, - }, - }, + terms: { + // here we DO want to see both schemas are present + // to make this schema-compatible forward and back + 'HostDetails.elastic.agent.id': [ + '00000000-0000-0000-0000-000000000000', + '11111111-1111-1111-1111-111111111111', + ], }, }, ], diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata_v1.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata_v1.test.ts index b879272862d48..b0d5286cda6ff 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata_v1.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata_v1.test.ts @@ -145,16 +145,14 @@ describe('test endpoint route v1', () => { ); expect(mockScopedClient.callAsCurrentUser).toHaveBeenCalledTimes(1); - expect(mockScopedClient.callAsCurrentUser.mock.calls[0][1]?.body?.query).toEqual({ - bool: { - must_not: { - terms: { - 'elastic.agent.id': [ - '00000000-0000-0000-0000-000000000000', - '11111111-1111-1111-1111-111111111111', - ], - }, - }, + expect( + mockScopedClient.callAsCurrentUser.mock.calls[0][1]?.body?.query.bool.must_not + ).toContainEqual({ + terms: { + 'elastic.agent.id': [ + '00000000-0000-0000-0000-000000000000', + '11111111-1111-1111-1111-111111111111', + ], }, }); expect(routeConfig.options).toEqual({ authRequired: true, tags: ['access:securitySolution'] }); @@ -201,35 +199,47 @@ describe('test endpoint route v1', () => { ); expect(mockScopedClient.callAsCurrentUser).toBeCalled(); - expect(mockScopedClient.callAsCurrentUser.mock.calls[0][1]?.body?.query).toEqual({ + // needs to have the KQL filter passed through + expect( + mockScopedClient.callAsCurrentUser.mock.calls[0][1]?.body?.query.bool.must + ).toContainEqual({ bool: { - must: [ - { - bool: { - must_not: { - terms: { - 'elastic.agent.id': [ - '00000000-0000-0000-0000-000000000000', - '11111111-1111-1111-1111-111111111111', - ], + must_not: { + bool: { + should: [ + { + match: { + 'host.ip': '10.140.73.246', }, }, + ], + minimum_should_match: 1, + }, + }, + }, + }); + // and unenrolled should be filtered out. + expect( + mockScopedClient.callAsCurrentUser.mock.calls[0][1]?.body?.query.bool.must + ).toContainEqual({ + bool: { + must_not: [ + { + terms: { + 'elastic.agent.id': [ + '00000000-0000-0000-0000-000000000000', + '11111111-1111-1111-1111-111111111111', + ], }, }, { - bool: { - must_not: { - bool: { - should: [ - { - match: { - 'host.ip': '10.140.73.246', - }, - }, - ], - minimum_should_match: 1, - }, - }, + terms: { + // we actually don't care about HostDetails in v1 queries, but + // harder to set up the expectation to ignore its inclusion succinctly + 'HostDetails.elastic.agent.id': [ + '00000000-0000-0000-0000-000000000000', + '11111111-1111-1111-1111-111111111111', + ], }, }, ], diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.test.ts index 88b141120a904..7caad5aee065f 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.test.ts @@ -12,7 +12,46 @@ import { metadataQueryStrategyV2 } from './support/query_strategies'; describe('query builder', () => { describe('MetadataListESQuery', () => { - it('test default query params for all endpoints metadata when no params or body is provided', async () => { + it('queries the correct index', async () => { + const mockRequest = httpServerMock.createKibanaRequest({ body: {} }); + const query = await kibanaRequestToMetadataListESQuery( + mockRequest, + { + logFactory: loggingSystemMock.create(), + service: new EndpointAppContextService(), + config: () => Promise.resolve(createMockConfig()), + }, + metadataQueryStrategyV2() + ); + expect(query.index).toEqual(metadataCurrentIndexPattern); + }); + + it('sorts using *event.created', async () => { + const mockRequest = httpServerMock.createKibanaRequest({ body: {} }); + const query = await kibanaRequestToMetadataListESQuery( + mockRequest, + { + logFactory: loggingSystemMock.create(), + service: new EndpointAppContextService(), + config: () => Promise.resolve(createMockConfig()), + }, + metadataQueryStrategyV2() + ); + expect(query.body.sort).toContainEqual({ + 'event.created': { + order: 'desc', + unmapped_type: 'date', + }, + }); + expect(query.body.sort).toContainEqual({ + 'HostDetails.event.created': { + order: 'desc', + unmapped_type: 'date', + }, + }); + }); + + it('queries for all endpoints when no specific parameters requested', async () => { const mockRequest = httpServerMock.createKibanaRequest({ body: {}, }); @@ -25,82 +64,43 @@ describe('query builder', () => { }, metadataQueryStrategyV2() ); - expect(query).toEqual({ - body: { - query: { - match_all: {}, - }, - sort: [ - { - 'HostDetails.event.created': { - order: 'desc', - }, - }, - ], - track_total_hits: true, - }, - from: 0, - size: 10, - index: metadataCurrentIndexPattern, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as Record); + expect(query.body.query).toHaveProperty('match_all'); }); - it( - 'test default query params for all endpoints metadata when no params or body is provided ' + - 'with unenrolled host ids excluded', - async () => { - const unenrolledElasticAgentId = '1fdca33f-799f-49f4-939c-ea4383c77672'; - const mockRequest = httpServerMock.createKibanaRequest({ - body: {}, - }); - const query = await kibanaRequestToMetadataListESQuery( - mockRequest, - { - logFactory: loggingSystemMock.create(), - service: new EndpointAppContextService(), - config: () => Promise.resolve(createMockConfig()), - }, - metadataQueryStrategyV2(), - { - unenrolledAgentIds: [unenrolledElasticAgentId], - } - ); + it('excludes unenrolled elastic agents when they exist, by default', async () => { + const unenrolledElasticAgentId = '1fdca33f-799f-49f4-939c-ea4383c77672'; + const mockRequest = httpServerMock.createKibanaRequest({ + body: {}, + }); + const query = await kibanaRequestToMetadataListESQuery( + mockRequest, + { + logFactory: loggingSystemMock.create(), + service: new EndpointAppContextService(), + config: () => Promise.resolve(createMockConfig()), + }, + metadataQueryStrategyV2(), + { + unenrolledAgentIds: [unenrolledElasticAgentId], + } + ); - expect(query).toEqual({ - body: { - query: { - bool: { - must_not: { - terms: { - 'HostDetails.elastic.agent.id': [unenrolledElasticAgentId], - }, - }, - }, - }, - sort: [ - { - 'HostDetails.event.created': { - order: 'desc', - }, - }, - ], - track_total_hits: true, - }, - from: 0, - size: 10, - index: metadataCurrentIndexPattern, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as Record); - } - ); + expect(query.body.query).toEqual({ + bool: { + must_not: [ + { terms: { 'elastic.agent.id': [unenrolledElasticAgentId] } }, + { terms: { 'HostDetails.elastic.agent.id': [unenrolledElasticAgentId] } }, + ], + }, + }); + }); }); describe('test query builder with kql filter', () => { it('test default query params for all endpoints metadata when body filter is provided', async () => { const mockRequest = httpServerMock.createKibanaRequest({ body: { - filters: { kql: 'not HostDetails.host.ip:10.140.73.246' }, + filters: { kql: 'not host.ip:10.140.73.246' }, }, }); const query = await kibanaRequestToMetadataListESQuery( @@ -113,44 +113,22 @@ describe('query builder', () => { metadataQueryStrategyV2() ); - expect(query).toEqual({ - body: { - query: { + expect(query.body.query.bool.must).toContainEqual({ + bool: { + must_not: { bool: { - must: [ + should: [ { - bool: { - must_not: { - bool: { - should: [ - { - match: { - 'HostDetails.host.ip': '10.140.73.246', - }, - }, - ], - minimum_should_match: 1, - }, - }, + match: { + 'host.ip': '10.140.73.246', }, }, ], + minimum_should_match: 1, }, }, - sort: [ - { - 'HostDetails.event.created': { - order: 'desc', - }, - }, - ], - track_total_hits: true, }, - from: 0, - size: 10, - index: metadataCurrentIndexPattern, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as Record); + }); }); it( @@ -160,7 +138,7 @@ describe('query builder', () => { const unenrolledElasticAgentId = '1fdca33f-799f-49f4-939c-ea4383c77672'; const mockRequest = httpServerMock.createKibanaRequest({ body: { - filters: { kql: 'not HostDetails.host.ip:10.140.73.246' }, + filters: { kql: 'not host.ip:10.140.73.246' }, }, }); const query = await kibanaRequestToMetadataListESQuery( @@ -176,69 +154,56 @@ describe('query builder', () => { } ); - expect(query).toEqual({ - body: { - query: { + expect(query.body.query.bool.must).toContainEqual({ + bool: { + must_not: [ + // both of these should exist, since the schema can be *either* + { terms: { 'elastic.agent.id': [unenrolledElasticAgentId] } }, + { terms: { 'HostDetails.elastic.agent.id': [unenrolledElasticAgentId] } }, + ], + }, + }); + expect(query.body.query.bool.must).toContainEqual({ + bool: { + must_not: { bool: { - must: [ - { - bool: { - must_not: { - terms: { - 'HostDetails.elastic.agent.id': [unenrolledElasticAgentId], - }, - }, - }, - }, + should: [ { - bool: { - must_not: { - bool: { - should: [ - { - match: { - 'HostDetails.host.ip': '10.140.73.246', - }, - }, - ], - minimum_should_match: 1, - }, - }, + match: { + 'host.ip': '10.140.73.246', }, }, ], + minimum_should_match: 1, }, }, - sort: [ - { - 'HostDetails.event.created': { - order: 'desc', - }, - }, - ], - track_total_hits: true, }, - from: 0, - size: 10, - index: metadataCurrentIndexPattern, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as Record); + }); } ); }); describe('MetadataGetQuery', () => { + it('searches the correct index', () => { + const query = getESQueryHostMetadataByID('nonsense-id', metadataQueryStrategyV2()); + expect(query.index).toEqual(metadataCurrentIndexPattern); + }); + it('searches for the correct ID', () => { const mockID = 'AABBCCDD-0011-2233-AA44-DEADBEEF8899'; const query = getESQueryHostMetadataByID(mockID, metadataQueryStrategyV2()); - expect(query).toEqual({ - body: { - query: { match: { 'HostDetails.agent.id': mockID } }, - sort: [{ 'HostDetails.event.created': { order: 'desc' } }], - size: 1, - }, - index: metadataCurrentIndexPattern, + expect(query.body.query.bool.filter[0].bool.should).toContainEqual({ + term: { 'agent.id': mockID }, + }); + }); + + it('supports HostDetails in schema for backwards compat', () => { + const mockID = 'AABBCCDD-0011-2233-AA44-DEADBEEF8899'; + const query = getESQueryHostMetadataByID(mockID, metadataQueryStrategyV2()); + + expect(query.body.query.bool.filter[0].bool.should).toContainEqual({ + term: { 'HostDetails.agent.id': mockID }, }); }); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.ts index 7980fc83358b1..253f7a54d3002 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders.ts @@ -12,6 +12,26 @@ export interface QueryBuilderOptions { statusAgentIDs?: string[]; } +// sort using either event.created, or HostDetails.event.created, +// depending on whichever exists. This works for QueryStrat v1 and v2, and the v2+ schema change. +// using unmapped_type avoids errors when the given field doesn't exist, and sets to the 0-value for that type +// effectively ignoring it +// https://www.elastic.co/guide/en/elasticsearch/reference/current/sort-search-results.html#_ignoring_unmapped_fields +const MetadataSortMethod = [ + { + 'event.created': { + order: 'desc', + unmapped_type: 'date', + }, + }, + { + 'HostDetails.event.created': { + order: 'desc', + unmapped_type: 'date', + }, + }, +]; + export async function kibanaRequestToMetadataListESQuery( // eslint-disable-next-line @typescript-eslint/no-explicit-any request: KibanaRequest, @@ -31,7 +51,7 @@ export async function kibanaRequestToMetadataListESQuery( queryBuilderOptions?.statusAgentIDs! ), ...metadataQueryStrategy.extraBodyProperties, - sort: metadataQueryStrategy.sortProperty, + sort: MetadataSortMethod, }, from: pagingProperties.pageIndex * pagingProperties.pageSize, size: pagingProperties.pageSize, @@ -68,23 +88,29 @@ function buildQueryBody( statusAgentIDs: string[] | undefined // eslint-disable-next-line @typescript-eslint/no-explicit-any ): Record { + // the filtered properties may be preceded by 'HostDetails' under an older index mapping const filterUnenrolledAgents = unerolledAgentIds && unerolledAgentIds.length > 0 ? { - must_not: { - terms: { - [metadataQueryStrategy.elasticAgentIdProperty]: unerolledAgentIds, - }, - }, + must_not: [ + { terms: { 'elastic.agent.id': unerolledAgentIds } }, // OR + { terms: { 'HostDetails.elastic.agent.id': unerolledAgentIds } }, + ], } : null; const filterStatusAgents = statusAgentIDs ? { - must: { - terms: { - [metadataQueryStrategy.elasticAgentIdProperty]: statusAgentIDs, + filter: [ + { + bool: { + // OR's the two together + should: [ + { terms: { 'elastic.agent.id': statusAgentIDs } }, + { terms: { 'HostDetails.elastic.agent.id': statusAgentIDs } }, + ], + }, }, - }, + ], } : null; @@ -122,11 +148,20 @@ export function getESQueryHostMetadataByID( return { body: { query: { - match: { - [metadataQueryStrategy.hostIdProperty]: agentID, + bool: { + filter: [ + { + bool: { + should: [ + { term: { 'agent.id': agentID } }, + { term: { 'HostDetails.agent.id': agentID } }, + ], + }, + }, + ], }, }, - sort: metadataQueryStrategy.sortProperty, + sort: MetadataSortMethod, size: 1, }, index: metadataQueryStrategy.index, diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders_v1.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders_v1.test.ts index ca65d18bb9f6b..192ca773d3338 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders_v1.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/query_builders_v1.test.ts @@ -25,39 +25,24 @@ describe('query builder v1', () => { }, metadataQueryStrategyV1() ); - expect(query).toEqual({ - body: { - query: { - match_all: {}, - }, - collapse: { + + expect(query.body.query).toHaveProperty('match_all'); // no filtering + expect(query.body.collapse).toEqual({ + field: 'agent.id', + inner_hits: { + name: 'most_recent', + size: 1, + sort: [{ 'event.created': 'desc' }], + }, + }); + expect(query.body.aggs).toEqual({ + total: { + cardinality: { field: 'agent.id', - inner_hits: { - name: 'most_recent', - size: 1, - sort: [{ 'event.created': 'desc' }], - }, }, - aggs: { - total: { - cardinality: { - field: 'agent.id', - }, - }, - }, - sort: [ - { - 'event.created': { - order: 'desc', - }, - }, - ], }, - from: 0, - size: 10, - index: metadataIndexPattern, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as Record); + }); + expect(query.index).toEqual(metadataIndexPattern); }); it( @@ -80,45 +65,10 @@ describe('query builder v1', () => { unenrolledAgentIds: [unenrolledElasticAgentId], } ); - expect(query).toEqual({ - body: { - query: { - bool: { - must_not: { - terms: { - 'elastic.agent.id': [unenrolledElasticAgentId], - }, - }, - }, - }, - collapse: { - field: 'agent.id', - inner_hits: { - name: 'most_recent', - size: 1, - sort: [{ 'event.created': 'desc' }], - }, - }, - aggs: { - total: { - cardinality: { - field: 'agent.id', - }, - }, - }, - sort: [ - { - 'event.created': { - order: 'desc', - }, - }, - ], - }, - from: 0, - size: 10, - index: metadataIndexPattern, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as Record); + expect(Object.keys(query.body.query.bool)).toEqual(['must_not']); // only filtering out unenrolled + expect(query.body.query.bool.must_not).toContainEqual({ + terms: { 'elastic.agent.id': [unenrolledElasticAgentId] }, + }); } ); }); @@ -139,59 +89,23 @@ describe('query builder v1', () => { }, metadataQueryStrategyV1() ); - - expect(query).toEqual({ - body: { - query: { + expect(query.body.query.bool.must).toHaveLength(1); // should not be any other filtering happening + expect(query.body.query.bool.must).toContainEqual({ + bool: { + must_not: { bool: { - must: [ + should: [ { - bool: { - must_not: { - bool: { - should: [ - { - match: { - 'host.ip': '10.140.73.246', - }, - }, - ], - minimum_should_match: 1, - }, - }, + match: { + 'host.ip': '10.140.73.246', }, }, ], + minimum_should_match: 1, }, }, - collapse: { - field: 'agent.id', - inner_hits: { - name: 'most_recent', - size: 1, - sort: [{ 'event.created': 'desc' }], - }, - }, - aggs: { - total: { - cardinality: { - field: 'agent.id', - }, - }, - }, - sort: [ - { - 'event.created': { - order: 'desc', - }, - }, - ], }, - from: 0, - size: 10, - index: metadataIndexPattern, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as Record); + }); }); it( @@ -217,67 +131,34 @@ describe('query builder v1', () => { } ); - expect(query).toEqual({ - body: { - query: { + expect(query.body.query.bool.must.length).toBeGreaterThan(1); + // unenrollment filter should be there + expect(query.body.query.bool.must).toContainEqual({ + bool: { + must_not: [ + { terms: { 'elastic.agent.id': [unenrolledElasticAgentId] } }, + // below is not actually necessary behavior for v1, but hard to structure the test to ignore it + { terms: { 'HostDetails.elastic.agent.id': [unenrolledElasticAgentId] } }, + ], + }, + }); + // and KQL should also be there + expect(query.body.query.bool.must).toContainEqual({ + bool: { + must_not: { bool: { - must: [ + should: [ { - bool: { - must_not: { - terms: { - 'elastic.agent.id': [unenrolledElasticAgentId], - }, - }, - }, - }, - { - bool: { - must_not: { - bool: { - should: [ - { - match: { - 'host.ip': '10.140.73.246', - }, - }, - ], - minimum_should_match: 1, - }, - }, + match: { + 'host.ip': '10.140.73.246', }, }, ], + minimum_should_match: 1, }, }, - collapse: { - field: 'agent.id', - inner_hits: { - name: 'most_recent', - size: 1, - sort: [{ 'event.created': 'desc' }], - }, - }, - aggs: { - total: { - cardinality: { - field: 'agent.id', - }, - }, - }, - sort: [ - { - 'event.created': { - order: 'desc', - }, - }, - ], }, - from: 0, - size: 10, - index: metadataIndexPattern, - // eslint-disable-next-line @typescript-eslint/no-explicit-any - } as Record); + }); } ); }); @@ -287,13 +168,8 @@ describe('query builder v1', () => { const mockID = 'AABBCCDD-0011-2233-AA44-DEADBEEF8899'; const query = getESQueryHostMetadataByID(mockID, metadataQueryStrategyV1()); - expect(query).toEqual({ - body: { - query: { match: { 'agent.id': mockID } }, - sort: [{ 'event.created': { order: 'desc' } }], - size: 1, - }, - index: metadataIndexPattern, + expect(query.body.query.bool.filter[0].bool.should).toContainEqual({ + term: { 'agent.id': mockID }, }); }); }); diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/query_strategies.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/query_strategies.ts index 5c25ff0b99faa..7c1ffa0c9383c 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/query_strategies.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/query_strategies.ts @@ -8,11 +8,7 @@ import { metadataCurrentIndexPattern, metadataIndexPattern, } from '../../../../../common/endpoint/constants'; -import { - HostMetadata, - HostMetadataDetails, - MetadataQueryStrategyVersions, -} from '../../../../../common/endpoint/types'; +import { HostMetadata, MetadataQueryStrategyVersions } from '../../../../../common/endpoint/types'; import { HostListQueryResult, HostQueryResult, MetadataQueryStrategy } from '../../../types'; interface HitSource { @@ -22,15 +18,6 @@ interface HitSource { export function metadataQueryStrategyV1(): MetadataQueryStrategy { return { index: metadataIndexPattern, - elasticAgentIdProperty: 'elastic.agent.id', - hostIdProperty: 'agent.id', - sortProperty: [ - { - 'event.created': { - order: 'desc', - }, - }, - ], extraBodyProperties: { collapse: { field: 'agent.id', @@ -49,7 +36,7 @@ export function metadataQueryStrategyV1(): MetadataQueryStrategy { }, }, queryResponseToHostListResult: ( - searchResponse: SearchResponse + searchResponse: SearchResponse ): HostListQueryResult => { const response = searchResponse as SearchResponse; return { @@ -61,9 +48,7 @@ export function metadataQueryStrategyV1(): MetadataQueryStrategy { queryStrategyVersion: MetadataQueryStrategyVersions.VERSION_1, }; }, - queryResponseToHostResult: ( - searchResponse: SearchResponse - ): HostQueryResult => { + queryResponseToHostResult: (searchResponse: SearchResponse): HostQueryResult => { const response = searchResponse as SearchResponse; return { resultLength: response.hits.hits.length, @@ -77,42 +62,46 @@ export function metadataQueryStrategyV1(): MetadataQueryStrategy { export function metadataQueryStrategyV2(): MetadataQueryStrategy { return { index: metadataCurrentIndexPattern, - elasticAgentIdProperty: 'HostDetails.elastic.agent.id', - hostIdProperty: 'HostDetails.agent.id', - sortProperty: [ - { - 'HostDetails.event.created': { - order: 'desc', - }, - }, - ], extraBodyProperties: { track_total_hits: true, }, queryResponseToHostListResult: ( - searchResponse: SearchResponse + searchResponse: SearchResponse ): HostListQueryResult => { - const response = searchResponse as SearchResponse; + const response = searchResponse as SearchResponse< + HostMetadata | { HostDetails: HostMetadata } + >; + const list = + response.hits.hits.length > 0 + ? response.hits.hits.map((entry) => stripHostDetails(entry._source)) + : []; + return { resultLength: ((response.hits?.total as unknown) as { value: number; relation: string }).value || 0, - resultList: - response.hits.hits.length > 0 - ? response.hits.hits.map((entry) => entry._source.HostDetails) - : [], + resultList: list, queryStrategyVersion: MetadataQueryStrategyVersions.VERSION_2, }; }, queryResponseToHostResult: ( - searchResponse: SearchResponse + searchResponse: SearchResponse ): HostQueryResult => { - const response = searchResponse as SearchResponse; + const response = searchResponse as SearchResponse< + HostMetadata | { HostDetails: HostMetadata } + >; return { resultLength: response.hits.hits.length, result: - response.hits.hits.length > 0 ? response.hits.hits[0]._source.HostDetails : undefined, + response.hits.hits.length > 0 + ? stripHostDetails(response.hits.hits[0]._source) + : undefined, queryStrategyVersion: MetadataQueryStrategyVersions.VERSION_2, }; }, }; } + +// remove the top-level 'HostDetails' property if found, from previous schemas +function stripHostDetails(host: HostMetadata | { HostDetails: HostMetadata }): HostMetadata { + return 'HostDetails' in host ? host.HostDetails : host; +} diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/test_support.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/test_support.ts index ac6ee380c8b06..d42672d3f7a0c 100644 --- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/test_support.ts +++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/support/test_support.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { SearchResponse } from 'elasticsearch'; -import { HostMetadata, HostMetadataDetails } from '../../../../../common/endpoint/types'; +import { HostMetadata } from '../../../../../common/endpoint/types'; export function createV1SearchResponse(hostMetadata?: HostMetadata): SearchResponse { return ({ @@ -62,9 +62,7 @@ export function createV1SearchResponse(hostMetadata?: HostMetadata): SearchRespo } as unknown) as SearchResponse; } -export function createV2SearchResponse( - hostMetadata?: HostMetadata -): SearchResponse { +export function createV2SearchResponse(hostMetadata?: HostMetadata): SearchResponse { return ({ took: 15, timed_out: false, @@ -87,17 +85,12 @@ export function createV2SearchResponse( _id: '8FhM0HEBYyRTvb6lOQnw', _score: null, _source: { - agent: { - id: '1e3472bb-5c20-4946-b469-b5af1a809e4f', - }, - HostDetails: { - ...hostMetadata, - }, + ...hostMetadata, }, sort: [1588337587997], }, ] : [], }, - } as unknown) as SearchResponse; + } as unknown) as SearchResponse; } diff --git a/x-pack/plugins/security_solution/server/endpoint/types.ts b/x-pack/plugins/security_solution/server/endpoint/types.ts index 0e4f03c56b9f0..f61b9b7a80f3a 100644 --- a/x-pack/plugins/security_solution/server/endpoint/types.ts +++ b/x-pack/plugins/security_solution/server/endpoint/types.ts @@ -8,11 +8,7 @@ import { SearchResponse } from 'elasticsearch'; import { ConfigType } from '../config'; import { EndpointAppContextService } from './endpoint_app_context_services'; import { JsonObject } from '../../../../../src/plugins/kibana_utils/common'; -import { - HostMetadata, - HostMetadataDetails, - MetadataQueryStrategyVersions, -} from '../../common/endpoint/types'; +import { HostMetadata, MetadataQueryStrategyVersions } from '../../common/endpoint/types'; /** * The context for Endpoint apps. @@ -41,14 +37,9 @@ export interface HostQueryResult { export interface MetadataQueryStrategy { index: string; - elasticAgentIdProperty: string; - hostIdProperty: string; - sortProperty: JsonObject[]; extraBodyProperties?: JsonObject; queryResponseToHostListResult: ( - searchResponse: SearchResponse + searchResponse: SearchResponse ) => HostListQueryResult; - queryResponseToHostResult: ( - searchResponse: SearchResponse - ) => HostQueryResult; + queryResponseToHostResult: (searchResponse: SearchResponse) => HostQueryResult; } diff --git a/x-pack/plugins/security_solution/server/graphql/timeline/resolvers.ts b/x-pack/plugins/security_solution/server/graphql/timeline/resolvers.ts index fc14663b138b2..3d049374d6c57 100644 --- a/x-pack/plugins/security_solution/server/graphql/timeline/resolvers.ts +++ b/x-pack/plugins/security_solution/server/graphql/timeline/resolvers.ts @@ -7,6 +7,7 @@ import { AppResolverWithFields, AppResolverOf } from '../../lib/framework'; import { MutationResolvers, QueryResolvers } from '../types'; import { Timeline } from '../../lib/timeline/saved_object'; +import { TimelineType } from '../../../common/types/timeline'; export type QueryTimelineResolver = AppResolverOf; @@ -63,7 +64,13 @@ export const createTimelineResolvers = ( return true; }, async persistFavorite(root, args, { req }) { - return libs.timeline.persistFavorite(req, args.timelineId || null); + return libs.timeline.persistFavorite( + req, + args.timelineId || null, + args.templateTimelineId || null, + args.templateTimelineVersion || null, + args.timelineType || TimelineType.default + ); }, async persistTimeline(root, args, { req }) { return libs.timeline.persistTimeline( diff --git a/x-pack/plugins/security_solution/server/graphql/timeline/schema.gql.ts b/x-pack/plugins/security_solution/server/graphql/timeline/schema.gql.ts index ca6c57f025faf..933c3e9123893 100644 --- a/x-pack/plugins/security_solution/server/graphql/timeline/schema.gql.ts +++ b/x-pack/plugins/security_solution/server/graphql/timeline/schema.gql.ts @@ -294,6 +294,9 @@ export const timelineSchema = gql` code: Float message: String savedObjectId: String! + templateTimelineId: String + templateTimelineVersion: Int + timelineType: TimelineType version: String! favorite: [FavoriteTimelineResult!] } @@ -320,7 +323,7 @@ export const timelineSchema = gql` extend type Mutation { "Persists a timeline" persistTimeline(id: ID, version: String, timeline: TimelineInput!): ResponseTimeline! - persistFavorite(timelineId: ID): ResponseFavoriteTimeline! + persistFavorite(timelineId: ID, templateTimelineId: String, templateTimelineVersion: Int, timelineType: TimelineType): ResponseFavoriteTimeline! deleteTimeline(id: [ID!]!): Boolean! } `; diff --git a/x-pack/plugins/security_solution/server/graphql/types.ts b/x-pack/plugins/security_solution/server/graphql/types.ts index 3ea964c0ee01f..783e61106387d 100644 --- a/x-pack/plugins/security_solution/server/graphql/types.ts +++ b/x-pack/plugins/security_solution/server/graphql/types.ts @@ -837,6 +837,12 @@ export interface ResponseFavoriteTimeline { savedObjectId: string; + templateTimelineId?: Maybe; + + templateTimelineVersion?: Maybe; + + timelineType?: Maybe; + version: string; favorite?: Maybe; @@ -1693,6 +1699,12 @@ export interface PersistTimelineMutationArgs { } export interface PersistFavoriteMutationArgs { timelineId?: Maybe; + + templateTimelineId?: Maybe; + + templateTimelineVersion?: Maybe; + + timelineType?: Maybe; } export interface DeleteTimelineMutationArgs { id: string[]; @@ -3419,6 +3431,12 @@ export namespace MutationResolvers { > = Resolver; export interface PersistFavoriteArgs { timelineId?: Maybe; + + templateTimelineId?: Maybe; + + templateTimelineVersion?: Maybe; + + timelineType?: Maybe; } export type DeleteTimelineResolver = Resolver< @@ -3492,6 +3510,12 @@ export namespace ResponseFavoriteTimelineResolvers { savedObjectId?: SavedObjectIdResolver; + templateTimelineId?: TemplateTimelineIdResolver, TypeParent, TContext>; + + templateTimelineVersion?: TemplateTimelineVersionResolver, TypeParent, TContext>; + + timelineType?: TimelineTypeResolver, TypeParent, TContext>; + version?: VersionResolver; favorite?: FavoriteResolver, TypeParent, TContext>; @@ -3512,6 +3536,21 @@ export namespace ResponseFavoriteTimelineResolvers { Parent = ResponseFavoriteTimeline, TContext = SiemContext > = Resolver; + export type TemplateTimelineIdResolver< + R = Maybe, + Parent = ResponseFavoriteTimeline, + TContext = SiemContext + > = Resolver; + export type TemplateTimelineVersionResolver< + R = Maybe, + Parent = ResponseFavoriteTimeline, + TContext = SiemContext + > = Resolver; + export type TimelineTypeResolver< + R = Maybe, + Parent = ResponseFavoriteTimeline, + TContext = SiemContext + > = Resolver; export type VersionResolver< R = string, Parent = ResponseFavoriteTimeline, diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/index/delete_all_index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/index/delete_all_index.ts index d165bf69f1da1..46d911bae8d69 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/index/delete_all_index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/index/delete_all_index.ts @@ -4,14 +4,42 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndicesDeleteParams } from 'elasticsearch'; +import { IndicesDeleteParams, Client } from 'elasticsearch'; import { CallWithRequest } from '../types'; export const deleteAllIndex = async ( - callWithRequest: CallWithRequest, - index: string + callWithRequest: CallWithRequest>, + pattern: string, + maxAttempts = 5 ): Promise => { - return callWithRequest('indices.delete', { - index, - }); + for (let attempt = 1; ; attempt++) { + if (attempt > maxAttempts) { + throw new Error( + `Failed to delete indexes with pattern [${pattern}] after ${maxAttempts} attempts` + ); + } + + // resolve pattern to concrete index names + const resp = await callWithRequest('indices.getAlias', { + index: pattern, + ignore: 404, + }); + + if (resp.status === 404) { + return true; + } + + const indices = Object.keys(resp) as string[]; + + // if no indexes exits then we're done with this pattern + if (!indices.length) { + return true; + } + + // delete the concrete indexes we found and try again until this pattern resolves to no indexes + await callWithRequest('indices.delete', { + index: indices, + ignoreUnavailable: true, + }); + } }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/index.ts new file mode 100644 index 0000000000000..34165ab7bc596 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/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 * as detectionsAdminUser from './detections_user.json'; +import * as detectionsAdminRole from './detections_role.json'; +export { detectionsAdminUser, detectionsAdminRole }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/index.ts new file mode 100644 index 0000000000000..ff3c017590f16 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/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 * as hunterUser from './detections_user.json'; +import * as hunterRole from './detections_role.json'; +export { hunterUser, hunterRole }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/index.ts new file mode 100644 index 0000000000000..f8d5e13024432 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/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. + */ +export * from './detections_admin'; +export * from './hunter'; +export * from './platform_engineer'; +export * from './reader'; +export * from './rule_author'; +export * from './soc_manager'; +export * from './t1_analyst'; +export * from './t2_analyst'; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/index.ts new file mode 100644 index 0000000000000..bc6ae6688d44a --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/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 * as platformEngineerUser from './detections_user.json'; +import * as platformEngineerRole from './detections_role.json'; +export { platformEngineerUser, platformEngineerRole }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/index.ts new file mode 100644 index 0000000000000..7344f8eb9d5e8 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/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 * as readerUser from './detections_user.json'; +import * as readerRole from './detections_role.json'; +export { readerUser, readerRole }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/index.ts new file mode 100644 index 0000000000000..748c3c8536f6e --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/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 * as ruleAuthorUser from './detections_user.json'; +import * as ruleAuthorRole from './detections_role.json'; +export { ruleAuthorUser, ruleAuthorRole }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/index.ts new file mode 100644 index 0000000000000..19a6dbaaea984 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/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 * as socManagerUser from './detections_user.json'; +import * as socManagerRole from './detections_role.json'; +export { socManagerUser, socManagerRole }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/index.ts new file mode 100644 index 0000000000000..3ea5cb0f42357 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/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 * as t1AnalystUser from './detections_user.json'; +import * as t1AnalystRole from './detections_role.json'; +export { t1AnalystUser, t1AnalystRole }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/index.ts new file mode 100644 index 0000000000000..99e5030399713 --- /dev/null +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/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 * as t2AnalystUser from './detections_user.json'; +import * as t2AnalystRole from './detections_role.json'; +export { t2AnalystUser, t2AnalystRole }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts index 3030bd8c52c70..2aa8981cc618b 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/search_after_bulk_create.ts @@ -70,6 +70,7 @@ export const searchAfterAndBulkCreate = async ({ interval, buildRuleMessage, }); + const tuplesToBeLogged = [...totalToFromTuples]; logger.debug(buildRuleMessage(`totalToFromTuples: ${totalToFromTuples.length}`)); while (totalToFromTuples.length > 0) { @@ -294,5 +295,6 @@ export const searchAfterAndBulkCreate = async ({ } } logger.debug(buildRuleMessage(`[+] completed bulk index of ${toReturn.createdSignalsCount}`)); + toReturn.totalToFromTuples = tuplesToBeLogged; return toReturn; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts index cce781f82e64f..bfdfe281ec038 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.test.ts @@ -151,6 +151,7 @@ describe('rules_notification_alert_type', () => { const value: Partial = { statusCode: 200, body: { + indices: ['index1', 'index2', 'index3', 'index4'], fields: { '@timestamp': { date: { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts index d08ab66af5683..a5df5983dc0d0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -9,7 +9,7 @@ import { Logger, KibanaRequest } from 'src/core/server'; import isEmpty from 'lodash/isEmpty'; import { chain, tryCatch } from 'fp-ts/lib/TaskEither'; -import { flow, pipe } from 'fp-ts/lib/function'; +import { flow } from 'fp-ts/lib/function'; import { toError, toPromise } from '../../../../common/fp_utils'; @@ -188,22 +188,14 @@ export const signalRulesAlertType = ({ try { if (!isEmpty(index)) { const hasTimestampOverride = timestampOverride != null && !isEmpty(timestampOverride); + const inputIndices = await getInputIndex(services, version, index); const [privileges, timestampFieldCaps] = await Promise.all([ - pipe( - { services, version, index }, - ({ services: svc, version: ver, index: idx }) => - pipe( - tryCatch(() => getInputIndex(svc, ver, idx), toError), - chain((indices) => tryCatch(() => checkPrivileges(svc, indices), toError)) - ), - toPromise - ), + checkPrivileges(services, inputIndices), services.scopedClusterClient.fieldCaps({ index, fields: hasTimestampOverride ? ['@timestamp', timestampOverride as string] : ['@timestamp'], - allow_no_indices: false, include_unmapped: true, }), ]); @@ -222,6 +214,7 @@ export const signalRulesAlertType = ({ wroteStatus, hasTimestampOverride ? (timestampOverride as string) : '@timestamp', timestampFieldCaps, + inputIndices, ruleStatusService, logger, buildRuleMessage @@ -670,6 +663,21 @@ export const signalRulesAlertType = ({ lastLookBackDate: result.lastLookBackDate?.toISOString(), }); } + + // adding this log line so we can get some information from cloud + logger.info( + buildRuleMessage( + `[+] Finished indexing ${result.createdSignalsCount} ${ + !isEmpty(result.totalToFromTuples) + ? `signals searched between date ranges ${JSON.stringify( + result.totalToFromTuples, + null, + 2 + )}` + : '' + }` + ) + ); } else { const errorMessage = buildRuleMessage( 'Bulk Indexing of signals failed:', diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts index 5ae411678aa03..cb955673a7ea6 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/types.ts @@ -5,7 +5,7 @@ */ import { DslQuery, Filter } from 'src/plugins/data/common'; -import moment from 'moment'; +import moment, { Moment } from 'moment'; import { Status } from '../../../../common/detection_engine/schemas/common/schemas'; import { RulesSchema } from '../../../../common/detection_engine/schemas/response/rules_schema'; import { @@ -263,6 +263,11 @@ export interface SearchAfterAndBulkCreateReturnType { createdSignalsCount: number; createdSignals: SignalHit[]; errors: string[]; + totalToFromTuples?: Array<{ + to: Moment | undefined; + from: Moment | undefined; + maxSignals: number; + }>; } export interface ThresholdAggregationBucket extends TermAggregationBucket { diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts index ec55ad7f588e8..58b21316720c0 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts @@ -819,6 +819,7 @@ describe('utils', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const timestampFieldCapsResponse: Partial, Context>> = { body: { + indices: ['myfakeindex-1', 'myfakeindex-2', 'myfakeindex-3', 'myfakeindex-4'], fields: { [timestampField]: { date: { @@ -843,6 +844,7 @@ describe('utils', () => { timestampField, // eslint-disable-next-line @typescript-eslint/no-explicit-any timestampFieldCapsResponse as ApiResponse>, + ['myfa*'], ruleStatusServiceMock, mockLogger, buildRuleMessage @@ -857,6 +859,7 @@ describe('utils', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const timestampFieldCapsResponse: Partial, Context>> = { body: { + indices: ['myfakeindex-1', 'myfakeindex-2', 'myfakeindex-3', 'myfakeindex-4'], fields: { [timestampField]: { date: { @@ -881,6 +884,7 @@ describe('utils', () => { timestampField, // eslint-disable-next-line @typescript-eslint/no-explicit-any timestampFieldCapsResponse as ApiResponse>, + ['myfa*'], ruleStatusServiceMock, mockLogger, buildRuleMessage @@ -1430,13 +1434,13 @@ describe('utils', () => { it('should generate a uuid without key', () => { const startedAt = new Date('2020-12-17T16:27:00Z'); const signalUuid = calculateThresholdSignalUuid('abcd', startedAt, 'agent.name'); - expect(signalUuid).toEqual('c0cbe4b7-48de-5734-ae81-d8de3e79839d'); + expect(signalUuid).toEqual('a4832768-a379-583a-b1a2-e2ce2ad9e6e9'); }); it('should generate a uuid with key', () => { const startedAt = new Date('2019-11-18T13:32:00Z'); const signalUuid = calculateThresholdSignalUuid('abcd', startedAt, 'host.ip', '1.2.3.4'); - expect(signalUuid).toEqual('f568509e-b570-5d3c-a7ed-7c73fd29ddaf'); + expect(signalUuid).toEqual('ee8870dc-45ff-5e6c-a2f9-80886651ce03'); }); }); }); diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts index 0ad502b67fbe6..6427dcf8c248e 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.ts @@ -107,11 +107,19 @@ export const hasTimestampFields = async ( // node_modules/@elastic/elasticsearch/api/kibana.d.ts // eslint-disable-next-line @typescript-eslint/no-explicit-any timestampFieldCapsResponse: ApiResponse, Context>, + inputIndices: string[], ruleStatusService: RuleStatusService, logger: Logger, buildRuleMessage: BuildRuleMessage ): Promise => { - if ( + if (!wroteStatus && isEmpty(timestampFieldCapsResponse.body.indices)) { + const errorString = `The following index patterns did not match any indices: ${JSON.stringify( + inputIndices + )}`; + logger.error(buildRuleMessage(errorString)); + await ruleStatusService.error(errorString); + return true; + } else if ( !wroteStatus && (isEmpty(timestampFieldCapsResponse.body.fields) || timestampFieldCapsResponse.body.fields[timestampField] == null || @@ -847,10 +855,9 @@ export const calculateThresholdSignalUuid = ( // used to generate constant Threshold Signals ID when run with the same params const NAMESPACE_ID = '0684ec03-7201-4ee0-8ee0-3a3f6b2479b2'; - let baseString = `${ruleId}${startedAt}${thresholdField}`; - if (key != null) { - baseString = `${baseString}${key}`; - } + const startedAtString = startedAt.toISOString(); + const keyString = key ?? ''; + const baseString = `${ruleId}${startedAtString}${thresholdField}${keyString}`; return uuidv5(baseString, NAMESPACE_ID); }; diff --git a/x-pack/plugins/security_solution/server/lib/timeline/saved_object.ts b/x-pack/plugins/security_solution/server/lib/timeline/saved_object.ts index 83566a6190610..c2698749f9d89 100644 --- a/x-pack/plugins/security_solution/server/lib/timeline/saved_object.ts +++ b/x-pack/plugins/security_solution/server/lib/timeline/saved_object.ts @@ -76,7 +76,10 @@ export interface Timeline { persistFavorite: ( request: FrameworkRequest, - timelineId: string | null + timelineId: string | null, + templateTimelineId: string | null, + templateTimelineVersion: number | null, + timelineType: TimelineType ) => Promise; persistTimeline: ( @@ -281,7 +284,10 @@ export const getDraftTimeline = async ( export const persistFavorite = async ( request: FrameworkRequest, - timelineId: string | null + timelineId: string | null, + templateTimelineId: string | null, + templateTimelineVersion: number | null, + timelineType: TimelineType ): Promise => { const userName = request.user?.username ?? UNAUTHENTICATED_USER; const fullName = request.user?.full_name ?? ''; @@ -324,7 +330,12 @@ export const persistFavorite = async ( timeline.favorite = [userFavoriteTimeline]; } - const persistResponse = await persistTimeline(request, timelineId, null, timeline); + const persistResponse = await persistTimeline(request, timelineId, null, { + ...timeline, + templateTimelineId, + templateTimelineVersion, + timelineType, + }); return { savedObjectId: persistResponse.timeline.savedObjectId, version: persistResponse.timeline.version, @@ -332,6 +343,9 @@ export const persistFavorite = async ( persistResponse.timeline.favorite != null ? persistResponse.timeline.favorite.filter((fav) => fav.userName === userName) : [], + templateTimelineId, + templateTimelineVersion, + timelineType, }; } catch (err) { if (getOr(null, 'output.statusCode', err) === 403) { diff --git a/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts b/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts index 8b2cce01cf07a..d25f1aaccc5e7 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/security_solution/index.ts @@ -37,7 +37,7 @@ export const securitySolutionSearchStrategyProvider = = { [TimelineEventsQueries.all]: timelineEventsAll, [TimelineEventsQueries.details]: timelineEventsDetails, + [TimelineEventsQueries.kpi]: timelineKpi, [TimelineEventsQueries.lastEventTime]: timelineEventsLastEventTime, }; diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/kpi/index.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/kpi/index.ts new file mode 100644 index 0000000000000..ee84a0faab2c2 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/kpi/index.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 { getOr } from 'lodash/fp'; + +import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; +import { + TimelineEventsQueries, + TimelineRequestBasicOptions, + TimelineKpiStrategyResponse, +} from '../../../../../../common/search_strategy/timeline'; +import { inspectStringifyObject } from '../../../../../utils/build_query'; +import { SecuritySolutionTimelineFactory } from '../../types'; +import { buildTimelineKpiQuery } from './query.kpi.dsl'; + +export const timelineKpi: SecuritySolutionTimelineFactory = { + buildDsl: (options: TimelineRequestBasicOptions) => buildTimelineKpiQuery(options), + parse: async ( + options: TimelineRequestBasicOptions, + response: IEsSearchResponse + ): Promise => { + const inspect = { + dsl: [inspectStringifyObject(buildTimelineKpiQuery(options))], + }; + + return { + ...response, + destinationIpCount: getOr(0, 'aggregations.destinationIpCount.value', response.rawResponse), + inspect, + hostCount: getOr(0, 'aggregations.hostCount.value', response.rawResponse), + processCount: getOr(0, 'aggregations.processCount.value', response.rawResponse), + sourceIpCount: getOr(0, 'aggregations.sourceIpCount.value', response.rawResponse), + userCount: getOr(0, 'aggregations.userCount.value', response.rawResponse), + }; + }, +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/kpi/query.kpi.dsl.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/kpi/query.kpi.dsl.ts new file mode 100644 index 0000000000000..b0333411bdee8 --- /dev/null +++ b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/kpi/query.kpi.dsl.ts @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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 { + TimerangeFilter, + TimerangeInput, + TimelineRequestBasicOptions, +} from '../../../../../../common/search_strategy'; +import { createQueryFilterClauses } from '../../../../../utils/build_query'; + +export const buildTimelineKpiQuery = ({ + defaultIndex, + filterQuery, + timerange, +}: TimelineRequestBasicOptions) => { + const filterClause = [...createQueryFilterClauses(filterQuery)]; + + const getTimerangeFilter = (timerangeOption: TimerangeInput | undefined): TimerangeFilter[] => { + if (timerangeOption) { + const { to, from } = timerangeOption; + return !isEmpty(to) && !isEmpty(from) + ? [ + { + range: { + '@timestamp': { + gte: from, + lte: to, + format: 'strict_date_optional_time', + }, + }, + }, + ] + : []; + } + return []; + }; + + const filter = [...filterClause, ...getTimerangeFilter(timerange), { match_all: {} }]; + + const dslQuery = { + allowNoIndices: true, + index: defaultIndex, + ignoreUnavailable: true, + body: { + aggs: { + userCount: { + cardinality: { + field: 'user.id', + }, + }, + destinationIpCount: { + cardinality: { + field: 'destination.ip', + }, + }, + hostCount: { + cardinality: { + field: 'host.id', + }, + }, + processCount: { + cardinality: { + field: 'process.entity_id', + }, + }, + sourceIpCount: { + cardinality: { + field: 'source.ip', + }, + }, + }, + query: { + bool: { + filter, + }, + }, + track_total_hits: true, + }, + }; + + return dslQuery; +}; diff --git a/x-pack/plugins/security_solution/server/search_strategy/timeline/index.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/index.ts index 5ad00a727c3b6..bb9c886917ce2 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/timeline/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/timeline/index.ts @@ -38,7 +38,7 @@ export const securitySolutionTimelineSearchStrategyProvider = { }) ).toEqual([0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); }); + + test('supports histogram buckets that begin in the past when tasks are overdue', async () => { + expect( + padBuckets(20, 3000, { + key: '2021-02-02T10:08:32.161Z-2021-02-02T10:09:32.161Z', + from: 1612260512161, + from_as_string: '2021-02-02T10:08:32.161Z', + to: 1612260572161, + to_as_string: '2021-02-02T10:09:32.161Z', + doc_count: 2, + histogram: { + buckets: [ + { + key_as_string: '2021-02-02T10:08:30.000Z', + key: 1612260510000, + doc_count: 1, + interval: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: '2s', + doc_count: 1, + }, + ], + }, + }, + { + key_as_string: '2021-02-02T10:08:33.000Z', + key: 1612260513000, + doc_count: 0, + interval: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [], + }, + }, + { + key_as_string: '2021-02-02T10:08:36.000Z', + key: 1612260516000, + doc_count: 0, + interval: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [], + }, + }, + { + key_as_string: '2021-02-02T10:08:39.000Z', + key: 1612260519000, + doc_count: 0, + interval: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [], + }, + }, + { + key_as_string: '2021-02-02T10:08:42.000Z', + key: 1612260522000, + doc_count: 0, + interval: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [], + }, + }, + { + key_as_string: '2021-02-02T10:08:45.000Z', + key: 1612260525000, + doc_count: 0, + interval: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [], + }, + }, + { + key_as_string: '2021-02-02T10:08:48.000Z', + key: 1612260528000, + doc_count: 0, + interval: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [], + }, + }, + { + key_as_string: '2021-02-02T10:08:51.000Z', + key: 1612260531000, + doc_count: 0, + interval: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [], + }, + }, + { + key_as_string: '2021-02-02T10:08:54.000Z', + key: 1612260534000, + doc_count: 1, + interval: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: '60s', + doc_count: 1, + }, + ], + }, + }, + ], + }, + }).length + // we need to ensure overdue buckets don't cause us to over pad the timeline by adding additional + // buckets before and after the reported timeline + ).toEqual(20); + }); }); function setTaskTypeCount( diff --git a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts index 8002ee44d01ff..8bd22bd88cf41 100644 --- a/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts +++ b/x-pack/plugins/task_manager/server/monitoring/workload_statistics.ts @@ -244,10 +244,19 @@ export function padBuckets( const firstBucket = histogram.buckets[0].key; const lastBucket = histogram.buckets[histogram.buckets.length - 1].key; - const bucketsToPadBeforeFirstBucket = calculateBucketsBetween(firstBucket, from, pollInterval); + // detect when the first bucket is before the `from` so that we can take that into + // account by begining the timeline earlier + // This can happen when you have overdue tasks and Elasticsearch returns their bucket + // as begining before the `from` + const firstBucketStartsInThePast = firstBucket - from < 0; + + const bucketsToPadBeforeFirstBucket = firstBucketStartsInThePast + ? [] + : calculateBucketsBetween(firstBucket, from, pollInterval); + const bucketsToPadAfterLast = calculateBucketsBetween( lastBucket + pollInterval, - to, + firstBucketStartsInThePast ? to - pollInterval : to, pollInterval ); diff --git a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json index 4ca373d9260b7..c1674f8a92669 100644 --- a/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json +++ b/x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @@ -1771,10 +1771,14 @@ } } }, - "fileUploadTelemetry": { + "fileUpload": { "properties": { - "filesUploadedTotalCount": { - "type": "long" + "file_upload": { + "properties": { + "index_creation_count": { + "type": "long" + } + } } } }, @@ -2215,6 +2219,13 @@ } } }, + "fileUploadTelemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, "maps": { "properties": { "settings": { @@ -2308,17 +2319,6 @@ } } }, - "mlTelemetry": { - "properties": { - "file_data_visualizer": { - "properties": { - "index_creation_count": { - "type": "long" - } - } - } - } - }, "monitoring": { "properties": { "hasMonitoringData": { diff --git a/x-pack/plugins/transform/tsconfig.json b/x-pack/plugins/transform/tsconfig.json new file mode 100644 index 0000000000000..2717f92c7a4df --- /dev/null +++ b/x-pack/plugins/transform/tsconfig.json @@ -0,0 +1,26 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "common/**/*", + "public/**/*", + "server/**/*", + "../../../typings/**/*", + // have to declare *.json explicitly due to https://github.com/microsoft/TypeScript/issues/25636 + "public/**/*.json", + ], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + { "path": "../../../src/plugins/index_pattern_management/tsconfig.json" }, + { "path": "../features/tsconfig.json" }, + { "path": "../license_management/tsconfig.json" }, + { "path": "../licensing/tsconfig.json" }, + { "path": "../ml/tsconfig.json" }, + ] +} diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index 4369cbf35594d..cab6973072f24 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -4713,7 +4713,6 @@ "visualize.createVisualization.failedToLoadErrorMessage": "ビジュアライゼーションを読み込めませんでした", "visualize.createVisualization.noIndexPatternOrSavedSearchIdErrorMessage": "indexPatternまたはsavedSearchIdが必要です", "visualize.createVisualization.noVisTypeErrorMessage": "有効なビジュアライゼーションタイプを指定してください", - "visualize.discover.visualizeFieldLabel": "Visualizeフィールド", "visualize.editor.createBreadcrumb": "作成", "visualize.editor.defaultEditBreadcrumbText": "編集", "visualize.experimentalVisInfoText": "このビジュアライゼーションはまだ実験段階であり、オフィシャルGA機能のサポートSLAが適用されません。フィードバックがある場合は、{githubLink}で問題を報告してください。", @@ -4784,7 +4783,6 @@ "xpack.actions.builtin.pagerdutyTitle": "PagerDuty", "xpack.actions.builtin.serverLog.errorLoggingErrorMessage": "メッセージのロギングエラー", "xpack.actions.builtin.serverLogTitle": "サーバーログ", - "xpack.actions.builtin.servicenowTitle": "ServiceNow", "xpack.actions.builtin.slack.errorPostingErrorMessage": "slack メッセージの投稿エラー", "xpack.actions.builtin.slack.errorPostingRetryDateErrorMessage": "slack メッセージの投稿エラー、 {retryString} で再試行", "xpack.actions.builtin.slack.errorPostingRetryLaterErrorMessage": "slack メッセージの投稿エラー、後ほど再試行", @@ -21161,7 +21159,6 @@ "xpack.triggersActionsUI.common.expressionItems.threshold.popoverTitle": "タイミング", "xpack.triggersActionsUI.components.addMessageVariables.addVariablePopoverButton": "変数を追加", "xpack.triggersActionsUI.components.addMessageVariables.addVariableTitle": "アラート変数を追加", - "xpack.triggersActionsUI.components.builtinActionTypes.common.requiredDescriptionTextField": "説明が必要です。", "xpack.triggersActionsUI.components.builtinActionTypes.common.requiredShortDescTextField": "短い説明が必要です。", "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.actionTypeTitle": "メールに送信", "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.configureAccountsHelpLabel": "電子メールアカウントの構成", @@ -21292,35 +21289,16 @@ "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.logLevelFieldLabel": "レベル", "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.logMessageFieldLabel": "メッセージ", "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.selectMessageText": "Kibana ログにメッセージを追加します。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.actionTypeTitle": "ServiceNow", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.apiTokenTextFieldLabel": "APIトークン", "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.apiUrlTextFieldLabel": "URL", "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.authenticationLabel": "認証", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.commentsTextAreaFieldLabel": "追加のコメント", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.descriptionTextAreaFieldLabel": "説明", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.emailTextFieldLabel": "メール", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.impactSelectFieldLabel": "インパクト", "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.invalidApiUrlTextField": "URL が無効です。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.mappingFieldComments": "コメント", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.mappingFieldDescription": "説明", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.mappingFieldShortDescription": "短い説明", "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.passwordTextFieldLabel": "パスワード", "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.reenterValuesLabel": "ユーザー名とパスワードは暗号化されます。これらのフィールドの値を再入力してください。", "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.rememberValuesLabel": "これらの値を覚えておいてください。コネクターを編集するたびに再入力する必要があります。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredApiTokenTextField": "API トークンが必要です。", "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredApiUrlTextField": "URL が必要です。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredEmailTextField": "電子メールが必要です。", "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredPasswordTextField": "パスワードが必要です。", "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredUsernameTextField": "ユーザー名が必要です。", "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requireHttpsApiUrlTextField": "URL は https:// から始める必要があります。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.selectMessageText": "ServiceNow でインシデントを作成します。", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.severitySelectFieldLabel": "深刻度", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.severitySelectHighOptionLabel": "高", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.severitySelectLawOptionLabel": "低", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.severitySelectMediumOptionLabel": "中", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.title": "インシデント", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.titleFieldLabel": "短い説明(必須)", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.urgencySelectFieldLabel": "緊急", "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.usernameTextFieldLabel": "ユーザー名", "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowAction.apiUrlHelpLabel": "Personal Developer Instance の構成", "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.actionTypeTitle": "Slack に送信", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index d2504c8752c05..2bdbfc3d565e5 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -662,9 +662,9 @@ "dashboard.topNav.showCloneModal.dashboardCopyTitle": "{title} 副本", "dashboard.topNave.addButtonAriaLabel": "库", "dashboard.topNave.addConfigDescription": "将现有可视化添加到仪表板", + "dashboard.topNave.cancelButtonAriaLabel": "取消", "dashboard.topNave.addNewButtonAriaLabel": "创建面板", "dashboard.topNave.addNewConfigDescription": "在此仪表板上创建新的面板", - "dashboard.topNave.cancelButtonAriaLabel": "取消", "dashboard.topNave.cloneButtonAriaLabel": "克隆", "dashboard.topNave.cloneConfigDescription": "创建仪表板的副本", "dashboard.topNave.editButtonAriaLabel": "编辑", @@ -4718,7 +4718,6 @@ "visualize.createVisualization.failedToLoadErrorMessage": "无法加载可视化", "visualize.createVisualization.noIndexPatternOrSavedSearchIdErrorMessage": "必须提供 indexPattern 或 savedSearchId", "visualize.createVisualization.noVisTypeErrorMessage": "必须提供有效的可视化类型", - "visualize.discover.visualizeFieldLabel": "可视化字段", "visualize.editor.createBreadcrumb": "创建", "visualize.editor.defaultEditBreadcrumbText": "编辑", "visualize.experimentalVisInfoText": "此可视化为试验性功能,不受正式发行版功能支持 SLA 的约束。如欲提供反馈,请在 {githubLink} 中创建问题。", @@ -4789,7 +4788,6 @@ "xpack.actions.builtin.pagerdutyTitle": "PagerDuty", "xpack.actions.builtin.serverLog.errorLoggingErrorMessage": "记录消息时出错", "xpack.actions.builtin.serverLogTitle": "服务器日志", - "xpack.actions.builtin.servicenowTitle": "ServiceNow", "xpack.actions.builtin.slack.errorPostingErrorMessage": "发布 slack 消息时出错", "xpack.actions.builtin.slack.errorPostingRetryDateErrorMessage": "发布 Slack 消息时出错,在 {retryString} 重试", "xpack.actions.builtin.slack.errorPostingRetryLaterErrorMessage": "发布 slack 消息时出错,稍后重试", @@ -21212,7 +21210,6 @@ "xpack.triggersActionsUI.common.expressionItems.threshold.popoverTitle": "当", "xpack.triggersActionsUI.components.addMessageVariables.addVariablePopoverButton": "添加变量", "xpack.triggersActionsUI.components.addMessageVariables.addVariableTitle": "添加告警变量", - "xpack.triggersActionsUI.components.builtinActionTypes.common.requiredDescriptionTextField": "“描述”必填。", "xpack.triggersActionsUI.components.builtinActionTypes.common.requiredShortDescTextField": "“简短描述”必填。", "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.actionTypeTitle": "发送到电子邮件", "xpack.triggersActionsUI.components.builtinActionTypes.emailAction.configureAccountsHelpLabel": "配置电子邮件帐户", @@ -21343,35 +21340,16 @@ "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.logLevelFieldLabel": "级别", "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.logMessageFieldLabel": "消息", "xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.selectMessageText": "将消息添加到 Kibana 日志。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.actionTypeTitle": "ServiceNow", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.apiTokenTextFieldLabel": "Api 令牌", "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.apiUrlTextFieldLabel": "URL", "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.authenticationLabel": "身份验证", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.commentsTextAreaFieldLabel": "其他注释", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.descriptionTextAreaFieldLabel": "描述", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.emailTextFieldLabel": "电子邮件", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.impactSelectFieldLabel": "影响", "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.invalidApiUrlTextField": "URL 无效。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.mappingFieldComments": "注释", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.mappingFieldDescription": "描述", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.mappingFieldShortDescription": "简短描述", "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.passwordTextFieldLabel": "密码", "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.reenterValuesLabel": "用户名和密码已加密。请为这些字段重新输入值。", "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.rememberValuesLabel": "请记住这些值。每次编辑连接器时都必须重新输入。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredApiTokenTextField": "“Api 令牌”必填。", "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredApiUrlTextField": "“URL”必填。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredEmailTextField": "“电子邮件”必填。", "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredPasswordTextField": "“密码”必填。", "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredUsernameTextField": "“用户名”必填。", "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requireHttpsApiUrlTextField": "URL 必须以 https:// 开头。", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.selectMessageText": "在 ServiceNow 中创建事件。", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.severitySelectFieldLabel": "严重性", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.severitySelectHighOptionLabel": "高", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.severitySelectLawOptionLabel": "低", - "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.severitySelectMediumOptionLabel": "中", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.title": "事件", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.titleFieldLabel": "简短描述(必填)", - "xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.urgencySelectFieldLabel": "紧急性", "xpack.triggersActionsUI.components.builtinActionTypes.servicenow.usernameTextFieldLabel": "用户名", "xpack.triggersActionsUI.components.builtinActionTypes.serviceNowAction.apiUrlHelpLabel": "配置个人开发者实例", "xpack.triggersActionsUI.components.builtinActionTypes.slackAction.actionTypeTitle": "发送到 Slack", diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/index.ts index b8514a06dc253..003b2c5eedb10 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/index.ts @@ -12,7 +12,7 @@ import { getPagerDutyActionType } from './pagerduty'; import { getWebhookActionType } from './webhook'; import { TypeRegistry } from '../../type_registry'; import { ActionTypeModel } from '../../../types'; -import { getServiceNowActionType } from './servicenow'; +import { getServiceNowITSMActionType, getServiceNowSIRActionType } from './servicenow'; import { getJiraActionType } from './jira'; import { getResilientActionType } from './resilient'; import { getTeamsActionType } from './teams'; @@ -28,7 +28,8 @@ export function registerBuiltInActionTypes({ actionTypeRegistry.register(getIndexActionType()); actionTypeRegistry.register(getPagerDutyActionType()); actionTypeRegistry.register(getWebhookActionType()); - actionTypeRegistry.register(getServiceNowActionType()); + actionTypeRegistry.register(getServiceNowITSMActionType()); + actionTypeRegistry.register(getServiceNowSIRActionType()); actionTypeRegistry.register(getJiraActionType()); actionTypeRegistry.register(getResilientActionType()); actionTypeRegistry.register(getTeamsActionType()); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/api.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/api.test.ts index d5474aaceaa48..4759eecf3ef0e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/api.test.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/api.test.ts @@ -8,6 +8,7 @@ import { httpServiceMock } from '../../../../../../../../src/core/public/mocks'; import { getIssueTypes, getFieldsByIssueType, getIssues, getIssue } from './api'; const issueTypesResponse = { + status: 'ok', data: { projects: [ { @@ -24,9 +25,11 @@ const issueTypesResponse = { }, ], }, + actionId: 'test', }; const fieldsResponse = { + status: 'ok', data: { projects: [ { @@ -70,13 +73,18 @@ const fieldsResponse = { ], }, ], + actionId: 'test', }, }; const issueResponse = { - id: '10267', - key: 'RJ-107', - fields: { summary: 'Test title' }, + status: 'ok', + data: { + id: '10267', + key: 'RJ-107', + fields: { summary: 'Test title' }, + }, + actionId: 'test', }; const issuesResponse = [issueResponse]; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/config.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/config.ts index 628600ee91c8e..d05bf78a5106e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/config.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/jira/config.ts @@ -15,24 +15,4 @@ export const connectorConfiguration = { enabledInConfig: true, enabledInLicense: true, minimumLicenseRequired: 'gold', - fields: { - summary: { - label: i18n.MAPPING_FIELD_SUMMARY, - validSourceFields: ['title', 'description'], - defaultSourceField: 'title', - defaultActionType: 'overwrite', - }, - description: { - label: i18n.MAPPING_FIELD_DESC, - validSourceFields: ['title', 'description'], - defaultSourceField: 'description', - defaultActionType: 'overwrite', - }, - comments: { - label: i18n.MAPPING_FIELD_COMMENTS, - validSourceFields: ['comments'], - defaultSourceField: 'comments', - defaultActionType: 'append', - }, - }, }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/api.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/api.test.ts new file mode 100644 index 0000000000000..24c7f7687da69 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/api.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 { httpServiceMock } from '../../../../../../../../src/core/public/mocks'; +import { getIncidentTypes, getSeverity } from './api'; + +const incidentTypesResponse = { + status: 'ok', + data: [ + { id: 17, name: 'Communication error (fax; email)' }, + { id: 1001, name: 'Custom type' }, + { id: 21, name: 'Denial of Service' }, + { id: 6, name: 'Improper disposal: digital asset(s)' }, + { id: 7, name: 'Improper disposal: documents / files' }, + { id: 4, name: 'Lost documents / files / records' }, + { id: 3, name: 'Lost PC / laptop / tablet' }, + { id: 1, name: 'Lost PDA / smartphone' }, + { id: 8, name: 'Lost storage device / media' }, + { id: 19, name: 'Malware' }, + { id: 23, name: 'Not an Issue' }, + { id: 18, name: 'Other' }, + { id: 22, name: 'Phishing' }, + { id: 11, name: 'Stolen documents / files / records' }, + { id: 12, name: 'Stolen PC / laptop / tablet' }, + { id: 13, name: 'Stolen PDA / smartphone' }, + { id: 14, name: 'Stolen storage device / media' }, + { id: 20, name: 'System Intrusion' }, + { id: 16, name: 'TBD / Unknown' }, + { id: 15, name: 'Vendor / 3rd party error' }, + ], + actionId: 'test', +}; + +const severityResponse = { + status: 'ok', + data: [ + { id: 4, name: 'Low' }, + { id: 5, name: 'Medium' }, + { id: 6, name: 'High' }, + ], + actionId: 'test', +}; + +describe('Resilient API', () => { + const http = httpServiceMock.createStartContract(); + + beforeEach(() => jest.resetAllMocks()); + + describe('getIncidentTypes', () => { + test('should call get choices API', async () => { + const abortCtrl = new AbortController(); + http.post.mockResolvedValueOnce(incidentTypesResponse); + const res = await getIncidentTypes({ + http, + signal: abortCtrl.signal, + connectorId: 'test', + }); + + expect(res).toEqual(incidentTypesResponse); + expect(http.post).toHaveBeenCalledWith('/api/actions/action/test/_execute', { + body: '{"params":{"subAction":"incidentTypes","subActionParams":{}}}', + signal: abortCtrl.signal, + }); + }); + }); + + describe('getSeverity', () => { + test('should call get choices API', async () => { + const abortCtrl = new AbortController(); + http.post.mockResolvedValueOnce(severityResponse); + const res = await getSeverity({ + http, + signal: abortCtrl.signal, + connectorId: 'test', + }); + + expect(res).toEqual(severityResponse); + expect(http.post).toHaveBeenCalledWith('/api/actions/action/test/_execute', { + body: '{"params":{"subAction":"severity","subActionParams":{}}}', + signal: abortCtrl.signal, + }); + }); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/config.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/config.ts index a2054585c19b8..9717d594b20ec 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/config.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/resilient/config.ts @@ -15,24 +15,4 @@ export const connectorConfiguration = { enabledInConfig: true, enabledInLicense: true, minimumLicenseRequired: 'platinum', - fields: { - name: { - label: i18n.MAPPING_FIELD_NAME, - validSourceFields: ['title', 'description'], - defaultSourceField: 'title', - defaultActionType: 'overwrite', - }, - description: { - label: i18n.MAPPING_FIELD_DESC, - validSourceFields: ['title', 'description'], - defaultSourceField: 'description', - defaultActionType: 'overwrite', - }, - comments: { - label: i18n.MAPPING_FIELD_COMMENTS, - validSourceFields: ['comments'], - defaultSourceField: 'comments', - defaultActionType: 'append', - }, - }, }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.test.ts new file mode 100644 index 0000000000000..e87b84439f6f8 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.test.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { httpServiceMock } from '../../../../../../../../src/core/public/mocks'; +import { getChoices } from './api'; + +const choicesResponse = { + status: 'ok', + data: [ + { + dependent_value: '', + label: '1 - Critical', + value: '1', + element: 'priority', + }, + { + dependent_value: '', + label: '2 - High', + value: '2', + element: 'priority', + }, + { + dependent_value: '', + label: '3 - Moderate', + value: '3', + element: 'priority', + }, + { + dependent_value: '', + label: '4 - Low', + value: '4', + element: 'priority', + }, + { + dependent_value: '', + label: '5 - Planning', + value: '5', + element: 'priority', + }, + ], +}; + +describe('ServiceNow API', () => { + const http = httpServiceMock.createStartContract(); + + beforeEach(() => jest.resetAllMocks()); + + describe('getChoices', () => { + test('should call get choices API', async () => { + const abortCtrl = new AbortController(); + http.post.mockResolvedValueOnce(choicesResponse); + const res = await getChoices({ + http, + signal: abortCtrl.signal, + connectorId: 'test', + fields: ['priority'], + }); + + expect(res).toEqual(choicesResponse); + expect(http.post).toHaveBeenCalledWith('/api/actions/action/test/_execute', { + body: '{"params":{"subAction":"getChoices","subActionParams":{"fields":["priority"]}}}', + signal: abortCtrl.signal, + }); + }); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.ts new file mode 100644 index 0000000000000..ecfc66f1b0391 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/api.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 { HttpSetup } from 'kibana/public'; +import { BASE_ACTION_API_PATH } from '../../../constants'; + +export async function getChoices({ + http, + signal, + connectorId, + fields, +}: { + http: HttpSetup; + signal: AbortSignal; + connectorId: string; + fields: string[]; +}): Promise> { + return await http.post(`${BASE_ACTION_API_PATH}/action/${connectorId}/_execute`, { + body: JSON.stringify({ + params: { subAction: 'getChoices', subActionParams: { fields } }, + }), + signal, + }); +} diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/config.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/config.ts index 7f810cf5eb38f..6920ee71144a5 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/config.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/config.ts @@ -7,32 +7,24 @@ import * as i18n from './translations'; import logo from './logo.svg'; -export const connectorConfiguration = { +export const serviceNowITSMConfiguration = { id: '.servicenow', - name: i18n.SERVICENOW_TITLE, + name: i18n.SERVICENOW_ITSM_TITLE, + desc: i18n.SERVICENOW_ITSM_DESC, + logo, + enabled: true, + enabledInConfig: true, + enabledInLicense: true, + minimumLicenseRequired: 'platinum', +}; + +export const serviceNowSIRConfiguration = { + id: '.servicenow-sir', + name: i18n.SERVICENOW_SIR_TITLE, + desc: i18n.SERVICENOW_SIR_DESC, logo, enabled: true, enabledInConfig: true, enabledInLicense: true, minimumLicenseRequired: 'platinum', - fields: { - short_description: { - label: i18n.MAPPING_FIELD_SHORT_DESC, - validSourceFields: ['title', 'description'], - defaultSourceField: 'title', - defaultActionType: 'overwrite', - }, - description: { - label: i18n.MAPPING_FIELD_DESC, - validSourceFields: ['title', 'description'], - defaultSourceField: 'description', - defaultActionType: 'overwrite', - }, - comments: { - label: i18n.MAPPING_FIELD_COMMENTS, - validSourceFields: ['comments'], - defaultSourceField: 'comments', - defaultActionType: 'append', - }, - }, }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/index.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/index.ts index 65bb3ae4f5a37..e1f66e506ed8e 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { getActionType as getServiceNowActionType } from './servicenow'; +export { getServiceNowITSMActionType, getServiceNowSIRActionType } from './servicenow'; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.test.tsx index dfa9bf56cc7a9..ce69f428e10a1 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.test.tsx @@ -8,102 +8,110 @@ import { registerBuiltInActionTypes } from '.././index'; import { ActionTypeModel } from '../../../../types'; import { ServiceNowActionConnector } from './types'; -const ACTION_TYPE_ID = '.servicenow'; -let actionTypeModel: ActionTypeModel; +const SERVICENOW_ITSM_ACTION_TYPE_ID = '.servicenow'; +const SERVICENOW_SIR_ACTION_TYPE_ID = '.servicenow-sir'; +let actionTypeRegistry: TypeRegistry; beforeAll(() => { - const actionTypeRegistry = new TypeRegistry(); + 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); + [SERVICENOW_ITSM_ACTION_TYPE_ID, SERVICENOW_SIR_ACTION_TYPE_ID].forEach((id) => { + test(`${id}: action type static data is as expected`, () => { + const actionTypeModel = actionTypeRegistry.get(id); + expect(actionTypeModel.id).toEqual(id); + }); }); }); describe('servicenow connector validation', () => { - test('connector validation succeeds when connector config is valid', () => { - const actionConnector = { - secrets: { - username: 'user', - password: 'pass', - }, - id: 'test', - actionTypeId: '.servicenow', - name: 'ServiceNow', - isPreconfigured: false, - config: { - apiUrl: 'https://dev94428.service-now.com/', - }, - } as ServiceNowActionConnector; + [SERVICENOW_ITSM_ACTION_TYPE_ID, SERVICENOW_SIR_ACTION_TYPE_ID].forEach((id) => { + test(`${id}: connector validation succeeds when connector config is valid`, () => { + const actionTypeModel = actionTypeRegistry.get(id); + const actionConnector = { + secrets: { + username: 'user', + password: 'pass', + }, + id: 'test', + actionTypeId: id, + name: 'ServiceNow', + isPreconfigured: false, + config: { + apiUrl: 'https://dev94428.service-now.com/', + }, + } as ServiceNowActionConnector; - expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ - config: { - errors: { - apiUrl: [], + expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ + config: { + errors: { + apiUrl: [], + }, }, - }, - secrets: { - errors: { - username: [], - password: [], + secrets: { + errors: { + username: [], + password: [], + }, }, - }, + }); }); - }); - test('connector validation fails when connector config is not valid', () => { - const actionConnector = ({ - secrets: { - username: 'user', - }, - id: '.servicenow', - actionTypeId: '.servicenow', - name: 'servicenow', - config: {}, - } as unknown) as ServiceNowActionConnector; + test(`${id}: connector validation fails when connector config is not valid`, () => { + const actionTypeModel = actionTypeRegistry.get(id); + const actionConnector = ({ + secrets: { + username: 'user', + }, + id, + actionTypeId: id, + name: 'servicenow', + config: {}, + } as unknown) as ServiceNowActionConnector; - expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ - config: { - errors: { - apiUrl: ['URL is required.'], + expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ + config: { + errors: { + apiUrl: ['URL is required.'], + }, }, - }, - secrets: { - errors: { - username: [], - password: ['Password is required.'], + secrets: { + errors: { + username: [], + password: ['Password is required.'], + }, }, - }, + }); }); }); }); describe('servicenow action params validation', () => { - test('action params validation succeeds when action params is valid', () => { - const actionParams = { - subActionParams: { incident: { short_description: 'some title {{test}}' }, comments: [] }, - }; + [SERVICENOW_ITSM_ACTION_TYPE_ID, SERVICENOW_SIR_ACTION_TYPE_ID].forEach((id) => { + test(`${id}: action params validation succeeds when action params is valid`, () => { + const actionTypeModel = actionTypeRegistry.get(id); + const actionParams = { + subActionParams: { incident: { short_description: 'some title {{test}}' }, comments: [] }, + }; - expect(actionTypeModel.validateParams(actionParams)).toEqual({ - errors: { ['subActionParams.incident.short_description']: [] }, + expect(actionTypeModel.validateParams(actionParams)).toEqual({ + errors: { ['subActionParams.incident.short_description']: [] }, + }); }); - }); - test('params validation fails when body is not valid', () => { - const actionParams = { - subActionParams: { incident: { short_description: '' }, comments: [] }, - }; + test(`${id}: params validation fails when body is not valid`, () => { + const actionTypeModel = actionTypeRegistry.get(id); + const actionParams = { + subActionParams: { incident: { short_description: '' }, comments: [] }, + }; - expect(actionTypeModel.validateParams(actionParams)).toEqual({ - errors: { - ['subActionParams.incident.short_description']: ['Short description is required.'], - }, + expect(actionTypeModel.validateParams(actionParams)).toEqual({ + errors: { + ['subActionParams.incident.short_description']: ['Short description is required.'], + }, + }); }); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.tsx index 4389abff72fcd..1b968cfff5d01 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow.tsx @@ -10,13 +10,14 @@ import { ActionTypeModel, ConnectorValidationResult, } from '../../../../types'; -import { connectorConfiguration } from './config'; +import { serviceNowITSMConfiguration, serviceNowSIRConfiguration } from './config'; import logo from './logo.svg'; import { ServiceNowActionConnector, ServiceNowConfig, ServiceNowSecrets, - ServiceNowActionParams, + ServiceNowITSMActionParams, + ServiceNowSIRActionParams, } from './types'; import * as i18n from './translations'; import { isValidUrl } from '../../../lib/value_validators'; @@ -60,19 +61,21 @@ const validateConnector = ( return validationResult; }; -export function getActionType(): ActionTypeModel< +export function getServiceNowITSMActionType(): ActionTypeModel< ServiceNowConfig, ServiceNowSecrets, - ServiceNowActionParams + ServiceNowITSMActionParams > { return { - id: connectorConfiguration.id, + id: serviceNowITSMConfiguration.id, iconClass: logo, - selectMessage: i18n.SERVICENOW_DESC, - actionTypeTitle: connectorConfiguration.name, + selectMessage: serviceNowITSMConfiguration.desc, + actionTypeTitle: serviceNowITSMConfiguration.name, validateConnector, actionConnectorFields: lazy(() => import('./servicenow_connectors')), - validateParams: (actionParams: ServiceNowActionParams): GenericValidationResult => { + validateParams: ( + actionParams: ServiceNowITSMActionParams + ): GenericValidationResult => { const errors = { // eslint-disable-next-line @typescript-eslint/naming-convention 'subActionParams.incident.short_description': new Array(), @@ -89,6 +92,39 @@ export function getActionType(): ActionTypeModel< } return validationResult; }, - actionParamsFields: lazy(() => import('./servicenow_params')), + actionParamsFields: lazy(() => import('./servicenow_itsm_params')), + }; +} + +export function getServiceNowSIRActionType(): ActionTypeModel< + ServiceNowConfig, + ServiceNowSecrets, + ServiceNowSIRActionParams +> { + return { + id: serviceNowSIRConfiguration.id, + iconClass: logo, + selectMessage: serviceNowSIRConfiguration.desc, + actionTypeTitle: serviceNowSIRConfiguration.name, + validateConnector, + actionConnectorFields: lazy(() => import('./servicenow_connectors')), + validateParams: (actionParams: ServiceNowSIRActionParams): GenericValidationResult => { + const errors = { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'subActionParams.incident.short_description': new Array(), + }; + const validationResult = { + errors, + }; + if ( + actionParams.subActionParams && + actionParams.subActionParams.incident && + !actionParams.subActionParams.incident.short_description?.length + ) { + errors['subActionParams.incident.short_description'].push(i18n.TITLE_REQUIRED); + } + return validationResult; + }, + actionParamsFields: lazy(() => import('./servicenow_sir_params')), }; } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.test.tsx similarity index 53% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_params.test.tsx rename to x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.test.tsx index 5519d7498a85e..51318e14a2cfd 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_params.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.test.tsx @@ -5,8 +5,18 @@ */ import React from 'react'; import { mount } from 'enzyme'; -import ServiceNowParamsFields from './servicenow_params'; +import { act } from '@testing-library/react'; + import { ActionConnector } from '../../../../types'; +import { useGetChoices } from './use_get_choices'; +import ServiceNowITSMParamsFields from './servicenow_itsm_params'; +import { Choice } from './types'; + +jest.mock('./use_get_choices'); +jest.mock('../../../../common/lib/kibana'); + +const useGetChoicesMock = useGetChoices as jest.Mock; + const actionParams = { subAction: 'pushToService', subActionParams: { @@ -16,7 +26,6 @@ const actionParams = { severity: '1', urgency: '2', impact: '3', - savedObjectId: '123', externalId: null, }, comments: [], @@ -31,6 +40,7 @@ const connector: ActionConnector = { name: 'Test', isPreconfigured: false, }; + const editAction = jest.fn(); const defaultProps = { actionConnector: connector, @@ -40,31 +50,71 @@ const defaultProps = { index: 0, messageVariables: [], }; -describe('ServiceNowParamsFields renders', () => { + +const useGetChoicesResponse = { + isLoading: false, + choices: ['severity', 'urgency', 'impact'] + .map((element) => [ + { + dependent_value: '', + label: '1 - Critical', + value: '1', + element, + }, + { + dependent_value: '', + label: '2 - High', + value: '2', + element, + }, + { + dependent_value: '', + label: '3 - Moderate', + value: '3', + element, + }, + { + dependent_value: '', + label: '4 - Low', + value: '4', + element, + }, + ]) + .flat(), +}; + +describe('ServiceNowITSMParamsFields renders', () => { + let onChoices = (choices: Choice[]) => {}; + beforeEach(() => { jest.clearAllMocks(); + useGetChoicesMock.mockImplementation((args) => { + onChoices = args.onSuccess; + return useGetChoicesResponse; + }); }); + test('all params fields is rendered', () => { - const wrapper = mount(); - expect(wrapper.find('[data-test-subj="urgencySelect"]').length > 0).toBeTruthy(); - expect(wrapper.find('[data-test-subj="severitySelect"]').first().prop('value')).toStrictEqual( - '1' - ); - expect(wrapper.find('[data-test-subj="impactSelect"]').length > 0).toBeTruthy(); - expect(wrapper.find('[data-test-subj="short_descriptionInput"]').length > 0).toBeTruthy(); - expect(wrapper.find('[data-test-subj="descriptionTextArea"]').length > 0).toBeTruthy(); - expect(wrapper.find('[data-test-subj="commentsTextArea"]').length > 0).toBeTruthy(); + const wrapper = mount(); + expect(wrapper.find('[data-test-subj="urgencySelect"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="severitySelect"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="impactSelect"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="short_descriptionInput"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="descriptionTextArea"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="commentsTextArea"]').exists()).toBeTruthy(); }); + test('If short_description has errors, form row is invalid', () => { const newProps = { ...defaultProps, // eslint-disable-next-line @typescript-eslint/naming-convention errors: { 'subActionParams.incident.short_description': ['error'] }, }; - const wrapper = mount(); + const wrapper = mount(); const title = wrapper.find('[data-test-subj="short_descriptionInput"]').first(); expect(title.prop('isInvalid')).toBeTruthy(); }); + test('When subActionParams is undefined, set to default', () => { const { subActionParams, ...newParams } = actionParams; @@ -72,12 +122,13 @@ describe('ServiceNowParamsFields renders', () => { ...defaultProps, actionParams: newParams, }; - mount(); + mount(); expect(editAction.mock.calls[0][1]).toEqual({ incident: {}, comments: [], }); }); + test('When subAction is undefined, set to default', () => { const { subAction, ...newParams } = actionParams; @@ -85,11 +136,12 @@ describe('ServiceNowParamsFields renders', () => { ...defaultProps, actionParams: newParams, }; - mount(); + mount(); expect(editAction.mock.calls[0][1]).toEqual('pushToService'); }); + test('Resets fields when connector changes', () => { - const wrapper = mount(); + const wrapper = mount(); expect(editAction.mock.calls.length).toEqual(0); wrapper.setProps({ actionConnector: { ...connector, id: '1234' } }); expect(editAction.mock.calls.length).toEqual(1); @@ -98,6 +150,52 @@ describe('ServiceNowParamsFields renders', () => { comments: [], }); }); + + test('it transforms the urgencies to options correctly', async () => { + const wrapper = mount(); + act(() => { + onChoices(useGetChoicesResponse.choices); + }); + + wrapper.update(); + expect(wrapper.find('[data-test-subj="urgencySelect"]').first().prop('options')).toEqual([ + { value: '1', text: '1 - Critical' }, + { value: '2', text: '2 - High' }, + { value: '3', text: '3 - Moderate' }, + { value: '4', text: '4 - Low' }, + ]); + }); + + test('it transforms the severities to options correctly', async () => { + const wrapper = mount(); + act(() => { + onChoices(useGetChoicesResponse.choices); + }); + + wrapper.update(); + expect(wrapper.find('[data-test-subj="severitySelect"]').first().prop('options')).toEqual([ + { value: '1', text: '1 - Critical' }, + { value: '2', text: '2 - High' }, + { value: '3', text: '3 - Moderate' }, + { value: '4', text: '4 - Low' }, + ]); + }); + + test('it transforms the impacts to options correctly', async () => { + const wrapper = mount(); + act(() => { + onChoices(useGetChoicesResponse.choices); + }); + + wrapper.update(); + expect(wrapper.find('[data-test-subj="impactSelect"]').first().prop('options')).toEqual([ + { value: '1', text: '1 - Critical' }, + { value: '2', text: '2 - High' }, + { value: '3', text: '3 - Moderate' }, + { value: '4', text: '4 - Low' }, + ]); + }); + describe('UI updates', () => { const changeEvent = { target: { value: 'Bug' } } as React.ChangeEvent; const simpleFields = [ @@ -107,22 +205,25 @@ describe('ServiceNowParamsFields renders', () => { { dataTestSubj: '[data-test-subj="severitySelect"]', key: 'severity' }, { dataTestSubj: '[data-test-subj="impactSelect"]', key: 'impact' }, ]; + simpleFields.forEach((field) => test(`${field.key} update triggers editAction :D`, () => { - const wrapper = mount(); + const wrapper = mount(); const theField = wrapper.find(field.dataTestSubj).first(); theField.prop('onChange')!(changeEvent); expect(editAction.mock.calls[0][1].incident[field.key]).toEqual(changeEvent.target.value); }) ); + test('A comment triggers editAction', () => { - const wrapper = mount(); + const wrapper = mount(); const comments = wrapper.find('textarea[data-test-subj="commentsTextArea"]'); expect(comments.simulate('change', changeEvent)); expect(editAction.mock.calls[0][1].comments.length).toEqual(1); }); + test('An empty comment does not trigger editAction', () => { - const wrapper = mount(); + const wrapper = mount(); const emptyComment = { target: { value: '' } }; const comments = wrapper.find('[data-test-subj="commentsTextArea"] textarea'); expect(comments.simulate('change', emptyComment)); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_params.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.tsx similarity index 64% rename from x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_params.tsx rename to x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.tsx index 3e6b443d790a9..658b964f8b91d 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_params.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_itsm_params.tsx @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, useCallback, useEffect, useMemo, useRef } from 'react'; -import { i18n } from '@kbn/i18n'; +import React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { EuiFormRow, EuiSelect, @@ -14,38 +13,29 @@ import { EuiSpacer, EuiTitle, } from '@elastic/eui'; +import { useKibana } from '../../../../common/lib/kibana'; import { ActionParamsProps } from '../../../../types'; -import { ServiceNowActionParams } from './types'; +import { ServiceNowITSMActionParams, Choice, Options } from './types'; import { TextAreaWithMessageVariables } from '../../text_area_with_message_variables'; import { TextFieldWithMessageVariables } from '../../text_field_with_message_variables'; +import { useGetChoices } from './use_get_choices'; +import * as i18n from './translations'; -const selectOptions = [ - { - value: '1', - text: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.severitySelectHighOptionLabel', - { defaultMessage: 'High' } - ), - }, - { - value: '2', - text: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.severitySelectMediumOptionLabel', - { defaultMessage: 'Medium' } - ), - }, - { - value: '3', - text: i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.severitySelectLawOptionLabel', - { defaultMessage: 'Low' } - ), - }, -]; +const useGetChoicesFields = ['urgency', 'severity', 'impact']; +const defaultOptions: Options = { + urgency: [], + severity: [], + impact: [], +}; const ServiceNowParamsFields: React.FunctionComponent< - ActionParamsProps + ActionParamsProps > = ({ actionConnector, actionParams, editAction, index, errors, messageVariables }) => { + const { + http, + notifications: { toasts }, + } = useKibana().services; + const actionConnectorRef = useRef(actionConnector?.id ?? ''); const { incident, comments } = useMemo( () => @@ -53,10 +43,12 @@ const ServiceNowParamsFields: React.FunctionComponent< (({ incident: {}, comments: [], - } as unknown) as ServiceNowActionParams['subActionParams']), + } as unknown) as ServiceNowITSMActionParams['subActionParams']), [actionParams.subActionParams] ); + const [options, setOptions] = useState(defaultOptions); + const editSubActionProperty = useCallback( (key: string, value: any) => { const newProps = @@ -80,6 +72,28 @@ const ServiceNowParamsFields: React.FunctionComponent< [editSubActionProperty] ); + const onChoicesSuccess = (choices: Choice[]) => + setOptions( + choices.reduce( + (acc, choice) => ({ + ...acc, + [choice.element]: [ + ...(acc[choice.element] != null ? acc[choice.element] : []), + { value: choice.value, text: choice.label }, + ], + }), + defaultOptions + ) + ); + + const { isLoading: isLoadingChoices } = useGetChoices({ + http, + toastNotifications: toasts, + actionConnector, + fields: useGetChoicesFields, + onSuccess: onChoicesSuccess, + }); + useEffect(() => { if (actionConnector != null && actionConnectorRef.current !== actionConnector.id) { actionConnectorRef.current = actionConnector.id; @@ -94,6 +108,7 @@ const ServiceNowParamsFields: React.FunctionComponent< } // eslint-disable-next-line react-hooks/exhaustive-deps }, [actionConnector]); + useEffect(() => { if (!actionParams.subAction) { editAction('subAction', 'pushToService', index); @@ -114,64 +129,47 @@ const ServiceNowParamsFields: React.FunctionComponent< return ( -

- {i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.title', - { defaultMessage: 'Incident' } - )} -

+

{i18n.INCIDENT}

- + editSubActionProperty('urgency', e.target.value)} /> - + editSubActionProperty('severity', e.target.value)} /> - + editSubActionProperty('impact', e.target.value)} /> @@ -185,10 +183,7 @@ const ServiceNowParamsFields: React.FunctionComponent< errors['subActionParams.incident.short_description'].length > 0 && incident.short_description !== undefined } - label={i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.titleFieldLabel', - { defaultMessage: 'Short description (required)' } - )} + label={i18n.SHORT_DESCRIPTION_LABEL} > 0 ? comments[0].comment : undefined} - label={i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.serviceNow.commentsTextAreaFieldLabel', - { defaultMessage: 'Additional comments' } - )} + label={i18n.COMMENTS_LABEL} />
); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.test.tsx new file mode 100644 index 0000000000000..72dfd63da3d4e --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.test.tsx @@ -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 React from 'react'; +import { mount } from 'enzyme'; +import { act } from '@testing-library/react'; + +import { ActionConnector } from '../../../../types'; +import { useGetChoices } from './use_get_choices'; +import ServiceNowSIRParamsFields from './servicenow_sir_params'; +import { Choice } from './types'; + +jest.mock('./use_get_choices'); +jest.mock('../../../../common/lib/kibana'); + +const useGetChoicesMock = useGetChoices as jest.Mock; + +const actionParams = { + subAction: 'pushToService', + subActionParams: { + incident: { + short_description: 'sn title', + description: 'some description', + category: 'Denial of Service', + dest_ip: '192.168.1.1', + source_ip: '192.168.1.2', + malware_hash: '098f6bcd4621d373cade4e832627b4f6', + malware_url: 'https://attack.com', + priority: '1', + subcategory: '20', + externalId: null, + }, + comments: [], + }, +}; + +const connector: ActionConnector = { + secrets: {}, + config: {}, + id: 'test', + actionTypeId: '.test', + name: 'Test', + isPreconfigured: false, +}; + +const editAction = jest.fn(); +const defaultProps = { + actionConnector: connector, + actionParams, + errors: { ['subActionParams.incident.short_description']: [] }, + editAction, + index: 0, + messageVariables: [], +}; + +const choicesResponse = { + isLoading: false, + choices: [ + { + dependent_value: '', + label: 'Priviledge Escalation', + value: 'Priviledge Escalation', + element: 'category', + }, + { + dependent_value: '', + label: 'Criminal activity/investigation', + value: 'Criminal activity/investigation', + element: 'category', + }, + { + dependent_value: '', + label: 'Denial of Service', + value: 'Denial of Service', + element: 'category', + }, + { + dependent_value: 'Denial of Service', + label: 'Inbound or outbound', + value: '12', + element: 'subcategory', + }, + { + dependent_value: 'Denial of Service', + label: 'Single or distributed (DoS or DDoS)', + value: '26', + element: 'subcategory', + }, + { + dependent_value: 'Denial of Service', + label: 'Inbound DDos', + value: 'inbound_ddos', + element: 'subcategory', + }, + { + dependent_value: '', + label: '1 - Critical', + value: '1', + element: 'priority', + }, + { + dependent_value: '', + label: '2 - High', + value: '2', + element: 'priority', + }, + { + dependent_value: '', + label: '3 - Moderate', + value: '3', + element: 'priority', + }, + { + dependent_value: '', + label: '4 - Low', + value: '4', + element: 'priority', + }, + { + dependent_value: '', + label: '5 - Planning', + value: '5', + element: 'priority', + }, + ], +}; + +describe('ServiceNowSIRParamsFields renders', () => { + let onChoicesSuccess = (choices: Choice[]) => {}; + + beforeEach(() => { + jest.clearAllMocks(); + useGetChoicesMock.mockImplementation((args) => { + onChoicesSuccess = args.onSuccess; + return choicesResponse; + }); + }); + + test('all params fields is rendered', () => { + const wrapper = mount(); + expect(wrapper.find('[data-test-subj="short_descriptionInput"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="source_ipInput"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="dest_ipInput"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="malware_urlInput"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="malware_hashInput"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="prioritySelect"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="categorySelect"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="subcategorySelect"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="descriptionTextArea"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="commentsTextArea"]').exists()).toBeTruthy(); + }); + + test('If short_description has errors, form row is invalid', () => { + const newProps = { + ...defaultProps, + // eslint-disable-next-line @typescript-eslint/naming-convention + errors: { 'subActionParams.incident.short_description': ['error'] }, + }; + const wrapper = mount(); + const title = wrapper.find('[data-test-subj="short_descriptionInput"]').first(); + expect(title.prop('isInvalid')).toBeTruthy(); + }); + + test('When subActionParams is undefined, set to default', () => { + const { subActionParams, ...newParams } = actionParams; + + const newProps = { + ...defaultProps, + actionParams: newParams, + }; + mount(); + expect(editAction.mock.calls[0][1]).toEqual({ + incident: {}, + comments: [], + }); + }); + + test('When subAction is undefined, set to default', () => { + const { subAction, ...newParams } = actionParams; + + const newProps = { + ...defaultProps, + actionParams: newParams, + }; + mount(); + expect(editAction.mock.calls[0][1]).toEqual('pushToService'); + }); + + test('Resets fields when connector changes', () => { + const wrapper = mount(); + expect(editAction.mock.calls.length).toEqual(0); + wrapper.setProps({ actionConnector: { ...connector, id: '1234' } }); + expect(editAction.mock.calls.length).toEqual(1); + expect(editAction.mock.calls[0][1]).toEqual({ + incident: {}, + comments: [], + }); + }); + + test('it transforms the categories to options correctly', async () => { + const wrapper = mount(); + act(() => { + onChoicesSuccess(choicesResponse.choices); + }); + + wrapper.update(); + expect(wrapper.find('[data-test-subj="categorySelect"]').first().prop('options')).toEqual([ + { value: 'Priviledge Escalation', text: 'Priviledge Escalation' }, + { + value: 'Criminal activity/investigation', + text: 'Criminal activity/investigation', + }, + { value: 'Denial of Service', text: 'Denial of Service' }, + ]); + }); + + test('it transforms the subcategories to options correctly', async () => { + const wrapper = mount(); + act(() => { + onChoicesSuccess(choicesResponse.choices); + }); + + wrapper.update(); + expect(wrapper.find('[data-test-subj="subcategorySelect"]').first().prop('options')).toEqual([ + { + text: 'Inbound or outbound', + value: '12', + }, + { + text: 'Single or distributed (DoS or DDoS)', + value: '26', + }, + { + text: 'Inbound DDos', + value: 'inbound_ddos', + }, + ]); + }); + + test('it transforms the priorities to options correctly', async () => { + const wrapper = mount(); + act(() => { + onChoicesSuccess(choicesResponse.choices); + }); + + wrapper.update(); + expect(wrapper.find('[data-test-subj="prioritySelect"]').first().prop('options')).toEqual([ + { + text: '1 - Critical', + value: '1', + }, + { + text: '2 - High', + value: '2', + }, + { + text: '3 - Moderate', + value: '3', + }, + { + text: '4 - Low', + value: '4', + }, + { + text: '5 - Planning', + value: '5', + }, + ]); + }); + + describe('UI updates', () => { + const changeEvent = { target: { value: 'Bug' } } as React.ChangeEvent; + const simpleFields = [ + { dataTestSubj: 'input[data-test-subj="short_descriptionInput"]', key: 'short_description' }, + { dataTestSubj: 'textarea[data-test-subj="descriptionTextArea"]', key: 'description' }, + { dataTestSubj: '[data-test-subj="source_ipInput"]', key: 'source_ip' }, + { dataTestSubj: '[data-test-subj="dest_ipInput"]', key: 'dest_ip' }, + { dataTestSubj: '[data-test-subj="malware_urlInput"]', key: 'malware_url' }, + { dataTestSubj: '[data-test-subj="malware_hashInput"]', key: 'malware_hash' }, + { dataTestSubj: '[data-test-subj="prioritySelect"]', key: 'priority' }, + { dataTestSubj: '[data-test-subj="categorySelect"]', key: 'category' }, + { dataTestSubj: '[data-test-subj="subcategorySelect"]', key: 'subcategory' }, + ]; + + simpleFields.forEach((field) => + test(`${field.key} update triggers editAction :D`, () => { + const wrapper = mount(); + const theField = wrapper.find(field.dataTestSubj).first(); + theField.prop('onChange')!(changeEvent); + expect(editAction.mock.calls[0][1].incident[field.key]).toEqual(changeEvent.target.value); + }) + ); + + test('A comment triggers editAction', () => { + const wrapper = mount(); + const comments = wrapper.find('textarea[data-test-subj="commentsTextArea"]'); + expect(comments.simulate('change', changeEvent)); + expect(editAction.mock.calls[0][1].comments.length).toEqual(1); + }); + + test('An empty comment does not trigger editAction', () => { + const wrapper = mount(); + const emptyComment = { target: { value: '' } }; + const comments = wrapper.find('[data-test-subj="commentsTextArea"] textarea'); + expect(comments.simulate('change', emptyComment)); + expect(editAction.mock.calls.length).toEqual(0); + }); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.tsx new file mode 100644 index 0000000000000..26957d828f5e0 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/servicenow_sir_params.tsx @@ -0,0 +1,295 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { + EuiFormRow, + EuiSelect, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiTitle, + EuiSelectOption, +} from '@elastic/eui'; +import { useKibana } from '../../../../common/lib/kibana'; +import { ActionParamsProps } from '../../../../types'; +import { TextAreaWithMessageVariables } from '../../text_area_with_message_variables'; +import { TextFieldWithMessageVariables } from '../../text_field_with_message_variables'; + +import * as i18n from './translations'; +import { useGetChoices } from './use_get_choices'; +import { ServiceNowSIRActionParams, Fields, Choice } from './types'; + +const useGetChoicesFields = ['category', 'subcategory', 'priority']; +const defaultFields: Fields = { + category: [], + subcategory: [], + priority: [], +}; + +const choicesToEuiOptions = (choices: Choice[]): EuiSelectOption[] => + choices.map((choice) => ({ value: choice.value, text: choice.label })); + +const ServiceNowSIRParamsFields: React.FunctionComponent< + ActionParamsProps +> = ({ actionConnector, actionParams, editAction, index, errors, messageVariables }) => { + const { + http, + notifications: { toasts }, + } = useKibana().services; + + const actionConnectorRef = useRef(actionConnector?.id ?? ''); + const { incident, comments } = useMemo( + () => + actionParams.subActionParams ?? + (({ + incident: {}, + comments: [], + } as unknown) as ServiceNowSIRActionParams['subActionParams']), + [actionParams.subActionParams] + ); + + const [choices, setChoices] = useState(defaultFields); + + const editSubActionProperty = useCallback( + (key: string, value: any) => { + const newProps = + key !== 'comments' + ? { + incident: { ...incident, [key]: value }, + comments, + } + : { incident, [key]: value }; + editAction('subActionParams', newProps, index); + }, + [comments, editAction, incident, index] + ); + + const editComment = useCallback( + (key, value) => { + if (value.length > 0) { + editSubActionProperty(key, [{ commentId: '1', comment: value }]); + } + }, + [editSubActionProperty] + ); + + const onChoicesSuccess = useCallback((values: Choice[]) => { + setChoices( + values.reduce( + (acc, value) => ({ + ...acc, + [value.element]: [...(acc[value.element] != null ? acc[value.element] : []), value], + }), + defaultFields + ) + ); + }, []); + + const { isLoading: isLoadingChoices } = useGetChoices({ + http, + toastNotifications: toasts, + actionConnector, + // Not having a memoized fields variable will cause infinitive API calls. + fields: useGetChoicesFields, + onSuccess: onChoicesSuccess, + }); + + const categoryOptions = useMemo(() => choicesToEuiOptions(choices.category), [choices.category]); + const priorityOptions = useMemo(() => choicesToEuiOptions(choices.priority), [choices.priority]); + + const subcategoryOptions = useMemo( + () => + choicesToEuiOptions( + choices.subcategory.filter( + (subcategory) => subcategory.dependent_value === incident.category + ) + ), + [choices.subcategory, incident.category] + ); + + useEffect(() => { + if (actionConnector != null && actionConnectorRef.current !== actionConnector.id) { + actionConnectorRef.current = actionConnector.id; + editAction( + 'subActionParams', + { + incident: {}, + comments: [], + }, + index + ); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [actionConnector]); + + useEffect(() => { + if (!actionParams.subAction) { + editAction('subAction', 'pushToService', index); + } + if (!actionParams.subActionParams) { + editAction( + 'subActionParams', + { + incident: {}, + comments: [], + }, + index + ); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [actionParams]); + + return ( + + +

{i18n.INCIDENT}

+
+ + 0 && + incident.short_description !== undefined + } + label={i18n.SHORT_DESCRIPTION_LABEL} + > + + + + + + + + + + + + + + + + + + + + + { + editAction( + 'subActionParams', + { + incident: { ...incident, priority: e.target.value }, + comments, + }, + index + ); + }} + /> + + + + + + { + editAction( + 'subActionParams', + { + incident: { ...incident, category: e.target.value, subcategory: null }, + comments, + }, + index + ); + }} + /> + + + + + editSubActionProperty('subcategory', e.target.value)} + /> + + + + + + 0 ? comments[0].comment : undefined} + label={i18n.COMMENTS_LABEL} + /> +
+ ); +}; + +// eslint-disable-next-line import/no-default-export +export { ServiceNowSIRParamsFields as default }; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/translations.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/translations.ts index c84a916c0fef4..c8bc2f427bde2 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/translations.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/translations.ts @@ -6,17 +6,31 @@ import { i18n } from '@kbn/i18n'; -export const SERVICENOW_DESC = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.selectMessageText', +export const SERVICENOW_ITSM_DESC = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.serviceNowITSM.selectMessageText', { - defaultMessage: 'Create an incident in ServiceNow.', + defaultMessage: 'Create an incident in ServiceNow ITSM.', } ); -export const SERVICENOW_TITLE = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.actionTypeTitle', +export const SERVICENOW_SIR_DESC = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.serviceNowSIR.selectMessageText', { - defaultMessage: 'ServiceNow', + defaultMessage: 'Create an incident in ServiceNow SIR.', + } +); + +export const SERVICENOW_ITSM_TITLE = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.serviceNowITSM.actionTypeTitle', + { + defaultMessage: 'ServiceNow ITSM', + } +); + +export const SERVICENOW_SIR_TITLE = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.serviceNowSIR.actionTypeTitle', + { + defaultMessage: 'ServiceNow SIR', } ); @@ -98,65 +112,114 @@ export const PASSWORD_REQUIRED = i18n.translate( } ); -export const API_TOKEN_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.apiTokenTextFieldLabel', +export const TITLE_REQUIRED = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.common.requiredShortDescTextField', { - defaultMessage: 'Api token', + defaultMessage: 'Short description is required.', } ); -export const API_TOKEN_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredApiTokenTextField', +export const SOURCE_IP_LABEL = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.sourceIPTitle', { - defaultMessage: 'Api token is required.', + defaultMessage: 'Source IP', } ); -export const EMAIL_LABEL = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.emailTextFieldLabel', +export const DEST_IP_LABEL = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.destinationIPTitle', { - defaultMessage: 'Email', + defaultMessage: 'Destination IP', } ); -export const EMAIL_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.requiredEmailTextField', +export const INCIDENT = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.title', { - defaultMessage: 'Email is required.', + defaultMessage: 'Incident', } ); -export const MAPPING_FIELD_SHORT_DESC = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.mappingFieldShortDescription', +export const SHORT_DESCRIPTION_LABEL = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.titleFieldLabel', { - defaultMessage: 'Short Description', + defaultMessage: 'Short description (required)', } ); -export const MAPPING_FIELD_DESC = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.mappingFieldDescription', +export const DESCRIPTION_LABEL = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.descriptionTextAreaFieldLabel', { defaultMessage: 'Description', } ); -export const MAPPING_FIELD_COMMENTS = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.mappingFieldComments', +export const COMMENTS_LABEL = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.commentsTextAreaFieldLabel', { - defaultMessage: 'Comments', + defaultMessage: 'Additional comments', } ); -export const DESCRIPTION_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.common.requiredDescriptionTextField', +export const MALWARE_URL_LABEL = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.malwareURLTitle', { - defaultMessage: 'Description is required.', + defaultMessage: 'Malware URL', } ); -export const TITLE_REQUIRED = i18n.translate( - 'xpack.triggersActionsUI.components.builtinActionTypes.common.requiredShortDescTextField', +export const MALWARE_HASH_LABEL = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.malwareHashTitle', { - defaultMessage: 'Short description is required.', + defaultMessage: 'Malware hash', + } +); + +export const CHOICES_API_ERROR = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.unableToGetChoicesMessage', + { + defaultMessage: 'Unable to get choices', + } +); + +export const CATEGORY_LABEL = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.categoryTitle', + { + defaultMessage: 'Category', + } +); + +export const SUBCATEGORY_LABEL = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.subcategoryTitle', + { + defaultMessage: 'Subcategory', + } +); + +export const URGENCY_LABEL = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.urgencySelectFieldLabel', + { + defaultMessage: 'Urgency', + } +); + +export const SEVERITY_LABEL = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.severitySelectFieldLabel', + { + defaultMessage: 'Severity', + } +); + +export const IMPACT_LABEL = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.impactSelectFieldLabel', + { + defaultMessage: 'Impact', + } +); + +export const PRIORITY_LABEL = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.servicenow.prioritySelectFieldLabel', + { + defaultMessage: 'Priority', } ); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/types.ts b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/types.ts index ae03680a80534..be9a7c634af8a 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/types.ts +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/types.ts @@ -4,18 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ +import { EuiSelectOption } from '@elastic/eui'; import { UserConfiguredActionConnector } from '../../../../types'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { ExecutorSubActionPushParams } from '../../../../../../actions/server/builtin_action_types/servicenow/types'; +import { + ExecutorSubActionPushParamsITSM, + ExecutorSubActionPushParamsSIR, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../../actions/server/builtin_action_types/servicenow/types'; export type ServiceNowActionConnector = UserConfiguredActionConnector< ServiceNowConfig, ServiceNowSecrets >; -export interface ServiceNowActionParams { +export interface ServiceNowITSMActionParams { subAction: string; - subActionParams: ExecutorSubActionPushParams; + subActionParams: ExecutorSubActionPushParamsITSM; +} + +export interface ServiceNowSIRActionParams { + subAction: string; + subActionParams: ExecutorSubActionPushParamsSIR; } export interface ServiceNowConfig { @@ -26,3 +35,13 @@ export interface ServiceNowSecrets { username: string; password: string; } + +export interface Choice { + value: string; + label: string; + element: string; + dependent_value: string; +} + +export type Fields = Record; +export type Options = Record; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_choices.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_choices.test.tsx new file mode 100644 index 0000000000000..4e8061ebaa6e9 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_choices.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 { renderHook } from '@testing-library/react-hooks'; + +import { useKibana } from '../../../../common/lib/kibana'; +import { ActionConnector } from '../../../../types'; +import { useGetChoices, UseGetChoices, UseGetChoicesProps } from './use_get_choices'; +import { getChoices } from './api'; + +jest.mock('./api'); +jest.mock('../../../../common/lib/kibana'); + +const useKibanaMock = useKibana as jest.Mocked; +const getChoicesMock = getChoices as jest.Mock; +const onSuccess = jest.fn(); + +const actionConnector = { + secrets: { + username: 'user', + password: 'pass', + }, + id: 'test', + actionTypeId: '.servicenow', + name: 'ServiceNow ITSM', + isPreconfigured: false, + config: { + apiUrl: 'https://dev94428.service-now.com/', + }, +} as ActionConnector; + +const getChoicesResponse = [ + { + dependent_value: '', + label: 'Priviledge Escalation', + value: 'Priviledge Escalation', + element: 'category', + }, + { + dependent_value: '', + label: 'Criminal activity/investigation', + value: 'Criminal activity/investigation', + element: 'category', + }, + { + dependent_value: '', + label: 'Denial of Service', + value: 'Denial of Service', + element: 'category', + }, +]; + +describe('useGetChoices', () => { + const { services } = useKibanaMock(); + getChoicesMock.mockResolvedValue({ + data: getChoicesResponse, + }); + + beforeEach(() => { + jest.clearAllMocks(); + }); + + const fields = ['priority']; + + it('init', async () => { + const { result, waitForNextUpdate } = renderHook(() => + useGetChoices({ + http: services.http, + actionConnector, + toastNotifications: services.notifications.toasts, + fields, + onSuccess, + }) + ); + + await waitForNextUpdate(); + + expect(result.current).toEqual({ + isLoading: false, + choices: getChoicesResponse, + }); + }); + + it('returns an empty array when connector is not presented', async () => { + const { result } = renderHook(() => + useGetChoices({ + http: services.http, + actionConnector: undefined, + toastNotifications: services.notifications.toasts, + fields, + onSuccess, + }) + ); + + expect(result.current).toEqual({ + isLoading: false, + choices: [], + }); + }); + + it('it calls onSuccess', async () => { + const { waitForNextUpdate } = renderHook(() => + useGetChoices({ + http: services.http, + actionConnector, + toastNotifications: services.notifications.toasts, + fields, + onSuccess, + }) + ); + + await waitForNextUpdate(); + + expect(onSuccess).toHaveBeenCalledWith(getChoicesResponse); + }); + + it('it displays an error when service fails', async () => { + getChoicesMock.mockResolvedValue({ + status: 'error', + serviceMessage: 'An error occurred', + }); + + const { waitForNextUpdate } = renderHook(() => + useGetChoices({ + http: services.http, + actionConnector, + toastNotifications: services.notifications.toasts, + fields, + onSuccess, + }) + ); + + await waitForNextUpdate(); + + expect(services.notifications.toasts.addDanger).toHaveBeenCalledWith({ + text: 'An error occurred', + title: 'Unable to get choices', + }); + }); + + it('it displays an error when http throws an error', async () => { + getChoicesMock.mockImplementation(() => { + throw new Error('An error occurred'); + }); + + renderHook(() => + useGetChoices({ + http: services.http, + actionConnector, + toastNotifications: services.notifications.toasts, + fields, + onSuccess, + }) + ); + + expect(services.notifications.toasts.addDanger).toHaveBeenCalledWith({ + text: 'An error occurred', + title: 'Unable to get choices', + }); + }); +}); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_choices.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_choices.tsx new file mode 100644 index 0000000000000..0e4338cec0e18 --- /dev/null +++ b/x-pack/plugins/triggers_actions_ui/public/application/components/builtin_action_types/servicenow/use_get_choices.tsx @@ -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 { useState, useEffect, useRef, useCallback } from 'react'; +import { HttpSetup, ToastsApi } from 'kibana/public'; +import { ActionConnector } from '../../../../types'; +import { getChoices } from './api'; +import { Choice } from './types'; +import * as i18n from './translations'; + +export interface UseGetChoicesProps { + http: HttpSetup; + toastNotifications: Pick< + ToastsApi, + 'get$' | 'add' | 'remove' | 'addSuccess' | 'addWarning' | 'addDanger' | 'addError' + >; + actionConnector?: ActionConnector; + fields: string[]; + onSuccess?: (choices: Choice[]) => void; +} + +export interface UseGetChoices { + choices: Choice[]; + isLoading: boolean; +} + +export const useGetChoices = ({ + http, + actionConnector, + toastNotifications, + fields, + onSuccess, +}: UseGetChoicesProps): UseGetChoices => { + const [isLoading, setIsLoading] = useState(false); + const [choices, setChoices] = useState([]); + const didCancel = useRef(false); + const abortCtrl = useRef(new AbortController()); + + const fetchData = useCallback(async () => { + if (!actionConnector) { + setIsLoading(false); + return; + } + + try { + didCancel.current = false; + abortCtrl.current.abort(); + abortCtrl.current = new AbortController(); + setIsLoading(true); + + const res = await getChoices({ + http, + signal: abortCtrl.current.signal, + connectorId: actionConnector.id, + fields, + }); + + if (!didCancel.current) { + setIsLoading(false); + setChoices(res.data ?? []); + if (res.status && res.status === 'error') { + toastNotifications.addDanger({ + title: i18n.CHOICES_API_ERROR, + text: `${res.serviceMessage ?? res.message}`, + }); + } else if (onSuccess) { + onSuccess(res.data ?? []); + } + } + } catch (error) { + if (!didCancel.current) { + setIsLoading(false); + toastNotifications.addDanger({ + title: i18n.CHOICES_API_ERROR, + text: error.message, + }); + } + } + }, [actionConnector, http, fields, onSuccess, toastNotifications]); + + useEffect(() => { + fetchData(); + + return () => { + didCancel.current = true; + abortCtrl.current.abort(); + setIsLoading(false); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + return { + choices, + isLoading, + }; +}; diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx index 13443e0b68245..9cde2802e0c40 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.test.tsx @@ -550,7 +550,9 @@ describe('action_form', () => { ]); expect(setHasActionsWithBrokenConnector).toHaveBeenLastCalledWith(true); expect(wrapper.find(EuiAccordion)).toHaveLength(3); - expect(wrapper.find(`div[data-test-subj="alertActionAccordionCallout"]`)).toHaveLength(2); + expect( + wrapper.find(`EuiIconTip[data-test-subj="alertActionAccordionErrorTooltip"]`) + ).toHaveLength(2); }); }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx index 2145443ba044c..ed6ea6a73f242 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/action_form.tsx @@ -34,7 +34,11 @@ import { ActionTypeForm, ActionTypeFormProps } from './action_type_form'; import { AddConnectorInline } from './connector_add_inline'; import { actionTypeCompare } from '../../lib/action_type_compare'; import { checkActionFormActionTypeEnabled } from '../../lib/check_action_type_enabled'; -import { VIEW_LICENSE_OPTIONS_LINK, DEFAULT_HIDDEN_ACTION_TYPES } from '../../../common/constants'; +import { + VIEW_LICENSE_OPTIONS_LINK, + DEFAULT_HIDDEN_ACTION_TYPES, + DEFAULT_HIDDEN_ONLY_ON_ALERTS_ACTION_TYPES, +} from '../../../common/constants'; import { ActionGroup, AlertActionParam } from '../../../../../alerts/common'; import { useKibana } from '../../../common/lib/kibana'; import { DefaultActionParamsGetter } from '../../lib/get_defaults_for_action_params'; @@ -230,9 +234,15 @@ export const ActionForm = ({ .list() /** * TODO: Remove when cases connector is available across Kibana. Issue: https://github.com/elastic/kibana/issues/82502. + * TODO: Need to decide about ServiceNow SIR connector. * If actionTypes are set, hidden connectors are filtered out. Otherwise, they are not. */ - .filter(({ id }) => actionTypes ?? !DEFAULT_HIDDEN_ACTION_TYPES.includes(id)) + .filter( + ({ id }) => + actionTypes ?? + (!DEFAULT_HIDDEN_ACTION_TYPES.includes(id) && + !DEFAULT_HIDDEN_ONLY_ON_ALERTS_ACTION_TYPES.includes(id)) + ) .filter((item) => actionTypesIndex[item.id]) .filter((item) => !!item.actionParamsFields) .sort((a, b) => @@ -308,6 +318,7 @@ export const ActionForm = ({ key={`action-form-action-at-${index}`} actionTypeRegistry={actionTypeRegistry} emptyActionsIds={emptyActionsIds} + connectors={connectors} onDeleteConnector={() => { const updatedActions = actions.filter( (_item: AlertAction, i: number) => i !== index @@ -330,6 +341,9 @@ export const ActionForm = ({ }); setAddModalVisibility(true); }} + onSelectConnector={(connectorId: string) => { + setActionIdByIndex(connectorId, index); + }} /> ); } diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx index 6ffe730658d3d..c2e96e6f3c0ef 100644 --- a/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/action_connector_form/connector_add_inline.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment } from 'react'; +import React, { Fragment, useEffect, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { @@ -18,8 +18,13 @@ import { EuiEmptyPrompt, EuiCallOut, EuiText, + EuiFormRow, + EuiButtonEmpty, + EuiComboBox, + EuiComboBoxOptionOption, + EuiIconTip, } from '@elastic/eui'; -import { AlertAction, ActionTypeIndex } from '../../../types'; +import { AlertAction, ActionTypeIndex, ActionConnector } from '../../../types'; import { hasSaveActionsCapability } from '../../lib/capabilities'; import { ActionAccordionFormProps } from './action_form'; import { useKibana } from '../../../common/lib/kibana'; @@ -27,9 +32,11 @@ import { useKibana } from '../../../common/lib/kibana'; type AddConnectorInFormProps = { actionTypesIndex: ActionTypeIndex; actionItem: AlertAction; + connectors: ActionConnector[]; index: number; onAddConnector: () => void; onDeleteConnector: () => void; + onSelectConnector: (connectorId: string) => void; emptyActionsIds: string[]; } & Pick; @@ -37,8 +44,10 @@ export const AddConnectorInline = ({ actionTypesIndex, actionItem, index, + connectors, onAddConnector, onDeleteConnector, + onSelectConnector, actionTypeRegistry, emptyActionsIds, }: AddConnectorInFormProps) => { @@ -46,10 +55,14 @@ export const AddConnectorInline = ({ application: { capabilities }, } = useKibana().services; const canSave = hasSaveActionsCapability(capabilities); + const [connectorOptionsList, setConnectorOptionsList] = useState([]); + const [isEmptyActionId, setIsEmptyActionId] = useState(false); + const [errors, setErrors] = useState([]); const actionTypeName = actionTypesIndex ? actionTypesIndex[actionItem.actionTypeId].name : actionItem.actionTypeId; + const actionType = actionTypesIndex[actionItem.actionTypeId]; const actionTypeRegistered = actionTypeRegistry.get(actionItem.actionTypeId); const noConnectorsLabel = ( @@ -61,6 +74,92 @@ export const AddConnectorInline = ({ }} /> ); + + const unableToLoadConnectorLabel = ( + + + + ); + + useEffect(() => { + if (connectors) { + const altConnectorOptions = connectors + .filter( + (connector) => + connector.actionTypeId === actionItem.actionTypeId && + // include only enabled by config connectors or preconfigured + (actionType?.enabledInConfig || connector.isPreconfigured) + ) + .map(({ name, id, isPreconfigured }) => ({ + label: `${name} ${isPreconfigured ? '(preconfigured)' : ''}`, + key: id, + id, + })); + setConnectorOptionsList(altConnectorOptions); + + if (altConnectorOptions.length > 0) { + setErrors([`Unable to load ${actionTypeRegistered.actionTypeTitle} connector`]); + } + } + + setIsEmptyActionId(!!emptyActionsIds.find((emptyId: string) => actionItem.id === emptyId)); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const connectorsDropdown = ( + + + + } + labelAppend={ + + + + } + error={errors} + isInvalid={errors.length > 0} + > + { + // On selecting a option from this combo box, this component will + // be removed but the EuiComboBox performs some additional updates on + // closing the dropdown. Wrapping in a `setTimeout` to avoid `React state + // update on an unmounted component` warnings. + setTimeout(() => { + onSelectConnector(selectedOptions[0].id ?? ''); + }); + }} + isClearable={false} + /> + + + + ); + return (
+ {!isEmptyActionId && ( + + + } + /> + + )}
} extraAction={ @@ -106,38 +221,27 @@ export const AddConnectorInline = ({ paddingSize="l" > {canSave ? ( - actionItem.id === emptyId) ? ( - noConnectorsLabel - ) : ( - - ) - } - actions={[ - - - , - ]} - /> + connectorOptionsList.length > 0 ? ( + connectorsDropdown + ) : ( + + + + } + /> + ) ) : (

diff --git a/x-pack/plugins/triggers_actions_ui/public/common/constants/index.ts b/x-pack/plugins/triggers_actions_ui/public/common/constants/index.ts index 833ed915fad59..8832f8b826eab 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/constants/index.ts +++ b/x-pack/plugins/triggers_actions_ui/public/common/constants/index.ts @@ -11,3 +11,5 @@ export { builtInGroupByTypes } from './group_by_types'; export const VIEW_LICENSE_OPTIONS_LINK = 'https://www.elastic.co/subscriptions'; // TODO: Remove when cases connector is available across Kibana. Issue: https://github.com/elastic/kibana/issues/82502. export const DEFAULT_HIDDEN_ACTION_TYPES = ['.case']; +// Action types included in this array will be hidden only from the alert's action type node list +export const DEFAULT_HIDDEN_ONLY_ON_ALERTS_ACTION_TYPES = ['.servicenow-sir']; diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/of.test.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/of.test.tsx index b01104a8d5cf7..413d17dcf76c4 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/of.test.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/of.test.tsx @@ -132,4 +132,22 @@ describe('of expression', () => { ) ).toBeTruthy(); }); + + it('renders a helptext when passed as a prop', () => { + const onChangeSelectedAggField = jest.fn(); + const wrapper = shallow( + + ); + + expect(wrapper.find('[data-test-subj="availablefieldsOptionsFormRow"]').prop('helpText')).toBe( + 'Helptext test message' + ); + }); }); diff --git a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/of.tsx b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/of.tsx index c6da09ea716c1..00971215b4704 100644 --- a/x-pack/plugins/triggers_actions_ui/public/common/expression_items/of.tsx +++ b/x-pack/plugins/triggers_actions_ui/public/common/expression_items/of.tsx @@ -44,6 +44,7 @@ interface OfExpressionProps { | 'rightUp' | 'rightDown'; display?: 'fullWidth' | 'inline'; + helpText?: string | JSX.Element; } export const OfExpression = ({ @@ -55,6 +56,7 @@ export const OfExpression = ({ display = 'inline', customAggTypesOptions, popupPosition, + helpText, }: OfExpressionProps) => { const [aggFieldPopoverOpen, setAggFieldPopoverOpen] = useState(false); const firstFieldOption = { @@ -119,6 +121,8 @@ export const OfExpression = ({ fullWidth isInvalid={errors.aggField.length > 0 && aggField !== undefined} error={errors.aggField} + data-test-subj="availablefieldsOptionsFormRow" + helpText={helpText} > { it('returns a list of unique node versions', async () => { const adminClient = ({ diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts index 713ba3a54178a..9a6ac4030e051 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts @@ -297,7 +297,7 @@ describe('ReindexActions', () => { } as RequestEvent); it('returns flat settings', async () => { - clusterClient.asCurrentUser.indices.getSettings.mockResolvedValueOnce( + clusterClient.asCurrentUser.indices.get.mockResolvedValueOnce( asApiResponse({ myIndex: { settings: { 'index.mySetting': '1' }, @@ -312,7 +312,7 @@ describe('ReindexActions', () => { }); it('returns null if index does not exist', async () => { - clusterClient.asCurrentUser.indices.getSettings.mockResolvedValueOnce(asApiResponse({})); + clusterClient.asCurrentUser.indices.get.mockResolvedValueOnce(asApiResponse({})); await expect(actions.getFlatSettings('myIndex')).resolves.toBeNull(); }); }); diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.ts index 611ab3c92b72b..653bf8336255b 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.ts @@ -236,7 +236,7 @@ export const reindexActionsFactory = ( }, async getFlatSettings(indexName: string) { - const { body: flatSettings } = await esClient.indices.getSettings<{ + const { body: flatSettings } = await esClient.indices.get<{ [indexName: string]: FlatSettings; }>({ index: indexName, diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts index b34f26be6e99c..29c8207a5f284 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts @@ -832,7 +832,7 @@ describe('reindexService', () => { }); it('fails if create index is not acknowledged', async () => { - clusterClient.asCurrentUser.indices.getSettings.mockResolvedValueOnce( + clusterClient.asCurrentUser.indices.get.mockResolvedValueOnce( asApiResponse({ myIndex: settingsMappings }) ); @@ -847,7 +847,7 @@ describe('reindexService', () => { }); it('fails if create index fails', async () => { - clusterClient.asCurrentUser.indices.getSettings.mockResolvedValueOnce( + clusterClient.asCurrentUser.indices.get.mockResolvedValueOnce( asApiResponse({ myIndex: settingsMappings }) ); diff --git a/x-pack/plugins/upgrade_assistant/tsconfig.json b/x-pack/plugins/upgrade_assistant/tsconfig.json new file mode 100644 index 0000000000000..220ae4d0f0160 --- /dev/null +++ b/x-pack/plugins/upgrade_assistant/tsconfig.json @@ -0,0 +1,26 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "common/**/*", + "public/**/*", + "server/**/*", + // have to declare *.json explicitly due to https://github.com/microsoft/TypeScript/issues/25636 + "public/**/*.json", + "server/**/*.json" + ], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + { "path": "../../../src/plugins/management/tsconfig.json" }, + { "path": "../../../src/plugins/usage_collection/tsconfig.json" }, + { "path": "../cloud/tsconfig.json" }, + { "path": "../features/tsconfig.json" }, + { "path": "../licensing/tsconfig.json" }, + ] +} diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp.tsx deleted file mode 100644 index be4f0fc62271d..0000000000000 --- a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp.tsx +++ /dev/null @@ -1,219 +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, { useContext, useEffect, useState } from 'react'; -import { - EuiButtonIcon, - EuiFlexGroup, - EuiFlexItem, - EuiImage, - EuiSpacer, - EuiText, - EuiLoadingSpinner, -} from '@elastic/eui'; -import useIntersection from 'react-use/lib/useIntersection'; -import moment from 'moment'; -import styled from 'styled-components'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { Ping } from '../../../../../common/runtime_types/ping'; -import { getShortTimeStamp } from '../../../overview/monitor_list/columns/monitor_status_column'; -import { euiStyled } from '../../../../../../../../src/plugins/kibana_react/common'; -import { useFetcher, FETCH_STATUS } from '../../../../../../observability/public'; -import { getJourneyScreenshot } from '../../../../state/api/journey'; -import { UptimeSettingsContext } from '../../../../contexts'; - -const StepImage = styled(EuiImage)` - &&& { - display: flex; - figcaption { - white-space: nowrap; - align-self: center; - margin-left: 8px; - margin-top: 8px; - text-decoration: none !important; - } - } -`; - -const StepDiv = styled.div` - figure.euiImage { - div.stepArrowsFullScreen { - display: none; - } - } - - figure.euiImage-isFullScreen { - div.stepArrowsFullScreen { - display: flex; - } - } - position: relative; - div.stepArrows { - display: none; - } - :hover { - div.stepArrows { - display: flex; - } - } -`; - -interface Props { - timestamp: string; - ping: Ping; -} - -export const PingTimestamp = ({ timestamp, ping }: Props) => { - const [stepNo, setStepNo] = useState(1); - - const [stepImages, setStepImages] = useState([]); - - const intersectionRef = React.useRef(null); - - const { basePath } = useContext(UptimeSettingsContext); - - const imgPath = basePath + `/api/uptime/journey/screenshot/${ping.monitor.check_group}/${stepNo}`; - - const intersection = useIntersection(intersectionRef, { - root: null, - rootMargin: '0px', - threshold: 1, - }); - - const { data, status } = useFetcher(() => { - if (intersection && intersection.intersectionRatio === 1 && !stepImages[stepNo - 1]) - return getJourneyScreenshot(imgPath); - }, [intersection?.intersectionRatio, stepNo]); - - useEffect(() => { - if (data) { - setStepImages((prevState) => [...prevState, data?.src]); - } - }, [data]); - - const imgSrc = stepImages[stepNo] || data?.src; - - const isLoading = status === FETCH_STATUS.LOADING; - const isPending = status === FETCH_STATUS.PENDING; - - const captionContent = `Step:${stepNo} ${data?.stepName}`; - - const ImageCaption = ( - <> -

- {imgSrc && ( - - - { - setStepNo(stepNo - 1); - }} - iconType="arrowLeft" - aria-label="Next" - /> - - - {captionContent} - - - { - setStepNo(stepNo + 1); - }} - iconType="arrowRight" - aria-label="Next" - /> - - - )} -
- {/* TODO: Add link to details page once it's available */} - {getShortTimeStamp(moment(timestamp))} - - - ); - - return ( - - {imgSrc ? ( - - ) : ( - - - {isLoading || isPending ? ( - - ) : ( - - )} - - {ImageCaption} - - )} - - - { - setStepNo(stepNo - 1); - }} - iconType="arrowLeft" - aria-label="Next" - /> - - - { - setStepNo(stepNo + 1); - }} - iconType="arrowRight" - aria-label="Next" - /> - - - - ); -}; - -const BorderedText = euiStyled(EuiText)` - width: 120px; - text-align: center; - border: 1px solid ${(props) => props.theme.eui.euiColorLightShade}; -`; - -export const NoImageAvailable = () => { - return ( - - - - - - ); -}; diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/index.ts b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/index.ts new file mode 100644 index 0000000000000..db9c18e30cfc1 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/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 { PingTimestamp } from './ping_timestamp'; diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/nav_buttons.test.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/nav_buttons.test.tsx new file mode 100644 index 0000000000000..c8acfd48a9913 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/nav_buttons.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 { fireEvent, waitFor } from '@testing-library/react'; +import React from 'react'; +import { render } from '../../../../../lib/helper/rtl_helpers'; +import { NavButtons, NavButtonsProps } from './nav_buttons'; + +describe('NavButtons', () => { + let defaultProps: NavButtonsProps; + + beforeEach(() => { + defaultProps = { + maxSteps: 3, + stepNumber: 2, + setStepNumber: jest.fn(), + setIsImagePopoverOpen: jest.fn(), + }; + }); + + it('labels prev and next buttons', () => { + const { getByLabelText } = render(); + + expect(getByLabelText('Previous step')); + expect(getByLabelText('Next step')); + }); + + it('increments step number on next click', async () => { + const { getByLabelText } = render(); + + const nextButton = getByLabelText('Next step'); + + fireEvent.click(nextButton); + + await waitFor(() => { + expect(defaultProps.setStepNumber).toHaveBeenCalledTimes(1); + expect(defaultProps.setStepNumber).toHaveBeenCalledWith(3); + }); + }); + + it('decrements step number on prev click', async () => { + const { getByLabelText } = render(); + + const nextButton = getByLabelText('Previous step'); + + fireEvent.click(nextButton); + + await waitFor(() => { + expect(defaultProps.setStepNumber).toHaveBeenCalledTimes(1); + expect(defaultProps.setStepNumber).toHaveBeenCalledWith(1); + }); + }); + + it('disables `next` button on final step', () => { + defaultProps.stepNumber = 3; + + const { getByLabelText } = render(); + + // getByLabelText('Next step'); + expect(getByLabelText('Next step')).toHaveAttribute('disabled'); + expect(getByLabelText('Previous step')).not.toHaveAttribute('disabled'); + }); + + it('disables `prev` button on final step', () => { + defaultProps.stepNumber = 1; + + const { getByLabelText } = render(); + + expect(getByLabelText('Next step')).not.toHaveAttribute('disabled'); + expect(getByLabelText('Previous step')).toHaveAttribute('disabled'); + }); + + it('opens popover when mouse enters', async () => { + const { getByLabelText } = render(); + + const nextButton = getByLabelText('Next step'); + + fireEvent.mouseEnter(nextButton); + + await waitFor(() => { + expect(defaultProps.setIsImagePopoverOpen).toHaveBeenCalledTimes(1); + expect(defaultProps.setIsImagePopoverOpen).toHaveBeenCalledWith(true); + }); + }); +}); diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/nav_buttons.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/nav_buttons.tsx new file mode 100644 index 0000000000000..1c24caba6a917 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/nav_buttons.tsx @@ -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 { EuiButtonIcon, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; +import React from 'react'; +import { nextAriaLabel, prevAriaLabel } from './translations'; + +export interface NavButtonsProps { + maxSteps?: number; + setIsImagePopoverOpen: React.Dispatch>; + setStepNumber: React.Dispatch>; + stepNumber: number; +} + +export const NavButtons: React.FC = ({ + maxSteps, + setIsImagePopoverOpen, + setStepNumber, + stepNumber, +}) => ( + setIsImagePopoverOpen(true)} + style={{ position: 'absolute', bottom: 0, left: 30 }} + > + + { + setStepNumber(stepNumber - 1); + }} + iconType="arrowLeft" + aria-label={prevAriaLabel} + /> + + + { + setStepNumber(stepNumber + 1); + }} + iconType="arrowRight" + aria-label={nextAriaLabel} + /> + + +); diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/no_image_available.test.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/no_image_available.test.tsx new file mode 100644 index 0000000000000..17e679846a66d --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/no_image_available.test.tsx @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { render } from '../../../../../lib/helper/rtl_helpers'; +import { NoImageAvailable } from './no_image_available'; + +describe('NoImageAvailable', () => { + it('renders expected text', () => { + const { getByText } = render(); + + expect(getByText('No image available')); + }); +}); diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/no_image_available.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/no_image_available.tsx new file mode 100644 index 0000000000000..2498e07969f11 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/no_image_available.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React from 'react'; +import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common'; + +const BorderedText = euiStyled(EuiText)` + width: 120px; + text-align: center; + border: 1px solid ${(props) => props.theme.eui.euiColorLightShade}; +`; + +export const NoImageAvailable = () => { + return ( + + + + + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/no_image_display.test.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/no_image_display.test.tsx new file mode 100644 index 0000000000000..24080e2f4061d --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/no_image_display.test.tsx @@ -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 React from 'react'; +import { render } from '../../../../../lib/helper/rtl_helpers'; +import { NoImageDisplay, NoImageDisplayProps } from './no_image_display'; +import { imageLoadingSpinnerAriaLabel } from './translations'; + +describe('NoImageDisplay', () => { + let defaultProps: NoImageDisplayProps; + beforeEach(() => { + defaultProps = { + imageCaption:
test caption
, + isLoading: false, + isPending: false, + }; + }); + + it('renders a loading spinner for loading state', () => { + defaultProps.isLoading = true; + const { getByText, getByLabelText } = render(); + + expect(getByLabelText(imageLoadingSpinnerAriaLabel)); + expect(getByText('test caption')); + }); + + it('renders a loading spinner for pending state', () => { + defaultProps.isPending = true; + const { getByText, getByLabelText } = render(); + + expect(getByLabelText(imageLoadingSpinnerAriaLabel)); + expect(getByText('test caption')); + }); + + it('renders no image available when not loading or pending', () => { + const { getByText } = render(); + + expect(getByText('No image available')); + expect(getByText('test caption')); + }); +}); diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/no_image_display.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/no_image_display.tsx new file mode 100644 index 0000000000000..185f488d5acd2 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/no_image_display.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 { EuiFlexItem, EuiFlexGroup, EuiLoadingSpinner } from '@elastic/eui'; +import React from 'react'; +import { NoImageAvailable } from './no_image_available'; +import { imageLoadingSpinnerAriaLabel } from './translations'; + +export interface NoImageDisplayProps { + imageCaption: JSX.Element; + isLoading: boolean; + isPending: boolean; +} + +export const NoImageDisplay: React.FC = ({ + imageCaption, + isLoading, + isPending, +}) => { + return ( + + + {isLoading || isPending ? ( + + ) : ( + + )} + + {imageCaption} + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp.test.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.test.tsx similarity index 70% rename from x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp.test.tsx rename to x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.test.tsx index 1baeb8a69d34c..a934f6fa39b22 100644 --- a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp.test.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.test.tsx @@ -5,16 +5,17 @@ */ import React from 'react'; +import { fireEvent, waitFor } from '@testing-library/react'; import { PingTimestamp } from './ping_timestamp'; -import { mockReduxHooks } from '../../../../lib/helper/test_helpers'; -import { render } from '../../../../lib/helper/rtl_helpers'; -import { Ping } from '../../../../../common/runtime_types/ping'; -import * as observabilityPublic from '../../../../../../observability/public'; +import { mockReduxHooks } from '../../../../../lib/helper/test_helpers'; +import { render } from '../../../../../lib/helper/rtl_helpers'; +import { Ping } from '../../../../../../common/runtime_types/ping'; +import * as observabilityPublic from '../../../../../../../observability/public'; mockReduxHooks(); -jest.mock('../../../../../../observability/public', () => { - const originalModule = jest.requireActual('../../../../../../observability/public'); +jest.mock('../../../../../../../observability/public', () => { + const originalModule = jest.requireActual('../../../../../../../observability/public'); return { ...originalModule, @@ -92,4 +93,26 @@ describe('Ping Timestamp component', () => { const { container } = render(); expect(container.querySelector('img')?.src).toBe(src); }); + + it('displays popover image when mouse enters img caption, and hides onLeave', async () => { + const src = 'http://sample.com/sampleImageSrc.png'; + jest.spyOn(observabilityPublic, 'useFetcher').mockReturnValue({ + status: FETCH_STATUS.SUCCESS, + data: { src }, + refetch: () => null, + }); + const { getByAltText, getByText, queryByAltText } = render( + + ); + const caption = getByText('Nov 26, 2020 10:28:56 AM'); + fireEvent.mouseEnter(caption); + + const altText = `A larger version of the screenshot for this journey step's thumbnail.`; + + await waitFor(() => getByAltText(altText)); + + fireEvent.mouseLeave(caption); + + await waitFor(() => expect(queryByAltText(altText)).toBeNull()); + }); }); diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.tsx new file mode 100644 index 0000000000000..6d605f25f6f68 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/ping_timestamp.tsx @@ -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 React, { useContext, useEffect, useState } from 'react'; +import useIntersection from 'react-use/lib/useIntersection'; +import styled from 'styled-components'; +import { Ping } from '../../../../../../common/runtime_types/ping'; +import { useFetcher, FETCH_STATUS } from '../../../../../../../observability/public'; +import { getJourneyScreenshot } from '../../../../../state/api/journey'; +import { UptimeSettingsContext } from '../../../../../contexts'; +import { NavButtons } from './nav_buttons'; +import { NoImageDisplay } from './no_image_display'; +import { StepImageCaption } from './step_image_caption'; +import { StepImagePopover } from './step_image_popover'; +import { formatCaptionContent } from './translations'; + +const StepDiv = styled.div` + figure.euiImage { + div.stepArrowsFullScreen { + display: none; + } + } + + figure.euiImage-isFullScreen { + div.stepArrowsFullScreen { + display: flex; + } + } + position: relative; + div.stepArrows { + display: none; + } + :hover { + div.stepArrows { + display: flex; + } + } +`; + +interface Props { + timestamp: string; + ping: Ping; +} + +export const PingTimestamp = ({ timestamp, ping }: Props) => { + const [stepNumber, setStepNumber] = useState(1); + const [isImagePopoverOpen, setIsImagePopoverOpen] = useState(false); + + const [stepImages, setStepImages] = useState([]); + + const intersectionRef = React.useRef(null); + + const { basePath } = useContext(UptimeSettingsContext); + + const imgPath = `${basePath}/api/uptime/journey/screenshot/${ping.monitor.check_group}/${stepNumber}`; + + const intersection = useIntersection(intersectionRef, { + root: null, + rootMargin: '0px', + threshold: 1, + }); + + const { data, status } = useFetcher(() => { + if (intersection && intersection.intersectionRatio === 1 && !stepImages[stepNumber - 1]) + return getJourneyScreenshot(imgPath); + }, [intersection?.intersectionRatio, stepNumber]); + + useEffect(() => { + if (data) { + setStepImages((prevState) => [...prevState, data?.src]); + } + }, [data]); + + const imgSrc = stepImages[stepNumber] || data?.src; + + const captionContent = formatCaptionContent(stepNumber, data?.maxSteps); + + const ImageCaption = ( + + ); + + return ( + setIsImagePopoverOpen(true)} + onMouseLeave={() => setIsImagePopoverOpen(false)} + ref={intersectionRef} + > + {imgSrc ? ( + + ) : ( + + )} + + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/step_image_caption.test.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/step_image_caption.test.tsx new file mode 100644 index 0000000000000..ef1d0cb388a18 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/step_image_caption.test.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 { fireEvent, waitFor } from '@testing-library/react'; +import React from 'react'; +import { render } from '../../../../../lib/helper/rtl_helpers'; +import { StepImageCaption, StepImageCaptionProps } from './step_image_caption'; + +describe('StepImageCaption', () => { + let defaultProps: StepImageCaptionProps; + + beforeEach(() => { + defaultProps = { + captionContent: 'test caption content', + imgSrc: 'http://sample.com/sampleImageSrc.png', + maxSteps: 3, + setStepNumber: jest.fn(), + stepNumber: 2, + timestamp: '2020-11-26T15:28:56.896Z', + }; + }); + + it('labels prev and next buttons', () => { + const { getByLabelText } = render(); + + expect(getByLabelText('Previous step')); + expect(getByLabelText('Next step')); + }); + + it('increments step number on next click', async () => { + const { getByLabelText } = render(); + + const nextButton = getByLabelText('Next step'); + + fireEvent.click(nextButton); + + await waitFor(() => { + expect(defaultProps.setStepNumber).toHaveBeenCalledTimes(1); + expect(defaultProps.setStepNumber).toHaveBeenCalledWith(3); + }); + }); + + it('decrements step number on prev click', async () => { + const { getByLabelText } = render(); + + const nextButton = getByLabelText('Previous step'); + + fireEvent.click(nextButton); + + await waitFor(() => { + expect(defaultProps.setStepNumber).toHaveBeenCalledTimes(1); + expect(defaultProps.setStepNumber).toHaveBeenCalledWith(1); + }); + }); + + it('disables `next` button on final step', () => { + defaultProps.stepNumber = 3; + + const { getByLabelText } = render(); + + // getByLabelText('Next step'); + expect(getByLabelText('Next step')).toHaveAttribute('disabled'); + expect(getByLabelText('Previous step')).not.toHaveAttribute('disabled'); + }); + + it('disables `prev` button on final step', () => { + defaultProps.stepNumber = 1; + + const { getByLabelText } = render(); + + expect(getByLabelText('Next step')).not.toHaveAttribute('disabled'); + expect(getByLabelText('Previous step')).toHaveAttribute('disabled'); + }); + + it('renders a timestamp', () => { + const { getByText } = render(); + + getByText('Nov 26, 2020 10:28:56 AM'); + }); + + it('renders caption content', () => { + const { getByText } = render(); + + getByText('test caption content'); + }); +}); diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/step_image_caption.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/step_image_caption.tsx new file mode 100644 index 0000000000000..c5da98bacc431 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/step_image_caption.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 { EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText } from '@elastic/eui'; +import React from 'react'; +import moment from 'moment'; +import { nextAriaLabel, prevAriaLabel } from './translations'; +import { getShortTimeStamp } from '../../../../overview/monitor_list/columns/monitor_status_column'; + +export interface StepImageCaptionProps { + captionContent: string; + imgSrc?: string; + maxSteps?: number; + setStepNumber: React.Dispatch>; + stepNumber: number; + timestamp: string; +} + +export const StepImageCaption: React.FC = ({ + captionContent, + imgSrc, + maxSteps, + setStepNumber, + stepNumber, + timestamp, +}) => { + return ( + <> +
+ {imgSrc && ( + + + { + setStepNumber(stepNumber - 1); + }} + iconType="arrowLeft" + aria-label={prevAriaLabel} + /> + + + {captionContent} + + + { + setStepNumber(stepNumber + 1); + }} + iconType="arrowRight" + aria-label={nextAriaLabel} + /> + + + )} +
+ {/* TODO: Add link to details page once it's available */} + {getShortTimeStamp(moment(timestamp))} + + + ); +}; diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/step_image_popover.test.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/step_image_popover.test.tsx new file mode 100644 index 0000000000000..184794c1465aa --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/step_image_popover.test.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 { fireEvent, waitFor } from '@testing-library/react'; +import React from 'react'; +import { StepImagePopover, StepImagePopoverProps } from './step_image_popover'; +import { render } from '../../../../../lib/helper/rtl_helpers'; + +describe('StepImagePopover', () => { + let defaultProps: StepImagePopoverProps; + + beforeEach(() => { + defaultProps = { + captionContent: 'test caption', + imageCaption:
test caption element
, + imgSrc: 'http://sample.com/sampleImageSrc.png', + isImagePopoverOpen: false, + }; + }); + + it('opens displays full-size image on click, hides after close is closed', async () => { + const { getByAltText, getByLabelText, queryByLabelText } = render( + + ); + + const closeFullScreenButton = 'Close full screen test caption image'; + + expect(queryByLabelText(closeFullScreenButton)).toBeNull(); + + const caption = getByAltText('test caption'); + fireEvent.click(caption); + + await waitFor(() => { + const closeButton = getByLabelText(closeFullScreenButton); + fireEvent.click(closeButton); + }); + + await waitFor(() => { + expect(queryByLabelText(closeFullScreenButton)).toBeNull(); + }); + }); + + it('shows the popover when `isOpen` is true', () => { + defaultProps.isImagePopoverOpen = true; + + const { getByAltText } = render(); + + expect(getByAltText(`A larger version of the screenshot for this journey step's thumbnail.`)); + }); + + it('renders caption content', () => { + const { getByRole } = render(); + const image = getByRole('img'); + expect(image).toHaveAttribute('alt', 'test caption'); + expect(image).toHaveAttribute('src', 'http://sample.com/sampleImageSrc.png'); + }); +}); diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/step_image_popover.tsx b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/step_image_popover.tsx new file mode 100644 index 0000000000000..fd7b7e6a886bb --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/step_image_popover.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 { EuiImage, EuiPopover } from '@elastic/eui'; +import React from 'react'; +import styled from 'styled-components'; +import { fullSizeImageAlt } from './translations'; + +const POPOVER_IMG_HEIGHT = 360; +const POPOVER_IMG_WIDTH = 640; + +const StepImage = styled(EuiImage)` + &&& { + display: flex; + figcaption { + white-space: nowrap; + align-self: center; + margin-left: 8px; + margin-top: 8px; + text-decoration: none !important; + } + } +`; +export interface StepImagePopoverProps { + captionContent: string; + imageCaption: JSX.Element; + imgSrc: string; + isImagePopoverOpen: boolean; +} + +export const StepImagePopover: React.FC = ({ + captionContent, + imageCaption, + imgSrc, + isImagePopoverOpen, +}) => ( + + } + isOpen={isImagePopoverOpen} + > + + +); diff --git a/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/translations.ts b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/translations.ts new file mode 100644 index 0000000000000..ad49143a68057 --- /dev/null +++ b/x-pack/plugins/uptime/public/components/monitor/ping_list/columns/ping_timestamp/translations.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'; + +export const prevAriaLabel = i18n.translate('xpack.uptime.synthetics.prevStepButton.airaLabel', { + defaultMessage: 'Previous step', +}); + +export const nextAriaLabel = i18n.translate('xpack.uptime.synthetics.nextStepButton.ariaLabel', { + defaultMessage: 'Next step', +}); + +export const imageLoadingSpinnerAriaLabel = i18n.translate( + 'xpack.uptime.synthetics.imageLoadingSpinner.ariaLabel', + { + defaultMessage: 'An animated spinner indicating the image is loading', + } +); + +export const fullSizeImageAlt = i18n.translate('xpack.uptime.synthetics.thumbnail.fullSize.alt', { + defaultMessage: `A larger version of the screenshot for this journey step's thumbnail.`, +}); + +export const formatCaptionContent = (stepNumber: number, stepName?: number) => + i18n.translate('xpack.uptime.synthetics.pingTimestamp.captionContent', { + defaultMessage: 'Step: {stepNumber} {stepName}', + values: { + stepNumber, + stepName, + }, + }); diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx index bdc6dbf3f6de2..3d9b646931e7d 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_detail/step_detail_container.tsx @@ -32,7 +32,7 @@ export const StepDetailContainer: React.FC = ({ checkGroup, stepIndex }) useEffect(() => { if (checkGroup) { - dispatch(getJourneySteps({ checkGroup })); + dispatch(getJourneySteps({ checkGroup, syntheticEventTypes: ['step/end'] })); } }, [dispatch, checkGroup]); diff --git a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_screenshot_display.tsx b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_screenshot_display.tsx index 3efcff196b55f..716e877c50943 100644 --- a/x-pack/plugins/uptime/public/components/monitor/synthetics/step_screenshot_display.tsx +++ b/x-pack/plugins/uptime/public/components/monitor/synthetics/step_screenshot_display.tsx @@ -98,7 +98,7 @@ export const StepScreenshotDisplay: FC = ({ closePopover={() => setIsImagePopoverOpen(false)} isOpen={isImagePopoverOpen} > - { = ({ } ) } - src={imgSrc} + url={imgSrc} style={{ width: POPOVER_IMG_WIDTH, height: POPOVER_IMG_HEIGHT, objectFit: 'contain' }} /> diff --git a/x-pack/plugins/uptime/public/components/settings/types.ts b/x-pack/plugins/uptime/public/components/settings/types.ts index faa1c7e72e47b..7a3af47524b20 100644 --- a/x-pack/plugins/uptime/public/components/settings/types.ts +++ b/x-pack/plugins/uptime/public/components/settings/types.ts @@ -9,7 +9,7 @@ import { JiraActionTypeId, PagerDutyActionTypeId, ServerLogActionTypeId, - ServiceNowActionTypeId, + ServiceNowITSMActionTypeId as ServiceNowActionTypeId, SlackActionTypeId, TeamsActionTypeId, WebhookActionTypeId, diff --git a/x-pack/plugins/uptime/public/state/actions/journey.ts b/x-pack/plugins/uptime/public/state/actions/journey.ts index 0d35559d97fc3..5931980c56947 100644 --- a/x-pack/plugins/uptime/public/state/actions/journey.ts +++ b/x-pack/plugins/uptime/public/state/actions/journey.ts @@ -9,6 +9,7 @@ import { SyntheticsJourneyApiResponse } from '../../../common/runtime_types'; export interface FetchJourneyStepsParams { checkGroup: string; + syntheticEventTypes?: string[]; } export interface GetJourneyFailPayload { diff --git a/x-pack/plugins/uptime/public/state/api/journey.ts b/x-pack/plugins/uptime/public/state/api/journey.ts index 1aeeb485e481f..684056b197f93 100644 --- a/x-pack/plugins/uptime/public/state/api/journey.ts +++ b/x-pack/plugins/uptime/public/state/api/journey.ts @@ -16,7 +16,7 @@ export async function fetchJourneySteps( ): Promise { return (await apiService.get( `/api/uptime/journey/${params.checkGroup}`, - undefined, + { syntheticEventTypes: params.syntheticEventTypes }, SyntheticsJourneyApiResponseType )) as SyntheticsJourneyApiResponse; } diff --git a/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.test.ts b/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.test.ts new file mode 100644 index 0000000000000..8c432ff6f1e0f --- /dev/null +++ b/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.test.ts @@ -0,0 +1,196 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getJourneySteps, formatSyntheticEvents } from './get_journey_steps'; +import { getUptimeESMockClient } from './helper'; + +describe('getJourneySteps request module', () => { + describe('formatStepTypes', () => { + it('returns default steps if none are provided', () => { + expect(formatSyntheticEvents()).toMatchInlineSnapshot(` + Array [ + "step/end", + "stderr", + "cmd/status", + "step/screenshot", + ] + `); + }); + + it('returns provided step array if isArray', () => { + expect(formatSyntheticEvents(['step/end', 'stderr'])).toMatchInlineSnapshot(` + Array [ + "step/end", + "stderr", + ] + `); + }); + + it('returns provided step string in an array', () => { + expect(formatSyntheticEvents('step/end')).toMatchInlineSnapshot(` + Array [ + "step/end", + ] + `); + }); + }); + + describe('getJourneySteps', () => { + let data: any; + beforeEach(() => { + data = { + body: { + hits: { + hits: [ + { + _id: 'o6myXncBFt2V8m6r6z-r', + _source: { + '@timestamp': '2021-02-01T17:45:19.001Z', + synthetics: { + package_version: '0.0.1-alpha.8', + journey: { + name: 'inline', + id: 'inline', + }, + step: { + name: 'load homepage', + index: 1, + }, + type: 'step/end', + }, + monitor: { + name: 'My Monitor', + id: 'my-monitor', + check_group: '2bf952dc-64b5-11eb-8b3b-42010a84000d', + type: 'browser', + }, + }, + }, + { + _id: 'IjqzXncBn2sjqrYxYoCG', + _source: { + '@timestamp': '2021-02-01T17:45:49.944Z', + synthetics: { + package_version: '0.0.1-alpha.8', + journey: { + name: 'inline', + id: 'inline', + }, + step: { + name: 'hover over products menu', + index: 2, + }, + type: 'step/end', + }, + monitor: { + name: 'My Monitor', + timespan: { + lt: '2021-02-01T17:46:49.945Z', + gte: '2021-02-01T17:45:49.945Z', + }, + id: 'my-monitor', + check_group: '2bf952dc-64b5-11eb-8b3b-42010a84000d', + type: 'browser', + }, + }, + }, + ], + }, + }, + }; + }); + + it('formats ES result', async () => { + const { esClient: mockEsClient, uptimeEsClient } = getUptimeESMockClient(); + + mockEsClient.search.mockResolvedValueOnce(data as any); + const result: any = await getJourneySteps({ + uptimeEsClient, + checkGroup: '2bf952dc-64b5-11eb-8b3b-42010a84000d', + }); + expect(mockEsClient.search).toHaveBeenCalledTimes(1); + const call: any = mockEsClient.search.mock.calls[0][0]; + + // check that default `synthetics.type` value is supplied, + expect(call.body.query.bool.filter[0]).toMatchInlineSnapshot(` + Object { + "terms": Object { + "synthetics.type": Array [ + "step/end", + "stderr", + "cmd/status", + "step/screenshot", + ], + }, + } + `); + + // given check group is used for the terms filter + expect(call.body.query.bool.filter[1]).toMatchInlineSnapshot(` + Object { + "term": Object { + "monitor.check_group": "2bf952dc-64b5-11eb-8b3b-42010a84000d", + }, + } + `); + + // should sort by step index, then timestamp + expect(call.body.sort).toMatchInlineSnapshot(` + Array [ + Object { + "synthetics.step.index": Object { + "order": "asc", + }, + }, + Object { + "@timestamp": Object { + "order": "asc", + }, + }, + ] + `); + + expect(result).toHaveLength(2); + // `getJourneySteps` is responsible for formatting these fields, so we need to check them + result.forEach((step: any) => { + expect(['2021-02-01T17:45:19.001Z', '2021-02-01T17:45:49.944Z']).toContain(step.timestamp); + expect(['o6myXncBFt2V8m6r6z-r', 'IjqzXncBn2sjqrYxYoCG']).toContain(step.docId); + expect(step.synthetics.screenshotExists).toBeDefined(); + }); + }); + + it('notes screenshot exists when a document of type step/screenshot is included', async () => { + const { esClient: mockEsClient, uptimeEsClient } = getUptimeESMockClient(); + + data.body.hits.hits[0]._source.synthetics.type = 'step/screenshot'; + data.body.hits.hits[0]._source.synthetics.step.index = 2; + mockEsClient.search.mockResolvedValueOnce(data as any); + + const result: any = await getJourneySteps({ + uptimeEsClient, + checkGroup: '2bf952dc-64b5-11eb-8b3b-42010a84000d', + syntheticEventTypes: ['stderr', 'step/end'], + }); + + const call: any = mockEsClient.search.mock.calls[0][0]; + + // assert that filters for only the provided step types are used + expect(call.body.query.bool.filter[0]).toMatchInlineSnapshot(` + Object { + "terms": Object { + "synthetics.type": Array [ + "stderr", + "step/end", + ], + }, + } + `); + + expect(result).toHaveLength(1); + expect(result[0].synthetics.screenshotExists).toBe(true); + }); + }); +}); diff --git a/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts b/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts index c330e1b66fe93..60d2a97c99f7d 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_journey_steps.ts @@ -9,11 +9,23 @@ import { Ping } from '../../../common/runtime_types'; interface GetJourneyStepsParams { checkGroup: string; + syntheticEventTypes?: string | string[]; } +const defaultEventTypes = ['step/end', 'stderr', 'cmd/status', 'step/screenshot']; + +export const formatSyntheticEvents = (eventTypes?: string | string[]) => { + if (!eventTypes) { + return defaultEventTypes; + } else { + return Array.isArray(eventTypes) ? eventTypes : [eventTypes]; + } +}; + export const getJourneySteps: UMElasticsearchQueryFn = async ({ uptimeEsClient, checkGroup, + syntheticEventTypes, }) => { const params = { query: { @@ -21,7 +33,7 @@ export const getJourneySteps: UMElasticsearchQueryFn checkGroup: schema.string(), _debug: schema.maybe(schema.boolean()), }), + query: schema.object({ + // provides a filter for the types of synthetic events to include + // when fetching a journey's data + syntheticEventTypes: schema.maybe( + schema.oneOf([schema.arrayOf(schema.string()), schema.string()]) + ), + }), }, handler: async ({ uptimeEsClient, request }): Promise => { const { checkGroup } = request.params; + const { syntheticEventTypes } = request.query; const result = await libs.requests.getJourneySteps({ uptimeEsClient, checkGroup, + syntheticEventTypes, }); const details = await libs.requests.getJourneyDetails({ diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/plugin.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/plugin.ts index 18f3c83b00141..dfdacb230763f 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/plugin.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/plugin.ts @@ -38,6 +38,7 @@ export function getAllExternalServiceSimulatorPaths(): string[] { getExternalServiceSimulatorPath(service) ); allPaths.push(`/api/_${NAME}/${ExternalServiceSimulator.SERVICENOW}/api/now/v2/table/incident`); + allPaths.push(`/api/_${NAME}/${ExternalServiceSimulator.SERVICENOW}/api/now/v2/table/sys_choice`); allPaths.push( `/api/_${NAME}/${ExternalServiceSimulator.SERVICENOW}/api/now/v2/table/sys_dictionary` ); diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/servicenow_simulation.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/servicenow_simulation.ts index 2c3138a36f071..b94bb89fc6f4a 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/servicenow_simulation.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/servicenow_simulation.ts @@ -127,6 +127,51 @@ export function initPlugin(router: IRouter, path: string) { }); } ); + + router.get( + { + path: `${path}/api/now/v2/table/sys_choice`, + options: { + authRequired: false, + }, + validate: {}, + }, + async function ( + context: RequestHandlerContext, + req: KibanaRequest, + res: KibanaResponseFactory + ): Promise> { + return jsonResponse(res, 200, { + result: [ + { + dependent_value: '', + label: '1 - Critical', + value: '1', + }, + { + dependent_value: '', + label: '2 - High', + value: '2', + }, + { + dependent_value: '', + label: '3 - Moderate', + value: '3', + }, + { + dependent_value: '', + label: '4 - Low', + value: '4', + }, + { + dependent_value: '', + label: '5 - Planning', + value: '5', + }, + ], + }); + } + ); } function jsonResponse(res: KibanaResponseFactory, code: number, object?: Record) { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index.ts index 61903c2902317..d1d71c40fe240 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index.ts @@ -14,9 +14,10 @@ const ES_TEST_INDEX_NAME = 'functional-test-actions-index'; export default function indexTest({ getService }: FtrProviderContext) { const es = getService('legacyEs'); const supertest = getService('supertest'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); describe('index action', () => { - beforeEach(() => clearTestIndex(es)); + beforeEach(() => esDeleteAllIndices(ES_TEST_INDEX_NAME)); let createdActionID: string; let createdActionIDWithIndex: string; @@ -262,13 +263,6 @@ export default function indexTest({ getService }: FtrProviderContext) { }); } -async function clearTestIndex(es: any) { - return await es.indices.delete({ - index: ES_TEST_INDEX_NAME, - ignoreUnavailable: true, - }); -} - async function getTestIndexItems(es: any) { const result = await es.search({ index: ES_TEST_INDEX_NAME, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index_preconfigured.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index_preconfigured.ts index 09b4b433d4847..081ad36d3ea32 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index_preconfigured.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/es_index_preconfigured.ts @@ -15,10 +15,11 @@ const ES_TEST_INDEX_NAME = 'functional-test-actions-index-preconfigured'; // eslint-disable-next-line import/no-default-export export default function indexTest({ getService }: FtrProviderContext) { const es = getService('legacyEs'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); const supertest = getService('supertest'); describe('preconfigured index action', () => { - beforeEach(() => clearTestIndex(es)); + beforeEach(() => esDeleteAllIndices(ES_TEST_INDEX_NAME)); it('should execute successfully when expected for a single body', async () => { const { body: result } = await supertest @@ -50,13 +51,6 @@ export default function indexTest({ getService }: FtrProviderContext) { }); } -async function clearTestIndex(es: any) { - return await es.indices.delete({ - index: ES_TEST_INDEX_NAME, - ignoreUnavailable: true, - }); -} - async function getTestIndexItems(es: any) { const result = await es.search({ index: ES_TEST_INDEX_NAME, diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow.ts index e448ad1f9c2ad..5f7146b43bfdb 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/actions/builtin_action_types/servicenow.ts @@ -216,7 +216,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { // Cannot destructure property 'value' of 'undefined' as it is undefined. // // The error seems to come from the exact same place in the code based on the - // exact same circomstances: + // exact same circumstances: // // https://github.com/elastic/kibana/blob/b0a223ebcbac7e404e8ae6da23b2cc6a4b509ff1/packages/kbn-config-schema/src/types/literal_type.ts#L28 // @@ -247,7 +247,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subAction]: expected value to equal [pushToService]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subAction]: expected value to equal [pushToService]\n- [4.subAction]: expected value to equal [getChoices]', }); }); }); @@ -265,7 +265,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.incident.short_description]: expected value of type [string] but got [undefined]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.incident.short_description]: expected value of type [string] but got [undefined]\n- [4.subAction]: expected value to equal [getChoices]', }); }); }); @@ -288,7 +288,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.incident.short_description]: expected value of type [string] but got [undefined]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.incident.short_description]: expected value of type [string] but got [undefined]\n- [4.subAction]: expected value to equal [getChoices]', }); }); }); @@ -315,7 +315,7 @@ export default function servicenowTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.comments]: types that failed validation:\n - [subActionParams.comments.0.0.commentId]: expected value of type [string] but got [undefined]\n - [subActionParams.comments.1]: expected value to equal [null]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.comments]: types that failed validation:\n - [subActionParams.comments.0.0.commentId]: expected value of type [string] but got [undefined]\n - [subActionParams.comments.1]: expected value to equal [null]\n- [4.subAction]: expected value to equal [getChoices]', }); }); }); @@ -342,10 +342,33 @@ export default function servicenowTest({ getService }: FtrProviderContext) { status: 'error', retry: false, message: - 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.comments]: types that failed validation:\n - [subActionParams.comments.0.0.comment]: expected value of type [string] but got [undefined]\n - [subActionParams.comments.1]: expected value to equal [null]', + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subActionParams.comments]: types that failed validation:\n - [subActionParams.comments.0.0.comment]: expected value of type [string] but got [undefined]\n - [subActionParams.comments.1]: expected value to equal [null]\n- [4.subAction]: expected value to equal [getChoices]', }); }); }); + + describe('getChoices', () => { + it('should fail when field is not provided', async () => { + await supertest + .post(`/api/actions/action/${simulatedActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + subAction: 'getChoices', + subActionParams: {}, + }, + }) + .then((resp: any) => { + expect(resp.body).to.eql({ + actionId: simulatedActionId, + status: 'error', + retry: false, + message: + 'error validating action params: types that failed validation:\n- [0.subAction]: expected value to equal [getFields]\n- [1.subAction]: expected value to equal [getIncident]\n- [2.subAction]: expected value to equal [handshake]\n- [3.subAction]: expected value to equal [pushToService]\n- [4.subActionParams.fields]: expected value of type [array] but got [undefined]', + }); + }); + }); + }); }); describe('Execution', () => { @@ -376,6 +399,54 @@ export default function servicenowTest({ getService }: FtrProviderContext) { }, }); }); + + describe('getChoices', () => { + it('should get choices', async () => { + const { body: result } = await supertest + .post(`/api/actions/action/${simulatedActionId}/_execute`) + .set('kbn-xsrf', 'foo') + .send({ + params: { + subAction: 'getChoices', + subActionParams: { fields: ['priority'] }, + }, + }) + .expect(200); + + expect(proxyHaveBeenCalled).to.equal(true); + expect(result).to.eql({ + status: 'ok', + actionId: simulatedActionId, + data: [ + { + dependent_value: '', + label: '1 - Critical', + value: '1', + }, + { + dependent_value: '', + label: '2 - High', + value: '2', + }, + { + dependent_value: '', + label: '3 - Moderate', + value: '3', + }, + { + dependent_value: '', + label: '4 - Low', + value: '4', + }, + { + dependent_value: '', + label: '5 - Planning', + value: '5', + }, + ], + }); + }); + }); }); after(() => { diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/index.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/index.ts index 2a256266697e6..e1502b496f77e 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/index.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/index.ts @@ -10,7 +10,12 @@ import { setupSpacesAndUsers, tearDown } from '..'; // eslint-disable-next-line import/no-default-export export default function alertingTests({ loadTestFile, getService }: FtrProviderContext) { describe('Alerts', () => { - describe('legacy alerts', () => { + // FLAKY: https://github.com/elastic/kibana/issues/86952 + describe.skip('legacy alerts', () => { + before(async () => { + await setupSpacesAndUsers(getService); + }); + after(async () => { await tearDown(getService); }); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/builtin_action_types/es_index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/builtin_action_types/es_index.ts index 0609e2f3f444f..e78b7aaa8c640 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/actions/builtin_action_types/es_index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/actions/builtin_action_types/es_index.ts @@ -14,9 +14,10 @@ const ES_TEST_INDEX_NAME = 'functional-test-actions-index'; export default function indexTest({ getService }: FtrProviderContext) { const es = getService('legacyEs'); const supertest = getService('supertest'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); describe('index action', () => { - beforeEach(() => clearTestIndex(es)); + beforeEach(() => esDeleteAllIndices(ES_TEST_INDEX_NAME)); let createdActionID: string; let createdActionIDWithIndex: string; @@ -138,13 +139,6 @@ export default function indexTest({ getService }: FtrProviderContext) { }); } -async function clearTestIndex(es: any) { - return await es.indices.delete({ - index: ES_TEST_INDEX_NAME, - ignoreUnavailable: true, - }); -} - async function getTestIndexItems(es: any) { const result = await es.search({ index: ES_TEST_INDEX_NAME, diff --git a/x-pack/test/api_integration/apis/management/cross_cluster_replication/follower_indices.js b/x-pack/test/api_integration/apis/management/cross_cluster_replication/follower_indices.js index e03eea2f3cf80..55032214ad53a 100644 --- a/x-pack/test/api_integration/apis/management/cross_cluster_replication/follower_indices.js +++ b/x-pack/test/api_integration/apis/management/cross_cluster_replication/follower_indices.js @@ -14,7 +14,6 @@ import { registerHelpers as registerFollowerIndicesnHelpers } from './follower_i export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('legacyEs'); const { addCluster, deleteAllClusters } = registerRemoteClustersHelpers(supertest); const { @@ -25,7 +24,7 @@ export default function ({ getService }) { unfollowAll, } = registerFollowerIndicesnHelpers(supertest); - const { createIndex, deleteAllIndices } = registerElasticSearchHelpers(es); + const { createIndex, deleteAllIndices } = registerElasticSearchHelpers(getService); describe('follower indices', function () { this.tags(['skipCloud']); diff --git a/x-pack/test/api_integration/apis/management/cross_cluster_replication/lib/elasticsearch.js b/x-pack/test/api_integration/apis/management/cross_cluster_replication/lib/elasticsearch.js index a9071fc55d444..aa595957310a7 100644 --- a/x-pack/test/api_integration/apis/management/cross_cluster_replication/lib/elasticsearch.js +++ b/x-pack/test/api_integration/apis/management/cross_cluster_replication/lib/elasticsearch.js @@ -10,7 +10,10 @@ import { getRandomString } from './random'; * during our tests. * @param {ElasticsearchClient} es The Elasticsearch client instance */ -export const registerHelpers = (es) => { +export const registerHelpers = (getService) => { + const es = getService('legacyEs'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); + let indicesCreated = []; const createIndex = (index = getRandomString()) => { @@ -18,17 +21,13 @@ export const registerHelpers = (es) => { return es.indices.create({ index }).then(() => index); }; - const deleteIndex = (index) => { - indicesCreated = indicesCreated.filter((i) => i !== index); - return es.indices.delete({ index }); + const deleteAllIndices = async () => { + await esDeleteAllIndices(indicesCreated); + indicesCreated = []; }; - const deleteAllIndices = () => - Promise.all(indicesCreated.map(deleteIndex)).then(() => (indicesCreated = [])); - return { createIndex, - deleteIndex, deleteAllIndices, }; }; diff --git a/x-pack/test/api_integration/apis/management/index_lifecycle_management/indices.js b/x-pack/test/api_integration/apis/management/index_lifecycle_management/indices.js index 87a67d0b6f6e6..f7ade849c1325 100644 --- a/x-pack/test/api_integration/apis/management/index_lifecycle_management/indices.js +++ b/x-pack/test/api_integration/apis/management/index_lifecycle_management/indices.js @@ -13,9 +13,10 @@ import { getPolicyPayload } from './fixtures'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); - const { getIndex, createIndex, cleanUp: cleanUpEsResources } = initElasticsearchHelpers(es); + const { getIndex, createIndex, cleanUp: cleanUpEsResources } = initElasticsearchHelpers( + getService + ); const { addPolicyToIndex, removePolicyFromIndex, retryPolicyOnIndex } = registerIndexHelpers({ supertest, diff --git a/x-pack/test/api_integration/apis/management/index_lifecycle_management/lib/elasticsearch.js b/x-pack/test/api_integration/apis/management/index_lifecycle_management/lib/elasticsearch.js index cf43ebf01b610..3d3526dfd1cd6 100644 --- a/x-pack/test/api_integration/apis/management/index_lifecycle_management/lib/elasticsearch.js +++ b/x-pack/test/api_integration/apis/management/index_lifecycle_management/lib/elasticsearch.js @@ -10,7 +10,10 @@ import { getRandomString } from './random'; * during our tests. * @param {ElasticsearchClient} es The Elasticsearch client instance */ -export const initElasticsearchHelpers = (es) => { +export const initElasticsearchHelpers = (getService) => { + const es = getService('es'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); + let indicesCreated = []; let templatesCreated = []; let composableTemplatesCreated = []; @@ -24,14 +27,11 @@ export const initElasticsearchHelpers = (es) => { return es.indices.create({ index }).then(() => index); }; - const deleteIndex = (index) => { - indicesCreated = indicesCreated.filter((i) => i !== index); - return es.indices.delete({ index }); + const deleteAllIndices = async () => { + await esDeleteAllIndices(indicesCreated); + indicesCreated = []; }; - const deleteAllIndices = () => - Promise.all(indicesCreated.map(deleteIndex)).then(() => (indicesCreated = [])); - // Data streams const createDataStream = (dataStream = getRandomString(), document) => { dataStreamsCreated.push(dataStream); @@ -102,7 +102,6 @@ export const initElasticsearchHelpers = (es) => { getIndex, createIndex, createDataStream, - deleteIndex, deleteAllIndices, deleteAllTemplates, getIndexTemplates, diff --git a/x-pack/test/api_integration/apis/management/index_lifecycle_management/nodes.js b/x-pack/test/api_integration/apis/management/index_lifecycle_management/nodes.js index 3de3a3279f77c..a54aeade3ecf0 100644 --- a/x-pack/test/api_integration/apis/management/index_lifecycle_management/nodes.js +++ b/x-pack/test/api_integration/apis/management/index_lifecycle_management/nodes.js @@ -13,9 +13,7 @@ import { initElasticsearchHelpers } from './lib'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); - - const { getNodesStats } = initElasticsearchHelpers(es); + const { getNodesStats } = initElasticsearchHelpers(getService); const { loadNodes, getNodeDetails } = registerHelpers({ supertest }); describe('nodes', function () { diff --git a/x-pack/test/api_integration/apis/management/index_lifecycle_management/policies.js b/x-pack/test/api_integration/apis/management/index_lifecycle_management/policies.js index 52979ed208bd6..39d89eaf538d0 100644 --- a/x-pack/test/api_integration/apis/management/index_lifecycle_management/policies.js +++ b/x-pack/test/api_integration/apis/management/index_lifecycle_management/policies.js @@ -15,14 +15,12 @@ import { DEFAULT_POLICY_NAME } from './constants'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); - const { createIndex, createComposableIndexTemplate, createDataStream, cleanUp: cleanUpEsResources, - } = initElasticsearchHelpers(es); + } = initElasticsearchHelpers(getService); const { loadPolicies, diff --git a/x-pack/test/api_integration/apis/management/index_lifecycle_management/templates.js b/x-pack/test/api_integration/apis/management/index_lifecycle_management/templates.js index 9e0d32b96af98..da3d1e20e221e 100644 --- a/x-pack/test/api_integration/apis/management/index_lifecycle_management/templates.js +++ b/x-pack/test/api_integration/apis/management/index_lifecycle_management/templates.js @@ -12,9 +12,8 @@ import { registerHelpers as registerPoliciesHelpers } from './policies.helpers'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('es'); - const { createIndexTemplate, cleanUp: cleanUpEsResources } = initElasticsearchHelpers(es); + const { createIndexTemplate, cleanUp: cleanUpEsResources } = initElasticsearchHelpers(getService); const { loadTemplates, addPolicyToTemplate } = registerTemplatesHelpers({ supertest, diff --git a/x-pack/test/api_integration/apis/management/index_management/component_templates.ts b/x-pack/test/api_integration/apis/management/index_management/component_templates.ts index 30ec95f208c80..06680fcf14555 100644 --- a/x-pack/test/api_integration/apis/management/index_management/component_templates.ts +++ b/x-pack/test/api_integration/apis/management/index_management/component_templates.ts @@ -14,13 +14,12 @@ import { API_BASE_PATH } from './constants'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const es = getService('legacyEs'); const { createComponentTemplate, cleanUpComponentTemplates, deleteComponentTemplate, - } = initElasticsearchHelpers(es); + } = initElasticsearchHelpers(getService); describe('Component templates', function () { after(async () => { diff --git a/x-pack/test/api_integration/apis/management/index_management/indices.js b/x-pack/test/api_integration/apis/management/index_management/indices.js index 68139e1cb6492..520af121aeead 100644 --- a/x-pack/test/api_integration/apis/management/index_management/indices.js +++ b/x-pack/test/api_integration/apis/management/index_management/indices.js @@ -11,14 +11,13 @@ import { registerHelpers } from './indices.helpers'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('legacyEs'); const { createIndex, catIndex, indexStats, cleanUp: cleanUpEsResources, - } = initElasticsearchHelpers(es); + } = initElasticsearchHelpers(getService); const { closeIndex, diff --git a/x-pack/test/api_integration/apis/management/index_management/lib/elasticsearch.js b/x-pack/test/api_integration/apis/management/index_management/lib/elasticsearch.js index 6dcfbecb6bd0d..11112f2c5d8ac 100644 --- a/x-pack/test/api_integration/apis/management/index_management/lib/elasticsearch.js +++ b/x-pack/test/api_integration/apis/management/index_management/lib/elasticsearch.js @@ -11,7 +11,10 @@ import { getRandomString } from './random'; * during our tests. * @param {ElasticsearchClient} es The Elasticsearch client instance */ -export const initElasticsearchHelpers = (es) => { +export const initElasticsearchHelpers = (getService) => { + const es = getService('legacyEs'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); + let indicesCreated = []; let componentTemplatesCreated = []; @@ -20,14 +23,11 @@ export const initElasticsearchHelpers = (es) => { return es.indices.create({ index, body }).then(() => index); }; - const deleteIndex = (index) => { - indicesCreated = indicesCreated.filter((i) => i !== index); - return es.indices.delete({ index, ignoreUnavailable: true }); + const deleteAllIndices = async () => { + await esDeleteAllIndices(indicesCreated); + indicesCreated = []; }; - const deleteAllIndices = () => - Promise.all(indicesCreated.map(deleteIndex)).then(() => (indicesCreated = [])); - const catIndex = (index, h) => es.cat.indices({ index, format: 'json', h }); const indexStats = (index, metric) => es.indices.stats({ index, metric }); @@ -60,7 +60,6 @@ export const initElasticsearchHelpers = (es) => { return { createIndex, - deleteIndex, deleteAllIndices, catIndex, indexStats, diff --git a/x-pack/test/api_integration/apis/management/index_management/mapping.js b/x-pack/test/api_integration/apis/management/index_management/mapping.js index 76a9b4707dd0c..d6c12e42e1409 100644 --- a/x-pack/test/api_integration/apis/management/index_management/mapping.js +++ b/x-pack/test/api_integration/apis/management/index_management/mapping.js @@ -11,9 +11,8 @@ import { registerHelpers } from './mapping.helpers'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('legacyEs'); - const { createIndex, cleanUp: cleanUpEsResources } = initElasticsearchHelpers(es); + const { createIndex, cleanUp: cleanUpEsResources } = initElasticsearchHelpers(getService); const { getIndexMapping } = registerHelpers({ supertest }); diff --git a/x-pack/test/api_integration/apis/management/index_management/settings.js b/x-pack/test/api_integration/apis/management/index_management/settings.js index 4ddde6f02fcf6..cdfcf33c47506 100644 --- a/x-pack/test/api_integration/apis/management/index_management/settings.js +++ b/x-pack/test/api_integration/apis/management/index_management/settings.js @@ -11,9 +11,8 @@ import { registerHelpers } from './settings.helpers'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('legacyEs'); - const { createIndex, cleanUp: cleanUpEsResources } = initElasticsearchHelpers(es); + const { createIndex, cleanUp: cleanUpEsResources } = initElasticsearchHelpers(getService); const { getIndexSettings, updateIndexSettings } = registerHelpers({ supertest }); diff --git a/x-pack/test/api_integration/apis/management/index_management/stats.js b/x-pack/test/api_integration/apis/management/index_management/stats.js index 97ad578472bfe..3a0f593356119 100644 --- a/x-pack/test/api_integration/apis/management/index_management/stats.js +++ b/x-pack/test/api_integration/apis/management/index_management/stats.js @@ -11,9 +11,8 @@ import { registerHelpers } from './stats.helpers'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('legacyEs'); - const { createIndex, cleanUp: cleanUpEsResources } = initElasticsearchHelpers(es); + const { createIndex, cleanUp: cleanUpEsResources } = initElasticsearchHelpers(getService); const { getIndexStats } = registerHelpers({ supertest }); diff --git a/x-pack/test/api_integration/apis/management/index_management/templates.js b/x-pack/test/api_integration/apis/management/index_management/templates.js index dd5dac5626041..bfff4df8e5f8e 100644 --- a/x-pack/test/api_integration/apis/management/index_management/templates.js +++ b/x-pack/test/api_integration/apis/management/index_management/templates.js @@ -11,9 +11,8 @@ import { registerHelpers } from './templates.helpers'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('legacyEs'); - const { cleanUp: cleanUpEsResources, catTemplate } = initElasticsearchHelpers(es); + const { cleanUp: cleanUpEsResources, catTemplate } = initElasticsearchHelpers(getService); const { getAllTemplates, diff --git a/x-pack/test/api_integration/apis/management/rollup/index_patterns_extensions.js b/x-pack/test/api_integration/apis/management/rollup/index_patterns_extensions.js index 0a93e8b8bd1e3..0a6b97f7790c5 100644 --- a/x-pack/test/api_integration/apis/management/rollup/index_patterns_extensions.js +++ b/x-pack/test/api_integration/apis/management/rollup/index_patterns_extensions.js @@ -12,12 +12,10 @@ import { getRandomString } from './lib'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('legacyEs'); - const { createIndexWithMappings, getJobPayload, createJob, cleanUp } = registerHelpers({ - supertest, - es, - }); + const { createIndexWithMappings, getJobPayload, createJob, cleanUp } = registerHelpers( + getService + ); describe('index patterns extension', () => { describe('Fields for wildcards', () => { diff --git a/x-pack/test/api_integration/apis/management/rollup/lib/es_index.js b/x-pack/test/api_integration/apis/management/rollup/lib/es_index.js index 0e09269c955fa..a1eadfd773abf 100644 --- a/x-pack/test/api_integration/apis/management/rollup/lib/es_index.js +++ b/x-pack/test/api_integration/apis/management/rollup/lib/es_index.js @@ -10,7 +10,10 @@ import { getRandomString } from './random'; * during our tests. * @param {ElasticsearchClient} es The Elasticsearch client instance */ -export const initElasticsearchIndicesHelpers = (es) => { +export const initElasticsearchIndicesHelpers = (getService) => { + const es = getService('legacyEs'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); + let indicesCreated = []; const createIndex = (index = getRandomString(), body = {}) => { @@ -25,10 +28,8 @@ export const initElasticsearchIndicesHelpers = (es) => { const deleteIndex = (index) => { const indices = Array.isArray(index) ? index : [index]; - indices.forEach((_index) => { - indicesCreated = indicesCreated.filter((i) => i !== _index); - }); - return es.indices.delete({ index: indices }, { ignoreUnavailable: true }); + indicesCreated = indicesCreated.filter((i) => !indices.includes(i)); + return esDeleteAllIndices(indices); }; const deleteAllIndicesCreated = () => diff --git a/x-pack/test/api_integration/apis/management/rollup/rollup.js b/x-pack/test/api_integration/apis/management/rollup/rollup.js index d7d380253e837..21c50928c0108 100644 --- a/x-pack/test/api_integration/apis/management/rollup/rollup.js +++ b/x-pack/test/api_integration/apis/management/rollup/rollup.js @@ -11,7 +11,6 @@ import { registerHelpers } from './rollup.test_helpers'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('legacyEs'); const { createIndexWithMappings, @@ -22,7 +21,7 @@ export default function ({ getService }) { startJob, stopJob, cleanUp, - } = registerHelpers({ supertest, es }); + } = registerHelpers(getService); describe('jobs', () => { after(() => cleanUp()); diff --git a/x-pack/test/api_integration/apis/management/rollup/rollup.test_helpers.js b/x-pack/test/api_integration/apis/management/rollup/rollup.test_helpers.js index af51852cd04f2..f559a5c5676c3 100644 --- a/x-pack/test/api_integration/apis/management/rollup/rollup.test_helpers.js +++ b/x-pack/test/api_integration/apis/management/rollup/rollup.test_helpers.js @@ -10,8 +10,12 @@ import { API_BASE_PATH, ROLLUP_INDEX_NAME, INDEX_TO_ROLLUP_MAPPINGS } from './co const jobsCreated = []; const jobsStarted = []; -export const registerHelpers = ({ supertest, es }) => { - const { createIndex, deleteIndex, deleteAllIndicesCreated } = initElasticsearchIndicesHelpers(es); +export const registerHelpers = (getService) => { + const supertest = getService('supertest'); + + const { createIndex, deleteIndex, deleteAllIndicesCreated } = initElasticsearchIndicesHelpers( + getService + ); const createIndexWithMappings = (indexName = undefined, mappings = INDEX_TO_ROLLUP_MAPPINGS) => { return createIndex(indexName, { mappings }); diff --git a/x-pack/test/api_integration/apis/management/rollup/rollup_search.js b/x-pack/test/api_integration/apis/management/rollup/rollup_search.js index b2941b966cc9a..3d1f6c9f1c5e8 100644 --- a/x-pack/test/api_integration/apis/management/rollup/rollup_search.js +++ b/x-pack/test/api_integration/apis/management/rollup/rollup_search.js @@ -12,12 +12,10 @@ import { getRandomString } from './lib'; export default function ({ getService }) { const supertest = getService('supertest'); - const es = getService('legacyEs'); - const { createIndexWithMappings, getJobPayload, createJob, cleanUp } = registerHelpers({ - supertest, - es, - }); + const { createIndexWithMappings, getJobPayload, createJob, cleanUp } = registerHelpers( + getService + ); describe('search', () => { const URI = `${API_BASE_PATH}/search`; diff --git a/x-pack/test/api_integration/apis/maps/get_grid_tile.js b/x-pack/test/api_integration/apis/maps/get_grid_tile.js index 3eee56c962a27..0d499e186c70c 100644 --- a/x-pack/test/api_integration/apis/maps/get_grid_tile.js +++ b/x-pack/test/api_integration/apis/maps/get_grid_tile.js @@ -4,26 +4,92 @@ * you may not use this file except in compliance with the Elastic License. */ +import { VectorTile } from '@mapbox/vector-tile'; +import Protobuf from 'pbf'; +import expect from '@kbn/expect'; +import { + KBN_IS_CENTROID_FEATURE, + MVT_SOURCE_LAYER_NAME, +} from '../../../../plugins/maps/common/constants'; + export default function ({ getService }) { const supertest = getService('supertest'); describe('getGridTile', () => { - it('should validate params', async () => { - await supertest + it('should return vector tile containing cluster features', async () => { + const resp = await supertest .get( - `/api/maps/mvt/getGridTile?x=0&y=0&z=0&geometryFieldName=coordinates&index=logstash*&requestBody=(_source:(excludes:!()),aggs:(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:coordinates))),geotile_grid:(bounds:!n,field:coordinates,precision:!n,shard_size:65535,size:65535))),docvalue_fields:!((field:%27@timestamp%27,format:date_time),(field:timestamp,format:date_time),(field:utc_time,format:date_time)),query:(bool:(filter:!((match_all:()),(range:(timestamp:(format:strict_date_optional_time,gte:%272020-09-16T13:57:36.734Z%27,lte:%272020-09-23T13:57:36.734Z%27)))),must:!(),must_not:!(),should:!())),script_fields:(hour_of_day:(script:(lang:painless,source:%27doc[!%27timestamp!%27].value.getHour()%27))),size:0,stored_fields:!(%27*%27))&requestType=point&geoFieldType=geo_point` + `/api/maps/mvt/getGridTile\ +?x=2\ +&y=3\ +&z=3\ +&geometryFieldName=geo.coordinates\ +&index=logstash-*\ +&requestBody=(_source:(excludes:!()),aggs:(gridSplit:(aggs:(avg_of_bytes:(avg:(field:bytes)),gridCentroid:(geo_centroid:(field:geo.coordinates))),geotile_grid:(bounds:!n,field:geo.coordinates,precision:!n,shard_size:65535,size:65535))),fields:!((field:%27@timestamp%27,format:date_time),(field:%27relatedContent.article:modified_time%27,format:date_time),(field:%27relatedContent.article:published_time%27,format:date_time),(field:utc_time,format:date_time)),query:(bool:(filter:!((match_all:()),(range:(%27@timestamp%27:(format:strict_date_optional_time,gte:%272015-09-20T00:00:00.000Z%27,lte:%272015-09-20T01:00:00.000Z%27)))),must:!(),must_not:!(),should:!())),runtime_mappings:(),script_fields:(hour_of_day:(script:(lang:painless,source:%27doc[!%27@timestamp!%27].value.getHour()%27))),size:0,stored_fields:!(%27*%27))\ +&requestType=point\ +&geoFieldType=geo_point` ) .set('kbn-xsrf', 'kibana') + .responseType('blob') .expect(200); + + const jsonTile = new VectorTile(new Protobuf(resp.body)); + const layer = jsonTile.layers[MVT_SOURCE_LAYER_NAME]; + expect(layer.length).to.be(1); + const clusterFeature = layer.feature(0); + expect(clusterFeature.type).to.be(1); + expect(clusterFeature.extent).to.be(4096); + expect(clusterFeature.id).to.be(undefined); + expect(clusterFeature.properties).to.eql({ doc_count: 1, avg_of_bytes: 9252 }); + expect(clusterFeature.loadGeometry()).to.eql([[{ x: 87, y: 667 }]]); }); - it('should not validate when required params are missing', async () => { - await supertest + it('should return vector tile containing grid features', async () => { + const resp = await supertest .get( - `/api/maps/mvt/getGridTile?x=0&y=0&z=0&geometryFieldName=coordinates&index=logstash*&requestBody=(_source:(excludes:!()),aggs:(gridSplit:(aggs:(gridCentroid:(geo_centroid:(field:coordinates))),geotile_grid:(bounds:!n,field:coordinates,precision:!n,shard_size:65535,size:65535))),docvalue_fields:!((field:%27@timestamp%27,format:date_time),(field:timestamp,format:date_time),(field:utc_time,format:date_time)),query:(bool:(filter:!((match_all:()),(range:(timestamp:(format:strict_date_optional_time,gte:%272020-09-16T13:57:36.734Z%27,lte:%272020-09-23T13:57:36.734Z%27)))),must:!(),must_not:!(),should:!())),script_fields:(hour_of_day:(script:(lang:painless,source:%27doc[!%27timestamp!%27].value.getHour()%27))),size:0,stored_fields:!(%27*%27))&requestType=point` + `/api/maps/mvt/getGridTile\ +?x=2\ +&y=3\ +&z=3\ +&geometryFieldName=geo.coordinates\ +&index=logstash-*\ +&requestBody=(_source:(excludes:!()),aggs:(gridSplit:(aggs:(avg_of_bytes:(avg:(field:bytes)),gridCentroid:(geo_centroid:(field:geo.coordinates))),geotile_grid:(bounds:!n,field:geo.coordinates,precision:!n,shard_size:65535,size:65535))),fields:!((field:%27@timestamp%27,format:date_time),(field:%27relatedContent.article:modified_time%27,format:date_time),(field:%27relatedContent.article:published_time%27,format:date_time),(field:utc_time,format:date_time)),query:(bool:(filter:!((match_all:()),(range:(%27@timestamp%27:(format:strict_date_optional_time,gte:%272015-09-20T00:00:00.000Z%27,lte:%272015-09-20T01:00:00.000Z%27)))),must:!(),must_not:!(),should:!())),runtime_mappings:(),script_fields:(hour_of_day:(script:(lang:painless,source:%27doc[!%27@timestamp!%27].value.getHour()%27))),size:0,stored_fields:!(%27*%27))\ +&requestType=grid\ +&geoFieldType=geo_point` ) .set('kbn-xsrf', 'kibana') - .expect(400); + .responseType('blob') + .expect(200); + + const jsonTile = new VectorTile(new Protobuf(resp.body)); + const layer = jsonTile.layers[MVT_SOURCE_LAYER_NAME]; + expect(layer.length).to.be(2); + + const gridFeature = layer.feature(0); + expect(gridFeature.type).to.be(3); + expect(gridFeature.extent).to.be(4096); + expect(gridFeature.id).to.be(undefined); + expect(gridFeature.properties).to.eql({ doc_count: 1, avg_of_bytes: 9252 }); + expect(gridFeature.loadGeometry()).to.eql([ + [ + { x: 96, y: 640 }, + { x: 96, y: 672 }, + { x: 64, y: 672 }, + { x: 64, y: 640 }, + { x: 96, y: 640 }, + ], + ]); + + const clusterFeature = layer.feature(1); + expect(clusterFeature.type).to.be(1); + expect(clusterFeature.extent).to.be(4096); + expect(clusterFeature.id).to.be(undefined); + expect(clusterFeature.properties).to.eql({ + doc_count: 1, + avg_of_bytes: 9252, + [KBN_IS_CENTROID_FEATURE]: true, + }); + expect(clusterFeature.loadGeometry()).to.eql([[{ x: 80, y: 656 }]]); }); }); } diff --git a/x-pack/test/api_integration/apis/maps/get_tile.js b/x-pack/test/api_integration/apis/maps/get_tile.js index 9dd0698b6c6ed..e39f8a72ae6ac 100644 --- a/x-pack/test/api_integration/apis/maps/get_tile.js +++ b/x-pack/test/api_integration/apis/maps/get_tile.js @@ -4,26 +4,82 @@ * you may not use this file except in compliance with the Elastic License. */ +import { VectorTile } from '@mapbox/vector-tile'; +import Protobuf from 'pbf'; +import expect from '@kbn/expect'; +import { MVT_SOURCE_LAYER_NAME } from '../../../../plugins/maps/common/constants'; + export default function ({ getService }) { const supertest = getService('supertest'); describe('getTile', () => { - it('should validate params', async () => { - await supertest + it('should return vector tile containing document', async () => { + const resp = await supertest .get( - `/api/maps/mvt/getTile?x=15&y=11&z=5&geometryFieldName=coordinates&index=logstash*&requestBody=(_source:(includes:!(coordinates)),docvalue_fields:!(),query:(bool:(filter:!((match_all:())),must:!(),must_not:!(),should:!())),script_fields:(),size:10000,stored_fields:!(coordinates))&geoFieldType=geo_point` + `/api/maps/mvt/getTile\ +?x=1\ +&y=1\ +&z=2\ +&geometryFieldName=geo.coordinates\ +&index=logstash-*\ +&requestBody=(_source:!f,docvalue_fields:!(bytes,geo.coordinates,machine.os.raw),query:(bool:(filter:!((match_all:()),(range:(%27@timestamp%27:(format:strict_date_optional_time,gte:%272015-09-20T00:00:00.000Z%27,lte:%272015-09-20T01:00:00.000Z%27)))),must:!(),must_not:!(),should:!())),runtime_mappings:(),script_fields:(),size:10000,stored_fields:!(bytes,geo.coordinates,machine.os.raw))\ +&geoFieldType=geo_point` ) .set('kbn-xsrf', 'kibana') + .responseType('blob') .expect(200); + + const jsonTile = new VectorTile(new Protobuf(resp.body)); + const layer = jsonTile.layers[MVT_SOURCE_LAYER_NAME]; + expect(layer.length).to.be(2); + const feature = layer.feature(0); + expect(feature.type).to.be(1); + expect(feature.extent).to.be(4096); + expect(feature.id).to.be(undefined); + expect(feature.properties).to.eql({ + __kbn__feature_id__: 'logstash-2015.09.20:AU_x3_BsGFA8no6Qjjug:0', + _id: 'AU_x3_BsGFA8no6Qjjug', + _index: 'logstash-2015.09.20', + bytes: 9252, + ['machine.os.raw']: 'ios', + }); + expect(feature.loadGeometry()).to.eql([[{ x: 44, y: 2382 }]]); }); - it('should not validate when required params are missing', async () => { - await supertest + it('should return vector tile containing bounds when count exceeds size', async () => { + const resp = await supertest + // requestBody sets size=1 to force count exceeded .get( - `/api/maps/mvt/getTile?&index=logstash*&requestBody=(_source:(includes:!(coordinates)),docvalue_fields:!(),query:(bool:(filter:!((match_all:())),must:!(),must_not:!(),should:!())),script_fields:(),size:10000,stored_fields:!(coordinates))` + `/api/maps/mvt/getTile\ +?x=1\ +&y=1\ +&z=2\ +&geometryFieldName=geo.coordinates\ +&index=logstash-*\ +&requestBody=(_source:!f,docvalue_fields:!(bytes,geo.coordinates,machine.os.raw),query:(bool:(filter:!((match_all:()),(range:(%27@timestamp%27:(format:strict_date_optional_time,gte:%272015-09-20T00:00:00.000Z%27,lte:%272015-09-20T01:00:00.000Z%27)))),must:!(),must_not:!(),should:!())),runtime_mappings:(),script_fields:(),size:1,stored_fields:!(bytes,geo.coordinates,machine.os.raw))\ +&geoFieldType=geo_point` ) .set('kbn-xsrf', 'kibana') - .expect(400); + .responseType('blob') + .expect(200); + + const jsonTile = new VectorTile(new Protobuf(resp.body)); + const layer = jsonTile.layers[MVT_SOURCE_LAYER_NAME]; + expect(layer.length).to.be(1); + const feature = layer.feature(0); + expect(feature.type).to.be(3); + expect(feature.extent).to.be(4096); + expect(feature.id).to.be(undefined); + expect(feature.properties).to.eql({ __kbn_too_many_features__: true }); + expect(feature.loadGeometry()).to.eql([ + [ + { x: 44, y: 2382 }, + { x: 44, y: 1913 }, + { x: 550, y: 1913 }, + { x: 550, y: 2382 }, + { x: 44, y: 2382 }, + ], + ]); }); }); } diff --git a/x-pack/test/api_integration/apis/metrics_ui/feature_controls.ts b/x-pack/test/api_integration/apis/metrics_ui/feature_controls.ts deleted file mode 100644 index d2b155984378e..0000000000000 --- a/x-pack/test/api_integration/apis/metrics_ui/feature_controls.ts +++ /dev/null @@ -1,252 +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 gql from 'graphql-tag'; -import { FtrProviderContext } from '../../ftr_provider_context'; - -const introspectionQuery = gql` - query Schema { - __schema { - queryType { - name - } - } - } -`; - -export default function ({ getService }: FtrProviderContext) { - const security = getService('security'); - const spaces = getService('spaces'); - const clientFactory = getService('infraOpsGraphQLClientFactory'); - - const expectGraphQL403 = (result: any) => { - expect(result.response).to.be(undefined); - expect(result.error).not.to.be(undefined); - expect(result.error).to.have.property('networkError'); - expect(result.error.networkError).to.have.property('statusCode', 403); - }; - - const expectGraphQLResponse = (result: any) => { - expect(result.error).to.be(undefined); - expect(result.response).to.have.property('data'); - expect(result.response.data).to.be.an('object'); - }; - - const executeGraphQLQuery = async (username: string, password: string, spaceId?: string) => { - const queryOptions = { - query: introspectionQuery, - }; - - const basePath = spaceId ? `/s/${spaceId}` : ''; - - const client = clientFactory({ username, password, basePath }); - let error; - let response; - try { - response = await client.query(queryOptions); - } catch (err) { - error = err; - } - return { - error, - response, - }; - }; - - describe('feature controls', () => { - it(`APIs can't be accessed by user with logstash-* "read" privileges`, async () => { - const username = 'logstash_read'; - const roleName = 'logstash_read'; - const password = `${username}-password`; - try { - await security.role.create(roleName, { - elasticsearch: { - indices: [ - { - names: ['logstash-*'], - privileges: ['read', 'view_index_metadata'], - }, - ], - }, - }); - - await security.user.create(username, { - password, - roles: [roleName], - full_name: 'a kibana user', - }); - - const graphQLResult = await executeGraphQLQuery(username, password); - expectGraphQL403(graphQLResult); - } finally { - await security.role.delete(roleName); - await security.user.delete(username); - } - }); - - it('APIs can be accessed user with global "all" and logstash-* "read" privileges', async () => { - const username = 'global_all'; - const roleName = 'global_all'; - const password = `${username}-password`; - try { - await security.role.create(roleName, { - elasticsearch: { - indices: [ - { - names: ['logstash-*'], - privileges: ['read', 'view_index_metadata'], - }, - ], - }, - kibana: [ - { - base: ['all'], - spaces: ['*'], - }, - ], - }); - - await security.user.create(username, { - password, - roles: [roleName], - full_name: 'a kibana user', - }); - - const graphQLResult = await executeGraphQLQuery(username, password); - expectGraphQLResponse(graphQLResult); - } finally { - await security.role.delete(roleName); - await security.user.delete(username); - } - }); - - // this could be any role which doesn't have access to the infra feature - it(`APIs can't be accessed by user with dashboard "all" and logstash-* "read" privileges`, async () => { - const username = 'dashboard_all'; - const roleName = 'dashboard_all'; - const password = `${username}-password`; - try { - await security.role.create(roleName, { - elasticsearch: { - indices: [ - { - names: ['logstash-*'], - privileges: ['read', 'view_index_metadata'], - }, - ], - }, - kibana: [ - { - feature: { - dashboard: ['all'], - }, - spaces: ['*'], - }, - ], - }); - - await security.user.create(username, { - password, - roles: [roleName], - full_name: 'a kibana user', - }); - - const graphQLResult = await executeGraphQLQuery(username, password); - expectGraphQL403(graphQLResult); - } finally { - await security.role.delete(roleName); - await security.user.delete(username); - } - }); - - describe('spaces', () => { - // the following tests create a user_1 which has infrastructure read access to space_1, logs read access to space_2 and dashboard all access to space_3 - const space1Id = 'space_1'; - const space2Id = 'space_2'; - const space3Id = 'space_3'; - - const roleName = 'user_1'; - const username = 'user_1'; - const password = 'user_1-password'; - - before(async () => { - await spaces.create({ - id: space1Id, - name: space1Id, - disabledFeatures: [], - }); - await spaces.create({ - id: space2Id, - name: space2Id, - disabledFeatures: [], - }); - await spaces.create({ - id: space3Id, - name: space3Id, - disabledFeatures: [], - }); - await security.role.create(roleName, { - elasticsearch: { - indices: [ - { - names: ['logstash-*'], - privileges: ['read', 'view_index_metadata'], - }, - ], - }, - kibana: [ - { - feature: { - infrastructure: ['read'], - }, - spaces: [space1Id], - }, - { - feature: { - logs: ['read'], - }, - spaces: [space2Id], - }, - { - feature: { - dashboard: ['all'], - }, - spaces: [space3Id], - }, - ], - }); - await security.user.create(username, { - password, - roles: [roleName], - }); - }); - - after(async () => { - await spaces.delete(space1Id); - await spaces.delete(space2Id); - await spaces.delete(space3Id); - await security.role.delete(roleName); - await security.user.delete(username); - }); - - it('user_1 can access APIs in space_1', async () => { - const graphQLResult = await executeGraphQLQuery(username, password, space1Id); - expectGraphQLResponse(graphQLResult); - }); - - it(`user_1 can access APIs in space_2`, async () => { - const graphQLResult = await executeGraphQLQuery(username, password, space2Id); - expectGraphQLResponse(graphQLResult); - }); - - it(`user_1 can't access APIs in space_3`, async () => { - const graphQLResult = await executeGraphQLQuery(username, password, space3Id); - expectGraphQL403(graphQLResult); - }); - }); - }); -} diff --git a/x-pack/test/api_integration/apis/metrics_ui/http_source.ts b/x-pack/test/api_integration/apis/metrics_ui/http_source.ts index 7e92caf0e37d7..16816f2ce90a5 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/http_source.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/http_source.ts @@ -34,7 +34,7 @@ export default function ({ getService }: FtrProviderContext) { before(() => esArchiver.load('infra/8.0.0/logs_and_metrics')); after(() => esArchiver.unload('infra/8.0.0/logs_and_metrics')); describe('/api/metrics/source/default/metrics', () => { - it('should just work', () => { + it('should just work', async () => { const resp = fetchSource(); return resp.then((data) => { expect(data).to.have.property('source'); @@ -50,14 +50,14 @@ export default function ({ getService }: FtrProviderContext) { tiebreaker: '_doc', timestamp: '@timestamp', }); - expect(data).to.have.property('status'); - expect(data?.status.metricIndicesExist).to.equal(true); - expect(data?.status.logIndicesExist).to.equal(true); + expect(data?.source).to.have.property('status'); + expect(data?.source.status?.metricIndicesExist).to.equal(true); + expect(data?.source.status?.logIndicesExist).to.equal(true); }); }); }); describe('/api/metrics/source/default/metrics/hasData', () => { - it('should just work', () => { + it('should just work', async () => { const resp = fetchHasData('metrics'); return resp.then((data) => { expect(data).to.have.property('hasData'); @@ -66,7 +66,7 @@ export default function ({ getService }: FtrProviderContext) { }); }); describe('/api/metrics/source/default/logs/hasData', () => { - it('should just work', () => { + it('should just work', async () => { const resp = fetchHasData('logs'); return resp.then((data) => { expect(data).to.have.property('hasData'); diff --git a/x-pack/test/api_integration/apis/metrics_ui/index.js b/x-pack/test/api_integration/apis/metrics_ui/index.js index 819a2d35b92a6..47e688f09c78f 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/index.js +++ b/x-pack/test/api_integration/apis/metrics_ui/index.js @@ -18,7 +18,6 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./snapshot')); loadTestFile(require.resolve('./metrics_alerting')); loadTestFile(require.resolve('./metrics_explorer')); - loadTestFile(require.resolve('./feature_controls')); loadTestFile(require.resolve('./ip_to_hostname')); loadTestFile(require.resolve('./http_source')); }); diff --git a/x-pack/test/api_integration/apis/metrics_ui/log_entries.ts b/x-pack/test/api_integration/apis/metrics_ui/log_entries.ts index 79d5e68344432..0e61e3aaa0754 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/log_entries.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/log_entries.ts @@ -6,21 +6,17 @@ import expect from '@kbn/expect'; import { v4 as uuidv4 } from 'uuid'; - -import { decodeOrThrow } from '../../../../plugins/infra/common/runtime_types'; - import { LOG_ENTRIES_PATH, logEntriesRequestRT, logEntriesResponseRT, } from '../../../../plugins/infra/common/http_api'; - import { - LogTimestampColumn, LogFieldColumn, LogMessageColumn, + LogTimestampColumn, } from '../../../../plugins/infra/common/log_entry'; - +import { decodeOrThrow } from '../../../../plugins/infra/common/runtime_types'; import { FtrProviderContext } from '../../ftr_provider_context'; const KEY_WITHIN_DATA_RANGE = { diff --git a/x-pack/test/api_integration/apis/metrics_ui/metadata.ts b/x-pack/test/api_integration/apis/metrics_ui/metadata.ts index e319e59045d26..e8bd547b3143e 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/metadata.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/metadata.ts @@ -5,7 +5,6 @@ */ import expect from '@kbn/expect'; -import { InfraNodeType } from '../../../../plugins/infra/server/graphql/types'; import { InfraMetadata, InfraMetadataRequest, @@ -50,7 +49,7 @@ export default function ({ getService }: FtrProviderContext) { const metadata = await fetchMetadata({ sourceId: 'default', nodeId: 'demo-stack-mysql-01', - nodeType: InfraNodeType.host, + nodeType: 'host', timeRange: timeRange700, }); if (metadata) { @@ -70,7 +69,7 @@ export default function ({ getService }: FtrProviderContext) { const metadata = await fetchMetadata({ sourceId: 'default', nodeId: '631f36a845514442b93c3fdd2dc91bcd8feb680b8ac5832c7fb8fdc167bb938e', - nodeType: InfraNodeType.container, + nodeType: 'container', timeRange: timeRange660, }); if (metadata) { @@ -92,7 +91,7 @@ export default function ({ getService }: FtrProviderContext) { const metadata = await fetchMetadata({ sourceId: 'default', nodeId: 'gke-observability-8--observability-8--bc1afd95-f0zc', - nodeType: InfraNodeType.host, + nodeType: 'host', timeRange: timeRange800withAws, }); if (metadata) { @@ -140,7 +139,7 @@ export default function ({ getService }: FtrProviderContext) { const metadata = await fetchMetadata({ sourceId: 'default', nodeId: 'ip-172-31-47-9.us-east-2.compute.internal', - nodeType: InfraNodeType.host, + nodeType: 'host', timeRange: timeRange800withAws, }); if (metadata) { @@ -189,7 +188,7 @@ export default function ({ getService }: FtrProviderContext) { const metadata = await fetchMetadata({ sourceId: 'default', nodeId: '14887487-99f8-11e9-9a96-42010a84004d', - nodeType: InfraNodeType.pod, + nodeType: 'pod', timeRange: timeRange800withAws, }); if (metadata) { @@ -242,7 +241,7 @@ export default function ({ getService }: FtrProviderContext) { const metadata = await fetchMetadata({ sourceId: 'default', nodeId: 'c74b04834c6d7cc1800c3afbe31d0c8c0c267f06e9eb45c2b0c2df3e6cee40c5', - nodeType: InfraNodeType.container, + nodeType: 'container', timeRange: timeRange800withAws, }); if (metadata) { diff --git a/x-pack/test/api_integration/apis/metrics_ui/metrics.ts b/x-pack/test/api_integration/apis/metrics_ui/metrics.ts index b9cbc58bbd6f7..e7cf2962cb313 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/metrics.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/metrics.ts @@ -7,8 +7,8 @@ import expect from '@kbn/expect'; import { first, last } from 'lodash'; +import { InfraTimerangeInput } from '../../../../plugins/infra/common/http_api/snapshot_api'; import { InventoryMetric } from '../../../../plugins/infra/common/inventory_models/types'; -import { InfraNodeType, InfraTimerangeInput } from '../../../../plugins/infra/public/graphql/types'; import { FtrProviderContext } from '../../ftr_provider_context'; import { DATES } from './constants'; @@ -19,7 +19,7 @@ const { min, max } = DATES['7.0.0'].hosts; interface NodeDetailsRequest { metrics: InventoryMetric[]; nodeId: string; - nodeType: InfraNodeType; + nodeType: string; sourceId: string; timerange: InfraTimerangeInput; cloudId?: string; @@ -44,7 +44,7 @@ export default function ({ getService }: FtrProviderContext) { return response.body; }; - it('should basically work', () => { + it('should basically work', async () => { const data = fetchNodeDetails({ sourceId: 'default', metrics: ['hostCpuUsage'], @@ -54,7 +54,7 @@ export default function ({ getService }: FtrProviderContext) { interval: '>=1m', }, nodeId: 'demo-stack-mysql-01', - nodeType: 'host' as InfraNodeType, + nodeType: 'host', }); return data.then((resp) => { if (!resp) { @@ -73,7 +73,7 @@ export default function ({ getService }: FtrProviderContext) { }); }); - it('should support multiple metrics', () => { + it('should support multiple metrics', async () => { const data = fetchNodeDetails({ sourceId: 'default', metrics: ['hostCpuUsage', 'hostLoad'], @@ -83,7 +83,7 @@ export default function ({ getService }: FtrProviderContext) { interval: '>=1m', }, nodeId: 'demo-stack-mysql-01', - nodeType: 'host' as InfraNodeType, + nodeType: 'host', }); return data.then((resp) => { if (!resp) { @@ -104,7 +104,7 @@ export default function ({ getService }: FtrProviderContext) { interval: '>=1m', }, nodeId: 'demo-stack-mysql-01', - nodeType: 'host' as InfraNodeType, + nodeType: 'host', }); return data.then((resp) => { if (!resp) { diff --git a/x-pack/test/api_integration/apis/metrics_ui/snapshot.ts b/x-pack/test/api_integration/apis/metrics_ui/snapshot.ts index 7339c142fb028..26048f25d55ff 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/snapshot.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/snapshot.ts @@ -7,10 +7,6 @@ import expect from '@kbn/expect'; import { first, last } from 'lodash'; -import { - InfraSnapshotMetricInput, - InfraNodeType, -} from '../../../../plugins/infra/server/graphql/types'; import { FtrProviderContext } from '../../ftr_provider_context'; import { SnapshotNodeResponse, @@ -39,7 +35,7 @@ export default function ({ getService }: FtrProviderContext) { before(() => esArchiver.load('infra/6.6.0/docker')); after(() => esArchiver.unload('infra/6.6.0/docker')); - it('should basically work', () => { + it('should basically work', async () => { const resp = fetchSnapshot({ sourceId: 'default', timerange: { @@ -47,8 +43,8 @@ export default function ({ getService }: FtrProviderContext) { from: min, interval: '1m', }, - metrics: [{ type: 'cpu' }] as InfraSnapshotMetricInput[], - nodeType: 'container' as InfraNodeType, + metrics: [{ type: 'cpu' }], + nodeType: 'container', groupBy: [], }); return resp.then((data) => { @@ -86,7 +82,7 @@ export default function ({ getService }: FtrProviderContext) { before(() => esArchiver.load('infra/8.0.0/logs_and_metrics')); after(() => esArchiver.unload('infra/8.0.0/logs_and_metrics')); - it("should use the id for the label when the name doesn't exist", () => { + it("should use the id for the label when the name doesn't exist", async () => { const resp = fetchSnapshot({ sourceId: 'default', timerange: { @@ -94,8 +90,8 @@ export default function ({ getService }: FtrProviderContext) { from: min, interval: '1m', }, - metrics: [{ type: 'cpu' }] as InfraSnapshotMetricInput[], - nodeType: 'pod' as InfraNodeType, + metrics: [{ type: 'cpu' }], + nodeType: 'pod', groupBy: [], }); return resp.then((data) => { @@ -118,7 +114,7 @@ export default function ({ getService }: FtrProviderContext) { } }); }); - it('should have an id and label', () => { + it('should have an id and label', async () => { const resp = fetchSnapshot({ sourceId: 'default', timerange: { @@ -126,8 +122,8 @@ export default function ({ getService }: FtrProviderContext) { from: min, interval: '1m', }, - metrics: [{ type: 'cpu' }] as InfraSnapshotMetricInput[], - nodeType: 'container' as InfraNodeType, + metrics: [{ type: 'cpu' }], + nodeType: 'container', groupBy: [], }); return resp.then((data) => { @@ -157,7 +153,7 @@ export default function ({ getService }: FtrProviderContext) { before(() => esArchiver.load('infra/7.0.0/hosts')); after(() => esArchiver.unload('infra/7.0.0/hosts')); - it('should basically work', () => { + it('should basically work', async () => { const resp = fetchSnapshot({ sourceId: 'default', timerange: { @@ -165,8 +161,8 @@ export default function ({ getService }: FtrProviderContext) { from: min, interval: '1m', }, - metrics: [{ type: 'cpu' }] as InfraSnapshotMetricInput[], - nodeType: 'host' as InfraNodeType, + metrics: [{ type: 'cpu' }], + nodeType: 'host', groupBy: [], }); return resp.then((data) => { @@ -193,7 +189,7 @@ export default function ({ getService }: FtrProviderContext) { }); }); - it('should allow for overrides for interval and ignoring lookback', () => { + it('should allow for overrides for interval and ignoring lookback', async () => { const resp = fetchSnapshot({ sourceId: 'default', timerange: { @@ -203,8 +199,8 @@ export default function ({ getService }: FtrProviderContext) { forceInterval: true, ignoreLookback: true, }, - metrics: [{ type: 'cpu' }] as InfraSnapshotMetricInput[], - nodeType: 'host' as InfraNodeType, + metrics: [{ type: 'cpu' }], + nodeType: 'host', groupBy: [], includeTimeseries: true, }); @@ -229,7 +225,7 @@ export default function ({ getService }: FtrProviderContext) { }); }); - it('should allow for overrides for lookback', () => { + it('should allow for overrides for lookback', async () => { const resp = fetchSnapshot({ sourceId: 'default', timerange: { @@ -238,8 +234,8 @@ export default function ({ getService }: FtrProviderContext) { interval: '1m', lookbackSize: 6, }, - metrics: [{ type: 'cpu' }] as InfraSnapshotMetricInput[], - nodeType: 'host' as InfraNodeType, + metrics: [{ type: 'cpu' }], + nodeType: 'host', groupBy: [], includeTimeseries: true, }); @@ -277,7 +273,7 @@ export default function ({ getService }: FtrProviderContext) { id: '1', }, ] as SnapshotMetricInput[], - nodeType: 'host' as InfraNodeType, + nodeType: 'host', groupBy: [], }); @@ -303,7 +299,7 @@ export default function ({ getService }: FtrProviderContext) { } }); - it('should basically work with 1 grouping', () => { + it('should basically work with 1 grouping', async () => { const resp = fetchSnapshot({ sourceId: 'default', timerange: { @@ -311,8 +307,8 @@ export default function ({ getService }: FtrProviderContext) { from: min, interval: '1m', }, - metrics: [{ type: 'cpu' }] as InfraSnapshotMetricInput[], - nodeType: 'host' as InfraNodeType, + metrics: [{ type: 'cpu' }], + nodeType: 'host', groupBy: [{ field: 'cloud.availability_zone' }], }); return resp.then((data) => { @@ -330,7 +326,7 @@ export default function ({ getService }: FtrProviderContext) { }); }); - it('should basically work with 2 groupings', () => { + it('should basically work with 2 groupings', async () => { const resp = fetchSnapshot({ sourceId: 'default', timerange: { @@ -338,8 +334,8 @@ export default function ({ getService }: FtrProviderContext) { from: min, interval: '1m', }, - metrics: [{ type: 'cpu' }] as InfraSnapshotMetricInput[], - nodeType: 'host' as InfraNodeType, + metrics: [{ type: 'cpu' }], + nodeType: 'host', groupBy: [{ field: 'cloud.provider' }, { field: 'cloud.availability_zone' }], }); @@ -359,7 +355,7 @@ export default function ({ getService }: FtrProviderContext) { }); }); - it('should show metrics for all nodes when grouping by service type', () => { + it('should show metrics for all nodes when grouping by service type', async () => { const resp = fetchSnapshot({ sourceId: 'default', timerange: { @@ -367,8 +363,8 @@ export default function ({ getService }: FtrProviderContext) { from: min, interval: '1m', }, - metrics: [{ type: 'cpu' }] as InfraSnapshotMetricInput[], - nodeType: 'host' as InfraNodeType, + metrics: [{ type: 'cpu' }], + nodeType: 'host', groupBy: [{ field: 'service.type' }], }); return resp.then((data) => { diff --git a/x-pack/test/api_integration/apis/metrics_ui/sources.ts b/x-pack/test/api_integration/apis/metrics_ui/sources.ts index 5908523af2496..d18ce882cab76 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/sources.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/sources.ts @@ -5,20 +5,27 @@ */ import expect from '@kbn/expect'; -import gql from 'graphql-tag'; -import { sourceQuery } from '../../../../plugins/infra/public/containers/source/query_source.gql_query'; import { - sourceConfigurationFieldsFragment, - sourceStatusFieldsFragment, -} from '../../../../plugins/infra/public/containers/source/source_fields_fragment.gql_query'; -import { SourceQuery } from '../../../../plugins/infra/public/graphql/types'; + SourceResponse, + InfraSavedSourceConfiguration, + SourceResponseRuntimeType, +} from '../../../../plugins/infra/common/http_api/source_api'; import { FtrProviderContext } from '../../ftr_provider_context'; -import { sharedFragments } from '../../../../plugins/infra/common/graphql/shared'; export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); - const client = getService('infraOpsGraphQLClient'); + const supertest = getService('supertest'); + const patchRequest = async ( + body: InfraSavedSourceConfiguration + ): Promise => { + const response = await supertest + .patch('/api/metrics/source/default') + .set('kbn-xsrf', 'xxx') + .send(body) + .expect(200); + return response.body; + }; describe('sources', () => { before(() => esArchiver.load('infra/metrics_and_logs')); @@ -26,409 +33,145 @@ export default function ({ getService }: FtrProviderContext) { beforeEach(() => esArchiver.load('empty_kibana')); afterEach(() => esArchiver.unload('empty_kibana')); - describe('query from container', () => { - it('returns the default source configuration when none has been saved', async () => { - const response = await client.query({ - query: sourceQuery, - variables: { - sourceId: 'default', - }, - }); - - const sourceConfiguration = response.data.source.configuration; - const sourceStatus = response.data.source.status; - - // shipped default values - expect(sourceConfiguration.name).to.be('Default'); - expect(sourceConfiguration.metricAlias).to.be('metrics-*,metricbeat-*'); - expect(sourceConfiguration.logAlias).to.be('logs-*,filebeat-*,kibana_sample_data_logs*'); - expect(sourceConfiguration.fields.container).to.be('container.id'); - expect(sourceConfiguration.fields.host).to.be('host.name'); - expect(sourceConfiguration.fields.pod).to.be('kubernetes.pod.uid'); - expect(sourceConfiguration.logColumns).to.have.length(3); - expect(sourceConfiguration.logColumns[0]).to.have.key('timestampColumn'); - expect(sourceConfiguration.logColumns[1]).to.have.key('fieldColumn'); - expect(sourceConfiguration.logColumns[2]).to.have.key('messageColumn'); - - // test data in x-pack/test/functional/es_archives/infra/data.json.gz - expect(sourceStatus.indexFields.length).to.be(1765); - expect(sourceStatus.logIndicesExist).to.be(true); - expect(sourceStatus.metricIndicesExist).to.be(true); - }); - }); - - describe('createSource mutation', () => { - it('saves and returns source configurations', async () => { - const response = await client.mutate({ - mutation: createSourceMutation, - variables: { - sourceProperties: { - name: 'NAME', - description: 'DESCRIPTION', - logAlias: 'filebeat-**', - metricAlias: 'metricbeat-**', - fields: { - container: 'CONTAINER', - host: 'HOST', - pod: 'POD', - tiebreaker: 'TIEBREAKER', - timestamp: 'TIMESTAMP', - }, - logColumns: [ - { - messageColumn: { - id: 'MESSAGE_COLUMN', - }, - }, - ], - }, - sourceId: 'default', - }, - }); - - const { version, updatedAt, configuration, status } = - response.data && response.data.createSource.source; - - expect(version).to.be.a('string'); - expect(updatedAt).to.be.greaterThan(0); - expect(configuration.name).to.be('NAME'); - expect(configuration.description).to.be('DESCRIPTION'); - expect(configuration.metricAlias).to.be('metricbeat-**'); - expect(configuration.logAlias).to.be('filebeat-**'); - expect(configuration.fields.container).to.be('CONTAINER'); - expect(configuration.fields.host).to.be('HOST'); - expect(configuration.fields.pod).to.be('POD'); - expect(configuration.fields.tiebreaker).to.be('TIEBREAKER'); - expect(configuration.fields.timestamp).to.be('TIMESTAMP'); - expect(configuration.logColumns).to.have.length(1); - expect(configuration.logColumns[0]).to.have.key('messageColumn'); - - expect(status.logIndicesExist).to.be(true); - expect(status.metricIndicesExist).to.be(true); - }); - - it('saves partial source configuration and returns it amended with defaults', async () => { - const response = await client.mutate({ - mutation: createSourceMutation, - variables: { - sourceProperties: { - name: 'NAME', - }, - sourceId: 'default', - }, - }); - - const { version, updatedAt, configuration, status } = - response.data && response.data.createSource.source; - - expect(version).to.be.a('string'); - expect(updatedAt).to.be.greaterThan(0); - expect(configuration.name).to.be('NAME'); - expect(configuration.description).to.be(''); - expect(configuration.metricAlias).to.be('metrics-*,metricbeat-*'); - expect(configuration.logAlias).to.be('logs-*,filebeat-*,kibana_sample_data_logs*'); - expect(configuration.fields.container).to.be('container.id'); - expect(configuration.fields.host).to.be('host.name'); - expect(configuration.fields.pod).to.be('kubernetes.pod.uid'); - expect(configuration.fields.tiebreaker).to.be('_doc'); - expect(configuration.fields.timestamp).to.be('@timestamp'); - expect(configuration.logColumns).to.have.length(3); - expect(status.logIndicesExist).to.be(true); - expect(status.metricIndicesExist).to.be(true); - }); - - it('refuses to overwrite an existing source', async () => { - await client.mutate({ - mutation: createSourceMutation, - variables: { - sourceProperties: { - name: 'NAME', - }, - sourceId: 'default', - }, - }); - - await client - .mutate({ - mutation: createSourceMutation, - variables: { - sourceProperties: { - name: 'NAME', - }, - sourceId: 'default', - }, - }) - .then( - () => { - expect().fail('should have failed with a conflict'); - }, - (err) => { - expect(err.message).to.contain('conflict'); - } - ); - }); - }); - - describe('deleteSource mutation', () => { - it('deletes an existing source', async () => { - const creationResponse = await client.mutate({ - mutation: createSourceMutation, - variables: { - sourceProperties: { - name: 'NAME', - }, - sourceId: 'default', - }, - }); - - const { version } = creationResponse.data && creationResponse.data.createSource.source; - - expect(version).to.be.a('string'); - - const deletionResponse = await client.mutate({ - mutation: deleteSourceMutation, - variables: { - sourceId: 'default', - }, - }); - - const { id } = deletionResponse.data && deletionResponse.data.deleteSource; - - expect(id).to.be('default'); - }); - }); - - describe('updateSource mutation', () => { + describe('patch request', () => { it('applies all top-level field updates to an existing source', async () => { - const creationResponse = await client.mutate({ - mutation: createSourceMutation, - variables: { - sourceProperties: { - name: 'NAME', - }, - sourceId: 'default', - }, + const creationResponse = await patchRequest({ + name: 'NAME', }); - const { version: initialVersion, updatedAt: createdAt } = - creationResponse.data && creationResponse.data.createSource.source; + const initialVersion = creationResponse?.source.version; + const createdAt = creationResponse?.source.updatedAt; expect(initialVersion).to.be.a('string'); expect(createdAt).to.be.greaterThan(0); - const updateResponse = await client.mutate({ - mutation: updateSourceMutation, - variables: { - sourceId: 'default', - sourceProperties: { - name: 'UPDATED_NAME', - description: 'UPDATED_DESCRIPTION', - metricAlias: 'metricbeat-**', - logAlias: 'filebeat-**', - }, - }, + const updateResponse = await patchRequest({ + name: 'UPDATED_NAME', + description: 'UPDATED_DESCRIPTION', + metricAlias: 'metricbeat-**', + logAlias: 'filebeat-**', }); - const { version, updatedAt, configuration, status } = - updateResponse.data && updateResponse.data.updateSource.source; + expect(SourceResponseRuntimeType.is(updateResponse)).to.be(true); + + const version = updateResponse?.source.version; + const updatedAt = updateResponse?.source.updatedAt; + const configuration = updateResponse?.source.configuration; + const status = updateResponse?.source.status; expect(version).to.be.a('string'); expect(version).to.not.be(initialVersion); - expect(updatedAt).to.be.greaterThan(createdAt); - expect(configuration.name).to.be('UPDATED_NAME'); - expect(configuration.description).to.be('UPDATED_DESCRIPTION'); - expect(configuration.metricAlias).to.be('metricbeat-**'); - expect(configuration.logAlias).to.be('filebeat-**'); - expect(configuration.fields.host).to.be('host.name'); - expect(configuration.fields.pod).to.be('kubernetes.pod.uid'); - expect(configuration.fields.tiebreaker).to.be('_doc'); - expect(configuration.fields.timestamp).to.be('@timestamp'); - expect(configuration.fields.container).to.be('container.id'); - expect(configuration.logColumns).to.have.length(3); - expect(status.logIndicesExist).to.be(true); - expect(status.metricIndicesExist).to.be(true); + expect(updatedAt).to.be.greaterThan(createdAt || 0); + expect(configuration?.name).to.be('UPDATED_NAME'); + expect(configuration?.description).to.be('UPDATED_DESCRIPTION'); + expect(configuration?.metricAlias).to.be('metricbeat-**'); + expect(configuration?.logAlias).to.be('filebeat-**'); + expect(configuration?.fields.host).to.be('host.name'); + expect(configuration?.fields.pod).to.be('kubernetes.pod.uid'); + expect(configuration?.fields.tiebreaker).to.be('_doc'); + expect(configuration?.fields.timestamp).to.be('@timestamp'); + expect(configuration?.fields.container).to.be('container.id'); + expect(configuration?.logColumns).to.have.length(3); + expect(status?.logIndicesExist).to.be(true); + expect(status?.metricIndicesExist).to.be(true); }); it('applies a single top-level update to an existing source', async () => { - const creationResponse = await client.mutate({ - mutation: createSourceMutation, - variables: { - sourceProperties: { - name: 'NAME', - }, - sourceId: 'default', - }, + const creationResponse = await patchRequest({ + name: 'NAME', }); - const { version: initialVersion, updatedAt: createdAt } = - creationResponse.data && creationResponse.data.createSource.source; + const initialVersion = creationResponse?.source.version; + const createdAt = creationResponse?.source.updatedAt; expect(initialVersion).to.be.a('string'); expect(createdAt).to.be.greaterThan(0); - const updateResponse = await client.mutate({ - mutation: updateSourceMutation, - variables: { - sourceId: 'default', - sourceProperties: { - metricAlias: 'metricbeat-**', - }, - }, + const updateResponse = await patchRequest({ + name: 'UPDATED_NAME', + description: 'UPDATED_DESCRIPTION', + metricAlias: 'metricbeat-**', }); - const { version, updatedAt, configuration, status } = - updateResponse.data && updateResponse.data.updateSource.source; + const version = updateResponse?.source.version; + const updatedAt = updateResponse?.source.updatedAt; + const configuration = updateResponse?.source.configuration; + const status = updateResponse?.source.status; expect(version).to.be.a('string'); expect(version).to.not.be(initialVersion); - expect(updatedAt).to.be.greaterThan(createdAt); - expect(configuration.metricAlias).to.be('metricbeat-**'); - expect(configuration.logAlias).to.be('logs-*,filebeat-*,kibana_sample_data_logs*'); - expect(status.logIndicesExist).to.be(true); - expect(status.metricIndicesExist).to.be(true); + expect(updatedAt).to.be.greaterThan(createdAt || 0); + expect(configuration?.metricAlias).to.be('metricbeat-**'); + expect(configuration?.logAlias).to.be('logs-*,filebeat-*,kibana_sample_data_logs*'); + expect(status?.logIndicesExist).to.be(true); + expect(status?.metricIndicesExist).to.be(true); }); it('applies a single nested field update to an existing source', async () => { - const creationResponse = await client.mutate({ - mutation: createSourceMutation, - variables: { - sourceProperties: { - name: 'NAME', - fields: { - host: 'HOST', - }, - }, - sourceId: 'default', + const creationResponse = await patchRequest({ + name: 'NAME', + fields: { + host: 'HOST', }, }); - const { version: initialVersion, updatedAt: createdAt } = - creationResponse.data && creationResponse.data.createSource.source; + const initialVersion = creationResponse?.source.version; + const createdAt = creationResponse?.source.updatedAt; expect(initialVersion).to.be.a('string'); expect(createdAt).to.be.greaterThan(0); - const updateResponse = await client.mutate({ - mutation: updateSourceMutation, - variables: { - sourceId: 'default', - sourceProperties: { - fields: { - container: 'UPDATED_CONTAINER', - }, - }, + const updateResponse = await patchRequest({ + fields: { + container: 'UPDATED_CONTAINER', }, }); - const { version, updatedAt, configuration } = - updateResponse.data && updateResponse.data.updateSource.source; + const version = updateResponse?.source.version; + const updatedAt = updateResponse?.source.updatedAt; + const configuration = updateResponse?.source.configuration; expect(version).to.be.a('string'); expect(version).to.not.be(initialVersion); - expect(updatedAt).to.be.greaterThan(createdAt); - expect(configuration.fields.container).to.be('UPDATED_CONTAINER'); - expect(configuration.fields.host).to.be('HOST'); - expect(configuration.fields.pod).to.be('kubernetes.pod.uid'); - expect(configuration.fields.tiebreaker).to.be('_doc'); - expect(configuration.fields.timestamp).to.be('@timestamp'); + expect(updatedAt).to.be.greaterThan(createdAt || 0); + expect(configuration?.fields.container).to.be('UPDATED_CONTAINER'); + expect(configuration?.fields.host).to.be('HOST'); + expect(configuration?.fields.pod).to.be('kubernetes.pod.uid'); + expect(configuration?.fields.tiebreaker).to.be('_doc'); + expect(configuration?.fields.timestamp).to.be('@timestamp'); }); it('applies a log column update to an existing source', async () => { - const creationResponse = await client.mutate({ - mutation: createSourceMutation, - variables: { - sourceProperties: { - name: 'NAME', - }, - sourceId: 'default', - }, + const creationResponse = await patchRequest({ + name: 'NAME', }); - const { version: initialVersion, updatedAt: createdAt } = - creationResponse.data && creationResponse.data.createSource.source; + const initialVersion = creationResponse?.source.version; + const createdAt = creationResponse?.source.updatedAt; - expect(initialVersion).to.be.a('string'); - expect(createdAt).to.be.greaterThan(0); - - const updateResponse = await client.mutate({ - mutation: updateSourceMutation, - variables: { - sourceId: 'default', - sourceProperties: { - logColumns: [ - { - fieldColumn: { - id: 'ADDED_COLUMN_ID', - field: 'ADDED_COLUMN_FIELD', - }, - }, - ], + const updateResponse = await patchRequest({ + logColumns: [ + { + fieldColumn: { + id: 'ADDED_COLUMN_ID', + field: 'ADDED_COLUMN_FIELD', + }, }, - }, + ], }); - const { version, updatedAt, configuration } = - updateResponse.data && updateResponse.data.updateSource.source; - + const version = updateResponse?.source.version; + const updatedAt = updateResponse?.source.updatedAt; + const configuration = updateResponse?.source.configuration; expect(version).to.be.a('string'); expect(version).to.not.be(initialVersion); - expect(updatedAt).to.be.greaterThan(createdAt); - expect(configuration.logColumns).to.have.length(1); - expect(configuration.logColumns[0]).to.have.key('fieldColumn'); - expect(configuration.logColumns[0].fieldColumn).to.have.property('id', 'ADDED_COLUMN_ID'); - expect(configuration.logColumns[0].fieldColumn).to.have.property( - 'field', - 'ADDED_COLUMN_FIELD' - ); + expect(updatedAt).to.be.greaterThan(createdAt || 0); + expect(configuration?.logColumns).to.have.length(1); + expect(configuration?.logColumns[0]).to.have.key('fieldColumn'); + const fieldColumn = (configuration?.logColumns[0] as any).fieldColumn; + expect(fieldColumn).to.have.property('id', 'ADDED_COLUMN_ID'); + expect(fieldColumn).to.have.property('field', 'ADDED_COLUMN_FIELD'); }); }); }); } - -const createSourceMutation = gql` - mutation createSource($sourceId: ID!, $sourceProperties: UpdateSourceInput!) { - createSource(id: $sourceId, sourceProperties: $sourceProperties) { - source { - ...InfraSourceFields - configuration { - ...SourceConfigurationFields - } - status { - ...SourceStatusFields - } - } - } - } - - ${sharedFragments.InfraSourceFields} - ${sourceConfigurationFieldsFragment} - ${sourceStatusFieldsFragment} -`; - -const deleteSourceMutation = gql` - mutation deleteSource($sourceId: ID!) { - deleteSource(id: $sourceId) { - id - } - } -`; - -const updateSourceMutation = gql` - mutation updateSource($sourceId: ID!, $sourceProperties: UpdateSourceInput!) { - updateSource(id: $sourceId, sourceProperties: $sourceProperties) { - source { - ...InfraSourceFields - configuration { - ...SourceConfigurationFields - } - status { - ...SourceStatusFields - } - } - } - } - - ${sharedFragments.InfraSourceFields} - ${sourceConfigurationFieldsFragment} - ${sourceStatusFieldsFragment} -`; diff --git a/x-pack/test/api_integration/apis/monitoring/elasticsearch_settings/set_collection_enabled.js b/x-pack/test/api_integration/apis/monitoring/elasticsearch_settings/set_collection_enabled.js index 91762e0933e94..9e3a131e06768 100644 --- a/x-pack/test/api_integration/apis/monitoring/elasticsearch_settings/set_collection_enabled.js +++ b/x-pack/test/api_integration/apis/monitoring/elasticsearch_settings/set_collection_enabled.js @@ -9,6 +9,7 @@ import expect from '@kbn/expect'; export default function ({ getService }) { const supertest = getService('supertest'); const esSupertest = getService('esSupertest'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); describe('update collection_enabled setting', () => { after(async () => { @@ -26,7 +27,7 @@ export default function ({ getService }) { }; await esSupertest.put('/_cluster/settings').send(disableCollection).expect(200); - await esSupertest.delete('/.monitoring-*').expect(200); + await esDeleteAllIndices('/.monitoring-*'); }); it('should set collection.enabled to true', async () => { diff --git a/x-pack/test/api_integration/apis/search/session.ts b/x-pack/test/api_integration/apis/search/session.ts index ee2e4337adc95..ad3504e756a09 100644 --- a/x-pack/test/api_integration/apis/search/session.ts +++ b/x-pack/test/api_integration/apis/search/session.ts @@ -6,12 +6,27 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; +import { SearchSessionStatus } from '../../../../plugins/data_enhanced/common'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertest'); describe('search session', () => { describe('session management', () => { + it('should fail to create a session with no name', async () => { + const sessionId = `my-session-${Math.random()}`; + await supertest + .post(`/internal/session`) + .set('kbn-xsrf', 'foo') + .send({ + sessionId, + appId: 'discover', + expires: '123', + urlGeneratorId: 'discover', + }) + .expect(400); + }); + it('should create and get a session', async () => { const sessionId = `my-session-${Math.random()}`; await supertest @@ -29,11 +44,11 @@ export default function ({ getService }: FtrProviderContext) { await supertest.get(`/internal/session/${sessionId}`).set('kbn-xsrf', 'foo').expect(200); }); - it('should fail to delete an unknown session', async () => { + it('should fail to cancel an unknown session', async () => { await supertest.delete(`/internal/session/123`).set('kbn-xsrf', 'foo').expect(404); }); - it('should create and delete a session', async () => { + it('should create and cancel a session', async () => { const sessionId = `my-session-${Math.random()}`; await supertest .post(`/internal/session`) @@ -49,10 +64,16 @@ export default function ({ getService }: FtrProviderContext) { await supertest.delete(`/internal/session/${sessionId}`).set('kbn-xsrf', 'foo').expect(200); - await supertest.get(`/internal/session/${sessionId}`).set('kbn-xsrf', 'foo').expect(404); + const resp = await supertest + .get(`/internal/session/${sessionId}`) + .set('kbn-xsrf', 'foo') + .expect(200); + + const { status } = resp.body.attributes; + expect(status).to.equal(SearchSessionStatus.CANCELLED); }); - it('should sync search ids into session', async () => { + it('should sync search ids into persisted session', async () => { const sessionId = `my-session-${Math.random()}`; // run search @@ -76,7 +97,7 @@ export default function ({ getService }: FtrProviderContext) { const { id: id1 } = searchRes1.body; - // create session + // persist session await supertest .post(`/internal/session`) .set('kbn-xsrf', 'foo') @@ -108,21 +129,168 @@ export default function ({ getService }: FtrProviderContext) { const { id: id2 } = searchRes2.body; - // wait 10 seconds for ids to be synced - // TODO: make the refresh interval dynamic, so we can speed it up! - await new Promise((resolve) => setTimeout(resolve, 10000)); - const resp = await supertest .get(`/internal/session/${sessionId}`) .set('kbn-xsrf', 'foo') .expect(200); - const { idMapping } = resp.body.attributes; + const { name, touched, created, persisted, idMapping } = resp.body.attributes; + expect(persisted).to.be(true); + expect(name).to.be('My Session'); + expect(touched).not.to.be(undefined); + expect(created).not.to.be(undefined); const idMappings = Object.values(idMapping).map((value: any) => value.id); expect(idMappings).to.contain(id1); expect(idMappings).to.contain(id2); }); + + it('should create and extend a session', async () => { + const sessionId = `my-session-${Math.random()}`; + await supertest + .post(`/internal/session`) + .set('kbn-xsrf', 'foo') + .send({ + sessionId, + name: 'My Session', + appId: 'discover', + expires: '123', + urlGeneratorId: 'discover', + }) + .expect(200); + + await supertest + .post(`/internal/session/${sessionId}/_extend`) + .set('kbn-xsrf', 'foo') + .send({ + expires: '2021-02-26T21:02:43.742Z', + }) + .expect(200); + }); + }); + + it('should fail to extend a nonexistent session', async () => { + await supertest + .post(`/internal/session/123/_extend`) + .set('kbn-xsrf', 'foo') + .send({ + expires: '2021-02-26T21:02:43.742Z', + }) + .expect(404); + }); + + it('should sync search ids into not persisted session', async () => { + const sessionId = `my-session-${Math.random()}`; + + // run search + const searchRes1 = await supertest + .post(`/internal/search/ese`) + .set('kbn-xsrf', 'foo') + .send({ + sessionId, + params: { + body: { + query: { + term: { + agent: '1', + }, + }, + }, + wait_for_completion_timeout: '1ms', + }, + }) + .expect(200); + + const { id: id1 } = searchRes1.body; + + // run search + const searchRes2 = await supertest + .post(`/internal/search/ese`) + .set('kbn-xsrf', 'foo') + .send({ + sessionId, + params: { + body: { + query: { + match_all: {}, + }, + }, + wait_for_completion_timeout: '1ms', + }, + }) + .expect(200); + + const { id: id2 } = searchRes2.body; + + const resp = await supertest + .get(`/internal/session/${sessionId}`) + .set('kbn-xsrf', 'foo') + .expect(200); + + const { appId, name, touched, created, persisted, idMapping } = resp.body.attributes; + expect(persisted).to.be(false); + expect(name).to.be(undefined); + expect(appId).to.be(undefined); + expect(touched).not.to.be(undefined); + expect(created).not.to.be(undefined); + + const idMappings = Object.values(idMapping).map((value: any) => value.id); + expect(idMappings).to.contain(id1); + expect(idMappings).to.contain(id2); + }); + + it('touched time updates when you poll on an search', async () => { + const sessionId = `my-session-${Math.random()}`; + + // run search + const searchRes1 = await supertest + .post(`/internal/search/ese`) + .set('kbn-xsrf', 'foo') + .send({ + sessionId, + params: { + body: { + query: { + term: { + agent: '1', + }, + }, + }, + wait_for_completion_timeout: '1ms', + }, + }) + .expect(200); + + const { id: id1 } = searchRes1.body; + + // it might take the session a moment to be created + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const getSessionFirstTime = await supertest + .get(`/internal/session/${sessionId}`) + .set('kbn-xsrf', 'foo') + .expect(200); + + // poll on search + await supertest + .post(`/internal/search/ese/${id1}`) + .set('kbn-xsrf', 'foo') + .send({ + sessionId, + }) + .expect(200); + + const getSessionSecondTime = await supertest + .get(`/internal/session/${sessionId}`) + .set('kbn-xsrf', 'foo') + .expect(200); + + expect(getSessionFirstTime.body.attributes.sessionId).to.be.equal( + getSessionSecondTime.body.attributes.sessionId + ); + expect(getSessionFirstTime.body.attributes.touched).to.be.lessThan( + getSessionSecondTime.body.attributes.touched + ); }); }); } diff --git a/x-pack/test/api_integration/apis/security_solution/saved_objects/timeline.ts b/x-pack/test/api_integration/apis/security_solution/saved_objects/timeline.ts index 07e7cad89c24a..b576d6386d9d5 100644 --- a/x-pack/test/api_integration/apis/security_solution/saved_objects/timeline.ts +++ b/x-pack/test/api_integration/apis/security_solution/saved_objects/timeline.ts @@ -19,6 +19,7 @@ import { deleteTimelineMutation } from '../../../../../plugins/security_solution import { persistTimelineFavoriteMutation } from '../../../../../plugins/security_solution/public/timelines/containers/favorite/persist.gql_query'; import { persistTimelineMutation } from '../../../../../plugins/security_solution/public/timelines/containers/persist.gql_query'; import { TimelineResult } from '../../../../../plugins/security_solution/public/graphql/types'; +import { TimelineType } from '../../../../../plugins/security_solution/common/types/timeline'; export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); @@ -209,12 +210,47 @@ export default function ({ getService }: FtrProviderContext) { mutation: persistTimelineFavoriteMutation, variables: { timelineId: savedObjectId, + templateTimelineId: null, + templateTimelineVersion: null, + timelineType: TimelineType.default, }, }); expect(responseToTest.data!.persistFavorite.savedObjectId).to.be(savedObjectId); expect(responseToTest.data!.persistFavorite.favorite.length).to.be(1); expect(responseToTest.data!.persistFavorite.version).to.not.be.eql(version); + expect(responseToTest.data!.persistFavorite.templateTimelineId).to.be.eql(null); + expect(responseToTest.data!.persistFavorite.templateTimelineVersion).to.be.eql(null); + expect(responseToTest.data!.persistFavorite.timelineType).to.be.eql(TimelineType.default); + }); + + it('to an existing timeline template', async () => { + const titleToSaved = 'hello title'; + const templateTimelineIdFromStore = 'f4a90a2d-365c-407b-9fef-c1dcb33a6ab3'; + const templateTimelineVersionFromStore = 1; + const response = await createBasicTimeline(client, titleToSaved); + const { savedObjectId, version } = response.data && response.data.persistTimeline.timeline; + + const responseToTest = await client.mutate({ + mutation: persistTimelineFavoriteMutation, + variables: { + timelineId: savedObjectId, + templateTimelineId: templateTimelineIdFromStore, + templateTimelineVersion: templateTimelineVersionFromStore, + timelineType: TimelineType.template, + }, + }); + + expect(responseToTest.data!.persistFavorite.savedObjectId).to.be(savedObjectId); + expect(responseToTest.data!.persistFavorite.favorite.length).to.be(1); + expect(responseToTest.data!.persistFavorite.version).to.not.be.eql(version); + expect(responseToTest.data!.persistFavorite.templateTimelineId).to.be.eql( + templateTimelineIdFromStore + ); + expect(responseToTest.data!.persistFavorite.templateTimelineVersion).to.be.eql( + templateTimelineVersionFromStore + ); + expect(responseToTest.data!.persistFavorite.timelineType).to.be.eql(TimelineType.template); }); it('to Unfavorite an existing timeline', async () => { @@ -226,6 +262,9 @@ export default function ({ getService }: FtrProviderContext) { mutation: persistTimelineFavoriteMutation, variables: { timelineId: savedObjectId, + templateTimelineId: null, + templateTimelineVersion: null, + timelineType: TimelineType.default, }, }); @@ -233,12 +272,57 @@ export default function ({ getService }: FtrProviderContext) { mutation: persistTimelineFavoriteMutation, variables: { timelineId: savedObjectId, + templateTimelineId: null, + templateTimelineVersion: null, + timelineType: TimelineType.default, }, }); expect(responseToTest.data!.persistFavorite.savedObjectId).to.be(savedObjectId); expect(responseToTest.data!.persistFavorite.favorite).to.be.empty(); expect(responseToTest.data!.persistFavorite.version).to.not.be.eql(version); + expect(responseToTest.data!.persistFavorite.templateTimelineId).to.be.eql(null); + expect(responseToTest.data!.persistFavorite.templateTimelineVersion).to.be.eql(null); + expect(responseToTest.data!.persistFavorite.timelineType).to.be.eql(TimelineType.default); + }); + + it('to Unfavorite an existing timeline template', async () => { + const titleToSaved = 'hello title'; + const templateTimelineIdFromStore = 'f4a90a2d-365c-407b-9fef-c1dcb33a6ab3'; + const templateTimelineVersionFromStore = 1; + const response = await createBasicTimeline(client, titleToSaved); + const { savedObjectId, version } = response.data && response.data.persistTimeline.timeline; + + await client.mutate({ + mutation: persistTimelineFavoriteMutation, + variables: { + timelineId: savedObjectId, + templateTimelineId: templateTimelineIdFromStore, + templateTimelineVersion: templateTimelineVersionFromStore, + timelineType: TimelineType.template, + }, + }); + + const responseToTest = await client.mutate({ + mutation: persistTimelineFavoriteMutation, + variables: { + timelineId: savedObjectId, + templateTimelineId: templateTimelineIdFromStore, + templateTimelineVersion: templateTimelineVersionFromStore, + timelineType: TimelineType.template, + }, + }); + + expect(responseToTest.data!.persistFavorite.savedObjectId).to.be(savedObjectId); + expect(responseToTest.data!.persistFavorite.favorite).to.be.empty(); + expect(responseToTest.data!.persistFavorite.version).to.not.be.eql(version); + expect(responseToTest.data!.persistFavorite.templateTimelineId).to.be.eql( + templateTimelineIdFromStore + ); + expect(responseToTest.data!.persistFavorite.templateTimelineVersion).to.be.eql( + templateTimelineVersionFromStore + ); + expect(responseToTest.data!.persistFavorite.timelineType).to.be.eql(TimelineType.template); }); it('to a timeline without a timelineId', async () => { @@ -246,12 +330,43 @@ export default function ({ getService }: FtrProviderContext) { mutation: persistTimelineFavoriteMutation, variables: { timelineId: null, + templateTimelineId: null, + templateTimelineVersion: null, + timelineType: TimelineType.default, }, }); expect(response.data!.persistFavorite.savedObjectId).to.not.be.empty(); expect(response.data!.persistFavorite.favorite.length).to.be(1); expect(response.data!.persistFavorite.version).to.not.be.empty(); + expect(response.data!.persistFavorite.templateTimelineId).to.be.eql(null); + expect(response.data!.persistFavorite.templateTimelineVersion).to.be.eql(null); + expect(response.data!.persistFavorite.timelineType).to.be.eql(TimelineType.default); + }); + + it('to a timeline template without a timelineId', async () => { + const templateTimelineIdFromStore = 'f4a90a2d-365c-407b-9fef-c1dcb33a6ab3'; + const templateTimelineVersionFromStore = 1; + const response = await client.mutate({ + mutation: persistTimelineFavoriteMutation, + variables: { + timelineId: null, + templateTimelineId: templateTimelineIdFromStore, + templateTimelineVersion: templateTimelineVersionFromStore, + timelineType: TimelineType.template, + }, + }); + + expect(response.data!.persistFavorite.savedObjectId).to.not.be.empty(); + expect(response.data!.persistFavorite.favorite.length).to.be(1); + expect(response.data!.persistFavorite.version).to.not.be.empty(); + expect(response.data!.persistFavorite.templateTimelineId).to.be.eql( + templateTimelineIdFromStore + ); + expect(response.data!.persistFavorite.templateTimelineVersion).to.be.eql( + templateTimelineVersionFromStore + ); + expect(response.data!.persistFavorite.timelineType).to.be.eql(TimelineType.template); }); }); diff --git a/x-pack/test/api_integration/apis/security_solution/timeline_details.ts b/x-pack/test/api_integration/apis/security_solution/timeline_details.ts index aec9a896afc7c..da104e5124b38 100644 --- a/x-pack/test/api_integration/apis/security_solution/timeline_details.ts +++ b/x-pack/test/api_integration/apis/security_solution/timeline_details.ts @@ -558,6 +558,14 @@ const EXPECTED_DATA = [ }, ]; +const EXPECTED_KPI_COUNTS = { + destinationIpCount: 154, + hostCount: 1, + processCount: 0, + sourceIpCount: 121, + userCount: 0, +}; + export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const supertest = getService('supertest'); @@ -587,5 +595,24 @@ export default function ({ getService }: FtrProviderContext) { }) ).to.eql(sortBy(EXPECTED_DATA, 'name')); }); + + it('Make sure that we get kpi data', async () => { + const { + body: { destinationIpCount, hostCount, processCount, sourceIpCount, userCount }, + } = await supertest + .post('/internal/search/securitySolutionTimelineSearchStrategy/') + .set('kbn-xsrf', 'true') + .send({ + factoryQueryType: TimelineEventsQueries.kpi, + docValueFields: [], + indexName: INDEX_NAME, + inspect: false, + eventId: ID, + }) + .expect(200); + expect({ destinationIpCount, hostCount, processCount, sourceIpCount, userCount }).to.eql( + EXPECTED_KPI_COUNTS + ); + }); }); } diff --git a/x-pack/test/api_integration/apis/uptime/index.ts b/x-pack/test/api_integration/apis/uptime/index.ts index 0ba23d578c706..5b2959c733b19 100644 --- a/x-pack/test/api_integration/apis/uptime/index.ts +++ b/x-pack/test/api_integration/apis/uptime/index.ts @@ -7,16 +7,10 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, loadTestFile }: FtrProviderContext) { - const es = getService('legacyEs'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); describe('uptime', () => { - before( - async () => - await es.indices.delete({ - index: 'heartbeat*', - ignore: [404], - }) - ); + before(async () => await esDeleteAllIndices('heartbeat*')); loadTestFile(require.resolve('./feature_controls')); loadTestFile(require.resolve('./get_all_pings')); diff --git a/x-pack/test/api_integration/config.ts b/x-pack/test/api_integration/config.ts index 546b23ab4f26c..8563d60ca68fc 100644 --- a/x-pack/test/api_integration/config.ts +++ b/x-pack/test/api_integration/config.ts @@ -31,6 +31,8 @@ export async function getApiIntegrationConfig({ readConfigFile }: FtrConfigProvi '--xpack.fleet.enabled=true', '--xpack.fleet.agents.pollingRequestTimeout=5000', // 5 seconds '--xpack.data_enhanced.search.sessions.enabled=true', // enable WIP send to background UI + '--xpack.data_enhanced.search.sessions.notTouchedTimeout=15s', // shorten notTouchedTimeout for quicker testing + '--xpack.data_enhanced.search.sessions.trackingInterval=5s', // shorten trackingInterval for quicker testing ], }, esTestCluster: { diff --git a/x-pack/test/api_integration/services/index.ts b/x-pack/test/api_integration/services/index.ts index 7113e117582dd..2ac6f30b1e181 100644 --- a/x-pack/test/api_integration/services/index.ts +++ b/x-pack/test/api_integration/services/index.ts @@ -15,10 +15,6 @@ import { EsSupertestWithoutAuthProvider } from './es_supertest_without_auth'; import { SupertestWithoutAuthProvider } from './supertest_without_auth'; import { UsageAPIProvider } from './usage_api'; -import { - InfraOpsGraphQLClientProvider, - InfraOpsGraphQLClientFactoryProvider, -} from './infraops_graphql_client'; import { SecuritySolutionGraphQLClientProvider, SecuritySolutionGraphQLClientFactoryProvider, @@ -37,8 +33,6 @@ export const services = { legacyEs: LegacyEsProvider, esSupertestWithoutAuth: EsSupertestWithoutAuthProvider, - infraOpsGraphQLClient: InfraOpsGraphQLClientProvider, - infraOpsGraphQLClientFactory: InfraOpsGraphQLClientFactoryProvider, infraOpsSourceConfiguration: InfraOpsSourceConfigurationProvider, infraLogSourceConfiguration: InfraLogSourceConfigurationProvider, securitySolutionGraphQLClient: SecuritySolutionGraphQLClientProvider, diff --git a/x-pack/test/api_integration/services/infraops_graphql_client.ts b/x-pack/test/api_integration/services/infraops_graphql_client.ts deleted file mode 100644 index 81f0ac8fad919..0000000000000 --- a/x-pack/test/api_integration/services/infraops_graphql_client.ts +++ /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 { format as formatUrl } from 'url'; -import fetch from 'node-fetch'; -import { InMemoryCache, IntrospectionFragmentMatcher } from 'apollo-cache-inmemory'; -import { ApolloClient } from 'apollo-client'; -import { HttpLink } from 'apollo-link-http'; - -import { FtrProviderContext } from '../ftr_provider_context'; - -import introspectionQueryResultData from '../../../plugins/infra/public/graphql/introspection.json'; - -export function InfraOpsGraphQLClientProvider(context: FtrProviderContext) { - return InfraOpsGraphQLClientFactoryProvider(context)(); -} - -interface InfraOpsGraphQLClientFactoryOptions { - username?: string; - password?: string; - basePath?: string; -} - -export function InfraOpsGraphQLClientFactoryProvider({ getService }: FtrProviderContext) { - const config = getService('config'); - const superAuth: string = config.get('servers.elasticsearch.auth'); - const [superUsername, superPassword] = superAuth.split(':'); - - return function (options?: InfraOpsGraphQLClientFactoryOptions) { - const { username = superUsername, password = superPassword, basePath = null } = options || {}; - - const kbnURLWithoutAuth = formatUrl({ ...config.get('servers.kibana'), auth: false }); - - const httpLink = new HttpLink({ - credentials: 'same-origin', - fetch: fetch as any, - headers: { - 'kbn-xsrf': 'xxx', - authorization: `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`, - }, - uri: `${kbnURLWithoutAuth}${basePath || ''}/api/infra/graphql`, - }); - - return new ApolloClient({ - cache: new InMemoryCache({ - fragmentMatcher: new IntrospectionFragmentMatcher({ - // @ts-expect-error apollo-cache-inmemory types don't match actual introspection data - introspectionQueryResultData, - }), - }), - defaultOptions: { - query: { - fetchPolicy: 'no-cache', - }, - watchQuery: { - fetchPolicy: 'no-cache', - }, - mutate: { - fetchPolicy: 'no-cache', - } as any, - }, - link: httpLink, - }); - }; -} diff --git a/x-pack/test/api_integration/services/infraops_source_configuration.ts b/x-pack/test/api_integration/services/infraops_source_configuration.ts index 728a58829d1ce..7c6f2c5aa7315 100644 --- a/x-pack/test/api_integration/services/infraops_source_configuration.ts +++ b/x-pack/test/api_integration/services/infraops_source_configuration.ts @@ -4,65 +4,36 @@ * you may not use this file except in compliance with the Elastic License. */ -import gql from 'graphql-tag'; - +import { + InfraSavedSourceConfiguration, + SourceResponse, +} from '../../../plugins/infra/common/http_api/source_api'; import { FtrProviderContext } from '../ftr_provider_context'; -import { UpdateSourceInput, UpdateSourceResult } from '../../../plugins/infra/public/graphql/types'; - -const createSourceMutation = gql` - mutation createSource($sourceId: ID!, $sourceProperties: UpdateSourceInput!) { - createSource(id: $sourceId, sourceProperties: $sourceProperties) { - source { - id - version - configuration { - name - logColumns { - ... on InfraSourceTimestampLogColumn { - timestampColumn { - id - } - } - ... on InfraSourceMessageLogColumn { - messageColumn { - id - } - } - ... on InfraSourceFieldLogColumn { - fieldColumn { - id - field - } - } - } - } - } - } - } -`; export function InfraOpsSourceConfigurationProvider({ getService }: FtrProviderContext) { - const client = getService('infraOpsGraphQLClient'); const log = getService('log'); + const supertest = getService('supertest'); + const patchRequest = async ( + body: InfraSavedSourceConfiguration + ): Promise => { + const response = await supertest + .patch('/api/metrics/source/default') + .set('kbn-xsrf', 'xxx') + .send(body) + .expect(200); + return response.body; + }; return { - async createConfiguration(sourceId: string, sourceProperties: UpdateSourceInput) { + async createConfiguration(sourceId: string, sourceProperties: InfraSavedSourceConfiguration) { log.debug( `Creating Infra UI source configuration "${sourceId}" with properties ${JSON.stringify( sourceProperties )}` ); - const response = await client.mutate({ - mutation: createSourceMutation, - variables: { - sourceProperties, - sourceId, - }, - }); - - const result: UpdateSourceResult = response.data!.createSource; - return result.source.version; + const response = await patchRequest(sourceProperties); + return response?.source.version; }, }; } diff --git a/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts b/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts index b3e7e0672fc7f..fe32e4493b6e4 100644 --- a/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts +++ b/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts @@ -6,7 +6,7 @@ import expect from '@kbn/expect'; import url from 'url'; -import { sortBy, pick, last } from 'lodash'; +import { sortBy, pick, last, omit } from 'lodash'; import { ValuesType } from 'utility-types'; import { registry } from '../../../common/registry'; import { Maybe } from '../../../../../plugins/apm/typings/common'; @@ -306,7 +306,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { before(async () => { response = await supertest.get( url.format({ - pathname: `/api/apm/services/opbeans-java/dependencies`, + pathname: `/api/apm/services/opbeans-python/dependencies`, query: { start, end, @@ -323,14 +323,41 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns at least one item', () => { expect(response.body.length).to.be.greaterThan(0); + + expectSnapshot( + omit(response.body[0], [ + 'errorRate.timeseries', + 'throughput.timeseries', + 'latency.timeseries', + ]) + ).toMatchInline(` + Object { + "errorRate": Object { + "value": 0, + }, + "impact": 1.97910470896139, + "latency": Object { + "value": 1043.99015586546, + }, + "name": "redis", + "spanSubtype": "redis", + "spanType": "db", + "throughput": Object { + "value": 40.6333333333333, + }, + "type": "external", + } + `); }); it('returns the right names', () => { const names = response.body.map((item) => item.name); expectSnapshot(names.sort()).toMatchInline(` Array [ - "opbeans-go", + "elasticsearch", + "opbeans-java", "postgresql", + "redis", ] `); }); @@ -342,7 +369,7 @@ export default function ApiTest({ getService }: FtrProviderContext) { expectSnapshot(serviceNames.sort()).toMatchInline(` Array [ - "opbeans-go", + "opbeans-java", ] `); }); @@ -356,32 +383,89 @@ export default function ApiTest({ getService }: FtrProviderContext) { expectSnapshot(latencyValues).toMatchInline(` Array [ Object { - "latency": 38506.4285714286, - "name": "opbeans-go", + "latency": 2568.40816326531, + "name": "elasticsearch", + }, + Object { + "latency": 25593.875, + "name": "opbeans-java", }, Object { - "latency": 5908.77272727273, + "latency": 28885.3293963255, "name": "postgresql", }, + Object { + "latency": 1043.99015586546, + "name": "redis", + }, ] `); }); it('returns the right throughput values', () => { const throughputValues = sortBy( - response.body.map((item) => ({ name: item.name, latency: item.throughput.value })), + response.body.map((item) => ({ name: item.name, throughput: item.throughput.value })), 'name' ); expectSnapshot(throughputValues).toMatchInline(` Array [ Object { - "latency": 0.466666666666667, - "name": "opbeans-go", + "name": "elasticsearch", + "throughput": 13.0666666666667, + }, + Object { + "name": "opbeans-java", + "throughput": 0.533333333333333, }, Object { - "latency": 3.66666666666667, "name": "postgresql", + "throughput": 50.8, + }, + Object { + "name": "redis", + "throughput": 40.6333333333333, + }, + ] + `); + }); + + it('returns the right impact values', () => { + const impactValues = sortBy( + response.body.map((item) => ({ + name: item.name, + impact: item.impact, + latency: item.latency.value, + throughput: item.throughput.value, + })), + 'name' + ); + + expectSnapshot(impactValues).toMatchInline(` + Array [ + Object { + "impact": 1.36961744704522, + "latency": 2568.40816326531, + "name": "elasticsearch", + "throughput": 13.0666666666667, + }, + Object { + "impact": 0, + "latency": 25593.875, + "name": "opbeans-java", + "throughput": 0.533333333333333, + }, + Object { + "impact": 100, + "latency": 28885.3293963255, + "name": "postgresql", + "throughput": 50.8, + }, + Object { + "impact": 1.97910470896139, + "latency": 1043.99015586546, + "name": "redis", + "throughput": 40.6333333333333, }, ] `); diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts b/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts index a8a5f2abd072b..f97309bafde37 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/create_rules.ts @@ -25,6 +25,7 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); describe('create_rules', () => { describe('validation errors', () => { @@ -46,11 +47,13 @@ export default ({ getService }: FtrProviderContext) => { describe('creating rules', () => { beforeEach(async () => { await createSignalsIndex(supertest); + await esArchiver.load('auditbeat/hosts'); }); afterEach(async () => { await deleteSignalsIndex(supertest); await deleteAllAlerts(supertest); + await esArchiver.unload('auditbeat/hosts'); }); it('should create a single rule with a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/create_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/basic/tests/create_rules_bulk.ts index 73be4154db1eb..30e2e22c0547c 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/create_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/create_rules_bulk.ts @@ -23,6 +23,7 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); describe('create_rules_bulk', () => { describe('validation errors', () => { @@ -49,11 +50,13 @@ export default ({ getService }: FtrProviderContext): void => { describe('creating rules in bulk', () => { beforeEach(async () => { await createSignalsIndex(supertest); + await esArchiver.load('auditbeat/hosts'); }); afterEach(async () => { await deleteSignalsIndex(supertest); await deleteAllAlerts(supertest); + await esArchiver.unload('auditbeat/hosts'); }); it('should create a single rule with a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts b/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts index 785b74d334276..7457aa37be5df 100644 --- a/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts +++ b/x-pack/test/detection_engine_api_integration/basic/tests/find_statuses.ts @@ -21,17 +21,20 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); const es = getService('es'); describe('find_statuses', () => { beforeEach(async () => { await createSignalsIndex(supertest); + await esArchiver.load('auditbeat/hosts'); }); afterEach(async () => { await deleteSignalsIndex(supertest); await deleteAllAlerts(supertest); await deleteAllRulesStatuses(es); + await esArchiver.unload('auditbeat/hosts'); }); it('should return an empty find statuses body correctly if no statuses are loaded', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/roles_users_utils/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/roles_users_utils/index.ts index 5098ff157b116..ec3d1dd1b0c56 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/roles_users_utils/index.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/roles_users_utils/index.ts @@ -4,63 +4,62 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as t1AnalystUser from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_user.json'; -import * as t2AnalystUser from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_user.json'; -import * as hunterUser from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_user.json'; -import * as ruleAuthorUser from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_user.json'; -import * as socManagerUser from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_user.json'; -import * as platformEngineerUser from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_user.json'; -import * as detectionsAdminUser from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_user.json'; - -import * as t1AnalystRole from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/detections_role.json'; -import * as t2AnalystRole from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t2_analyst/detections_role.json'; -import * as hunterRole from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/detections_role.json'; -import * as ruleAuthorRole from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/rule_author/detections_role.json'; -import * as socManagerRole from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/soc_manager/detections_role.json'; -import * as platformEngineerRole from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/platform_engineer/detections_role.json'; -import * as detectionsAdminRole from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users/detections_admin/detections_role.json'; +import { assertUnreachable } from '../../../../plugins/security_solution/common/utility_types'; +import { + t1AnalystUser, + t2AnalystUser, + hunterUser, + ruleAuthorUser, + socManagerUser, + platformEngineerUser, + detectionsAdminUser, + readerUser, + t1AnalystRole, + t2AnalystRole, + hunterRole, + ruleAuthorRole, + socManagerRole, + platformEngineerRole, + detectionsAdminRole, + readerRole, +} from '../../../../plugins/security_solution/server/lib/detection_engine/scripts/roles_users'; import { ROLES } from '../../../../plugins/security_solution/common/test'; import { FtrProviderContext } from '../../common/ftr_provider_context'; export const createUserAndRole = async ( securityService: ReturnType, - role: keyof typeof ROLES -) => { + role: ROLES +): Promise => { switch (role) { case ROLES.detections_admin: - await postRoleAndUser( + return postRoleAndUser( ROLES.detections_admin, detectionsAdminRole, detectionsAdminUser, securityService ); - break; case ROLES.t1_analyst: - await postRoleAndUser(ROLES.t1_analyst, t1AnalystRole, t1AnalystUser, securityService); - break; + return postRoleAndUser(ROLES.t1_analyst, t1AnalystRole, t1AnalystUser, securityService); case ROLES.t2_analyst: - await postRoleAndUser(ROLES.t2_analyst, t2AnalystRole, t2AnalystUser, securityService); - break; + return postRoleAndUser(ROLES.t2_analyst, t2AnalystRole, t2AnalystUser, securityService); case ROLES.hunter: - await postRoleAndUser(ROLES.hunter, hunterRole, hunterUser, securityService); - break; + return postRoleAndUser(ROLES.hunter, hunterRole, hunterUser, securityService); case ROLES.rule_author: - await postRoleAndUser(ROLES.rule_author, ruleAuthorRole, ruleAuthorUser, securityService); - break; + return postRoleAndUser(ROLES.rule_author, ruleAuthorRole, ruleAuthorUser, securityService); case ROLES.soc_manager: - await postRoleAndUser(ROLES.soc_manager, socManagerRole, socManagerUser, securityService); - break; + return postRoleAndUser(ROLES.soc_manager, socManagerRole, socManagerUser, securityService); case ROLES.platform_engineer: - await postRoleAndUser( + return postRoleAndUser( ROLES.platform_engineer, platformEngineerRole, platformEngineerUser, securityService ); - break; + case ROLES.reader: + return postRoleAndUser(ROLES.reader, readerRole, readerUser, securityService); default: - break; + return assertUnreachable(role); } }; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_actions.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_actions.ts index a2c3fc6c6c288..ffb418be5dc7c 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_actions.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/add_actions.ts @@ -24,16 +24,19 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext) => { const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); describe('add_actions', () => { describe('adding actions', () => { beforeEach(async () => { + await esArchiver.load('auditbeat/hosts'); await createSignalsIndex(supertest); }); afterEach(async () => { await deleteSignalsIndex(supertest); await deleteAllAlerts(supertest); + await esArchiver.unload('auditbeat/hosts'); }); it('should be able to create a new webhook action and attach it to a rule', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_exceptions.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_exceptions.ts index b90bea66be11f..6cfd3b9e9e1e3 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_exceptions.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_exceptions.ts @@ -45,12 +45,14 @@ export default ({ getService }: FtrProviderContext) => { describe('creating rules with exceptions', () => { beforeEach(async () => { await createSignalsIndex(supertest); + await esArchiver.load('auditbeat/hosts'); }); afterEach(async () => { await deleteSignalsIndex(supertest); await deleteAllAlerts(supertest); await deleteAllExceptions(es); + await esArchiver.unload('auditbeat/hosts'); }); it('should create a single rule with a rule_id and add an exception list to the rule', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_index.ts new file mode 100644 index 0000000000000..345d71cb4ee04 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_index.ts @@ -0,0 +1,426 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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 { + DEFAULT_SIGNALS_INDEX, + DETECTION_ENGINE_INDEX_URL, +} from '../../../../plugins/security_solution/common/constants'; + +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { deleteSignalsIndex } from '../../utils'; +import { ROLES } from '../../../../plugins/security_solution/common/test'; +import { createUserAndRole } from '../roles_users_utils'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const security = getService('security'); + + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/90229 + describe.skip('create_index', () => { + afterEach(async () => { + await deleteSignalsIndex(supertest); + }); + + describe('elastic admin', () => { + it('should return a 404 when the signal index has never been created', async () => { + const { body } = await supertest.get(DETECTION_ENGINE_INDEX_URL).send().expect(404); + expect(body).to.eql({ message: 'index for this space does not exist', status_code: 404 }); + }); + + it('should be able to create a signal index when it has not been created yet', async () => { + const { body } = await supertest + .post(DETECTION_ENGINE_INDEX_URL) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + expect(body).to.eql({ acknowledged: true }); + }); + + it('should be able to create a signal index two times in a row as the REST call is idempotent', async () => { + await supertest.post(DETECTION_ENGINE_INDEX_URL).set('kbn-xsrf', 'true').send().expect(200); + const { body } = await supertest + .post(DETECTION_ENGINE_INDEX_URL) + .set('kbn-xsrf', 'true') + .send() + .expect(200); + expect(body).to.eql({ acknowledged: true }); + }); + + it('should be able to read the index name and status as not being outdated', async () => { + await supertest.post(DETECTION_ENGINE_INDEX_URL).set('kbn-xsrf', 'true').send().expect(200); + + const { body } = await supertest.get(DETECTION_ENGINE_INDEX_URL).send().expect(200); + expect(body).to.eql({ + index_mapping_outdated: false, + name: `${DEFAULT_SIGNALS_INDEX}-default`, + }); + }); + }); + + describe('t1_analyst', () => { + const role = ROLES.t1_analyst; + beforeEach(async () => { + await createUserAndRole(security, role); + }); + + it('should return a 404 when the signal index has never been created', async () => { + const { body } = await supertestWithoutAuth + .get(DETECTION_ENGINE_INDEX_URL) + .auth(role, 'changeme') + .send() + .expect(404); + expect(body).to.eql({ message: 'index for this space does not exist', status_code: 404 }); + }); + + it('should NOT be able to create a signal index when it has not been created yet. Should return a 403 and error that the user is unauthorized', async () => { + const { body } = await supertestWithoutAuth + .post(DETECTION_ENGINE_INDEX_URL) + .set('kbn-xsrf', 'true') + .auth(role, 'changeme') + .send() + .expect(403); + expect(body).to.eql({ + message: + 'security_exception: action [cluster:admin/ilm/get] is unauthorized for user [t1_analyst], this action is granted by the privileges [read_ilm,manage_ilm,manage,all]', + status_code: 403, + }); + }); + + it('should be able to read the index name and status as not being outdated', async () => { + // create the index using super user since this user cannot create the index + await supertest.post(DETECTION_ENGINE_INDEX_URL).set('kbn-xsrf', 'true').send().expect(200); + + const { body } = await supertestWithoutAuth + .get(DETECTION_ENGINE_INDEX_URL) + .auth(role, 'changeme') + .send() + .expect(200); + expect(body).to.eql({ + index_mapping_outdated: null, + name: `${DEFAULT_SIGNALS_INDEX}-default`, + }); + }); + }); + + describe('t2_analyst', () => { + const role = ROLES.t2_analyst; + beforeEach(async () => { + await createUserAndRole(security, role); + }); + + it('should return a 404 when the signal index has never been created', async () => { + const { body } = await supertestWithoutAuth + .get(DETECTION_ENGINE_INDEX_URL) + .auth(role, 'changeme') + .send() + .expect(404); + expect(body).to.eql({ message: 'index for this space does not exist', status_code: 404 }); + }); + + it('should NOT be able to create a signal index when it has not been created yet. Should return a 403 and error that the user is unauthorized', async () => { + const { body } = await supertestWithoutAuth + .post(DETECTION_ENGINE_INDEX_URL) + .set('kbn-xsrf', 'true') + .auth(role, 'changeme') + .send() + .expect(403); + expect(body).to.eql({ + message: + 'security_exception: action [cluster:admin/ilm/get] is unauthorized for user [t2_analyst], this action is granted by the privileges [read_ilm,manage_ilm,manage,all]', + status_code: 403, + }); + }); + + it('should be able to read the index name and status as not being outdated', async () => { + // create the index using super user since this user cannot create an index + await supertest.post(DETECTION_ENGINE_INDEX_URL).set('kbn-xsrf', 'true').send().expect(200); + + const { body } = await supertestWithoutAuth + .get(DETECTION_ENGINE_INDEX_URL) + .auth(role, 'changeme') + .send() + .expect(200); + expect(body).to.eql({ + index_mapping_outdated: null, + name: `${DEFAULT_SIGNALS_INDEX}-default`, + }); + }); + }); + + describe('detections_admin', () => { + const role = ROLES.detections_admin; + beforeEach(async () => { + await createUserAndRole(security, role); + }); + + it('should return a 404 when the signal index has never been created', async () => { + const { body } = await supertestWithoutAuth + .get(DETECTION_ENGINE_INDEX_URL) + .auth(role, 'changeme') + .send() + .expect(404); + expect(body).to.eql({ message: 'index for this space does not exist', status_code: 404 }); + }); + + it('should be able to create a signal index when it has not been created yet', async () => { + const { body } = await supertestWithoutAuth + .post(DETECTION_ENGINE_INDEX_URL) + .set('kbn-xsrf', 'true') + .auth(role, 'changeme') + .send() + .expect(200); + expect(body).to.eql({ acknowledged: true }); + }); + + it('should be able to read the index name and status as not being outdated', async () => { + await supertestWithoutAuth + .post(DETECTION_ENGINE_INDEX_URL) + .set('kbn-xsrf', 'true') + .auth(role, 'changeme') + .send() + .expect(200); + + const { body } = await supertestWithoutAuth + .get(DETECTION_ENGINE_INDEX_URL) + .auth(role, 'changeme') + .send() + .expect(200); + expect(body).to.eql({ + index_mapping_outdated: false, + name: `${DEFAULT_SIGNALS_INDEX}-default`, + }); + }); + }); + + describe('soc_manager', () => { + const role = ROLES.soc_manager; + beforeEach(async () => { + await createUserAndRole(security, role); + }); + + it('should return a 404 when the signal index has never been created', async () => { + const { body } = await supertestWithoutAuth + .get(DETECTION_ENGINE_INDEX_URL) + .auth(role, 'changeme') + .send() + .expect(404); + expect(body).to.eql({ message: 'index for this space does not exist', status_code: 404 }); + }); + + it('should NOT be able to create a signal index when it has not been created yet. Should return a 403 and error that the user is unauthorized', async () => { + const { body } = await supertestWithoutAuth + .post(DETECTION_ENGINE_INDEX_URL) + .set('kbn-xsrf', 'true') + .auth(role, 'changeme') + .send() + .expect(403); + expect(body).to.eql({ + message: + 'security_exception: action [cluster:admin/ilm/get] is unauthorized for user [soc_manager], this action is granted by the privileges [read_ilm,manage_ilm,manage,all]', + status_code: 403, + }); + }); + + it('should be able to read the index name and status as not being outdated', async () => { + // create the index using super user since this user cannot create an index + await supertest.post(DETECTION_ENGINE_INDEX_URL).set('kbn-xsrf', 'true').send().expect(200); + + const { body } = await supertestWithoutAuth + .get(DETECTION_ENGINE_INDEX_URL) + .auth(role, 'changeme') + .send() + .expect(200); + expect(body).to.eql({ + index_mapping_outdated: false, + name: `${DEFAULT_SIGNALS_INDEX}-default`, + }); + }); + }); + + describe('hunter', () => { + const role = ROLES.hunter; + beforeEach(async () => { + await createUserAndRole(security, role); + }); + + it('should return a 404 when the signal index has never been created', async () => { + const { body } = await supertestWithoutAuth + .get(DETECTION_ENGINE_INDEX_URL) + .auth(role, 'changeme') + .send() + .expect(404); + expect(body).to.eql({ message: 'index for this space does not exist', status_code: 404 }); + }); + + it('should NOT be able to create a signal index when it has not been created yet. Should return a 403 and error that the user is unauthorized', async () => { + const { body } = await supertestWithoutAuth + .post(DETECTION_ENGINE_INDEX_URL) + .set('kbn-xsrf', 'true') + .auth(role, 'changeme') + .send() + .expect(403); + expect(body).to.eql({ + message: + 'security_exception: action [cluster:admin/ilm/get] is unauthorized for user [hunter], this action is granted by the privileges [read_ilm,manage_ilm,manage,all]', + status_code: 403, + }); + }); + + it('should be able to read the index name and status as not being outdated', async () => { + // create the index using super user since this user cannot create an index + await supertest.post(DETECTION_ENGINE_INDEX_URL).set('kbn-xsrf', 'true').send().expect(200); + + const { body } = await supertestWithoutAuth + .get(DETECTION_ENGINE_INDEX_URL) + .auth(role, 'changeme') + .send() + .expect(200); + expect(body).to.eql({ + index_mapping_outdated: null, + name: `${DEFAULT_SIGNALS_INDEX}-default`, + }); + }); + }); + + describe('platform_engineer', () => { + const role = ROLES.platform_engineer; + beforeEach(async () => { + await createUserAndRole(security, role); + }); + + it('should return a 404 when the signal index has never been created', async () => { + const { body } = await supertestWithoutAuth + .get(DETECTION_ENGINE_INDEX_URL) + .auth(role, 'changeme') + .send() + .expect(404); + expect(body).to.eql({ message: 'index for this space does not exist', status_code: 404 }); + }); + + it('should be able to create a signal index when it has not been created yet', async () => { + const { body } = await supertestWithoutAuth + .post(DETECTION_ENGINE_INDEX_URL) + .set('kbn-xsrf', 'true') + .auth(role, 'changeme') + .send() + .expect(200); + expect(body).to.eql({ acknowledged: true }); + }); + + it('should be able to read the index name and status as not being outdated', async () => { + await supertestWithoutAuth + .post(DETECTION_ENGINE_INDEX_URL) + .set('kbn-xsrf', 'true') + .auth(role, 'changeme') + .send() + .expect(200); + + const { body } = await supertestWithoutAuth + .get(DETECTION_ENGINE_INDEX_URL) + .auth(role, 'changeme') + .send() + .expect(200); + expect(body).to.eql({ + index_mapping_outdated: false, + name: `${DEFAULT_SIGNALS_INDEX}-default`, + }); + }); + }); + + describe('reader', () => { + const role = ROLES.reader; + beforeEach(async () => { + await createUserAndRole(security, role); + }); + + it('should return a 404 when the signal index has never been created', async () => { + const { body } = await supertestWithoutAuth + .get(DETECTION_ENGINE_INDEX_URL) + .auth(role, 'changeme') + .send() + .expect(404); + expect(body).to.eql({ message: 'index for this space does not exist', status_code: 404 }); + }); + + it('should NOT be able to create a signal index when it has not been created yet. Should return a 401 unauthorized', async () => { + const { body } = await supertestWithoutAuth + .post(DETECTION_ENGINE_INDEX_URL) + .set('kbn-xsrf', 'true') + .auth(role, 'changeme') + .send() + .expect(403); + expect(body).to.eql({ + message: + 'security_exception: action [cluster:admin/ilm/get] is unauthorized for user [reader], this action is granted by the privileges [read_ilm,manage_ilm,manage,all]', + status_code: 403, + }); + }); + + it('should be able to read the index name and status as being outdated.', async () => { + // create the index using super user since this user cannot create the index + await supertest.post(DETECTION_ENGINE_INDEX_URL).set('kbn-xsrf', 'true').send().expect(200); + + const { body } = await supertestWithoutAuth + .get(DETECTION_ENGINE_INDEX_URL) + .auth(role, 'changeme') + .send() + .expect(200); + expect(body).to.eql({ + index_mapping_outdated: false, + name: `${DEFAULT_SIGNALS_INDEX}-default`, + }); + }); + }); + + describe('rule_author', () => { + const role = ROLES.rule_author; + beforeEach(async () => { + await createUserAndRole(security, role); + }); + + it('should return a 404 when the signal index has never been created', async () => { + const { body } = await supertestWithoutAuth + .get(DETECTION_ENGINE_INDEX_URL) + .auth(role, 'changeme') + .send() + .expect(404); + expect(body).to.eql({ message: 'index for this space does not exist', status_code: 404 }); + }); + + it('should NOT be able to create a signal index when it has not been created yet. Should return a 401 unauthorized', async () => { + const { body } = await supertestWithoutAuth + .post(DETECTION_ENGINE_INDEX_URL) + .set('kbn-xsrf', 'true') + .auth(role, 'changeme') + .send() + .expect(403); + expect(body).to.eql({ + message: + 'security_exception: action [cluster:admin/ilm/get] is unauthorized for user [rule_author], this action is granted by the privileges [read_ilm,manage_ilm,manage,all]', + status_code: 403, + }); + }); + + it('should be able to read the index name and status as being outdated.', async () => { + // create the index using super user since this user cannot create the index + await supertest.post(DETECTION_ENGINE_INDEX_URL).set('kbn-xsrf', 'true').send().expect(200); + + const { body } = await supertestWithoutAuth + .get(DETECTION_ENGINE_INDEX_URL) + .auth(role, 'changeme') + .send() + .expect(200); + expect(body).to.eql({ + index_mapping_outdated: false, + name: `${DEFAULT_SIGNALS_INDEX}-default`, + }); + }); + }); + }); +}; diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts index ba4a524f3b9b2..e416dcf57b32b 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules.ts @@ -55,11 +55,13 @@ export default ({ getService }: FtrProviderContext) => { describe('creating rules', () => { beforeEach(async () => { await createSignalsIndex(supertest); + await esArchiver.load('auditbeat/hosts'); }); afterEach(async () => { await deleteSignalsIndex(supertest); await deleteAllAlerts(supertest); + await esArchiver.unload('auditbeat/hosts'); }); it('should create a single rule with a rule_id', async () => { @@ -111,6 +113,47 @@ export default ({ getService }: FtrProviderContext) => { expect(statusBody[body.id].current_status.status).to.eql('succeeded'); }); + it('should create a single rule with a rule_id and an index pattern that does not match anything available and fail the rule', async () => { + const simpleRule = getRuleForSignalTesting(['does-not-exist-*']); + const { body } = await supertest + .post(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .send(simpleRule) + .expect(200); + + await waitForRuleSuccessOrStatus(supertest, body.id, 'failed'); + + const { body: statusBody } = await supertest + .post(DETECTION_ENGINE_RULES_STATUS_URL) + .set('kbn-xsrf', 'true') + .send({ ids: [body.id] }) + .expect(200); + + expect(statusBody[body.id].current_status.status).to.eql('failed'); + expect(statusBody[body.id].current_status.last_failure_message).to.eql( + 'The following index patterns did not match any indices: ["does-not-exist-*"]' + ); + }); + + it('should create a single rule with a rule_id and an index pattern that does not match anything and an index pattern that does and the rule should be successful', async () => { + const simpleRule = getRuleForSignalTesting(['does-not-exist-*', 'auditbeat-*']); + const { body } = await supertest + .post(DETECTION_ENGINE_RULES_URL) + .set('kbn-xsrf', 'true') + .send(simpleRule) + .expect(200); + + await waitForRuleSuccessOrStatus(supertest, body.id, 'succeeded'); + + const { body: statusBody } = await supertest + .post(DETECTION_ENGINE_RULES_STATUS_URL) + .set('kbn-xsrf', 'true') + .send({ ids: [body.id] }) + .expect(200); + + expect(statusBody[body.id].current_status.status).to.eql('succeeded'); + }); + it('should create a single rule without an input index', async () => { const rule: CreateRulesSchema = { name: 'Simple Rule Query', diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts index 2577c6b163604..99854442b9c7a 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_rules_bulk.ts @@ -28,6 +28,7 @@ import { // eslint-disable-next-line import/no-default-export export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); describe('create_rules_bulk', () => { describe('validation errors', () => { @@ -54,11 +55,13 @@ export default ({ getService }: FtrProviderContext): void => { describe('creating rules in bulk', () => { beforeEach(async () => { await createSignalsIndex(supertest); + await esArchiver.load('auditbeat/hosts'); }); afterEach(async () => { await deleteSignalsIndex(supertest); await deleteAllAlerts(supertest); + await esArchiver.unload('auditbeat/hosts'); }); it('should create a single rule with a rule_id', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_statuses.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_statuses.ts index dfec35e4a64f3..d31b076ab12ea 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_statuses.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/find_statuses.ts @@ -22,16 +22,19 @@ import { export default ({ getService }: FtrProviderContext): void => { const supertest = getService('supertest'); const es = getService('es'); + const esArchiver = getService('esArchiver'); describe('find_statuses', () => { beforeEach(async () => { await createSignalsIndex(supertest); + await esArchiver.load('auditbeat/hosts'); }); afterEach(async () => { await deleteSignalsIndex(supertest); await deleteAllAlerts(supertest); await deleteAllRulesStatuses(es); + await esArchiver.unload('auditbeat/hosts'); }); it('should return an empty find statuses body correctly if no statuses are loaded', async () => { diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts index 44e033e96c89b..53008678a78e8 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts @@ -16,6 +16,7 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./add_prepackaged_rules')); loadTestFile(require.resolve('./create_rules')); loadTestFile(require.resolve('./create_rules_bulk')); + loadTestFile(require.resolve('./create_index')); loadTestFile(require.resolve('./create_threat_matching')); loadTestFile(require.resolve('./create_exceptions')); loadTestFile(require.resolve('./delete_rules')); @@ -31,6 +32,7 @@ export default ({ loadTestFile }: FtrProviderContext): void => { loadTestFile(require.resolve('./update_rules_bulk')); loadTestFile(require.resolve('./patch_rules_bulk')); loadTestFile(require.resolve('./patch_rules')); + loadTestFile(require.resolve('./read_privileges')); loadTestFile(require.resolve('./query_signals')); loadTestFile(require.resolve('./open_close_signals')); loadTestFile(require.resolve('./get_signals_migration_status')); diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/read_privileges.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/read_privileges.ts new file mode 100644 index 0000000000000..35519cdee2d65 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/read_privileges.ts @@ -0,0 +1,583 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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 { DETECTION_ENGINE_PRIVILEGES_URL } from '../../../../plugins/security_solution/common/constants'; + +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { ROLES } from '../../../../plugins/security_solution/common/test'; + +// eslint-disable-next-line import/no-default-export +export default ({ getService }: FtrProviderContext) => { + const supertest = getService('supertest'); + const supertestWithoutAuth = getService('supertestWithoutAuth'); + + // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/90229 + describe.skip('read_privileges', () => { + it('should return expected privileges for elastic admin', async () => { + const { body } = await supertest.get(DETECTION_ENGINE_PRIVILEGES_URL).send().expect(200); + expect(body).to.eql({ + username: 'elastic', + has_all_requested: true, + cluster: { + monitor_ml: true, + manage_ccr: true, + manage_index_templates: true, + monitor_watcher: true, + monitor_transform: true, + read_ilm: true, + manage_api_key: true, + manage_security: true, + manage_own_api_key: true, + manage_saml: true, + all: true, + manage_ilm: true, + manage_ingest_pipelines: true, + read_ccr: true, + manage_rollup: true, + monitor: true, + manage_watcher: true, + manage: true, + manage_transform: true, + manage_token: true, + manage_ml: true, + manage_pipeline: true, + monitor_rollup: true, + transport_client: true, + create_snapshot: true, + }, + index: { + '.siem-signals-default': { + all: true, + manage_ilm: true, + read: true, + create_index: true, + read_cross_cluster: true, + index: true, + monitor: true, + delete: true, + manage: true, + delete_index: true, + create_doc: true, + view_index_metadata: true, + create: true, + manage_follow_index: true, + manage_leader_index: true, + maintenance: true, + write: true, + }, + }, + application: {}, + is_authenticated: true, + has_encryption_key: true, + }); + }); + + it('should return expected privileges for a "reader" user', async () => { + const { body } = await supertestWithoutAuth + .get(DETECTION_ENGINE_PRIVILEGES_URL) + .auth(ROLES.reader, 'changeme') + .send() + .expect(200); + expect(body).to.eql({ + username: 'reader', + has_all_requested: false, + cluster: { + monitor_ml: false, + manage_ccr: false, + manage_index_templates: false, + monitor_watcher: false, + monitor_transform: false, + read_ilm: false, + manage_api_key: false, + manage_security: false, + manage_own_api_key: false, + manage_saml: false, + all: false, + manage_ilm: false, + manage_ingest_pipelines: false, + read_ccr: false, + manage_rollup: false, + monitor: false, + manage_watcher: false, + manage: false, + manage_transform: false, + manage_token: false, + manage_ml: false, + manage_pipeline: false, + monitor_rollup: false, + transport_client: false, + create_snapshot: false, + }, + index: { + '.siem-signals-default': { + all: false, + manage_ilm: false, + read: true, + create_index: false, + read_cross_cluster: false, + index: false, + monitor: false, + delete: false, + manage: false, + delete_index: false, + create_doc: false, + view_index_metadata: true, + create: false, + manage_follow_index: false, + manage_leader_index: false, + maintenance: true, + write: false, + }, + }, + application: {}, + is_authenticated: true, + has_encryption_key: true, + }); + }); + + it('should return expected privileges for a "t1_analyst" user', async () => { + const { body } = await supertestWithoutAuth + .get(DETECTION_ENGINE_PRIVILEGES_URL) + .auth(ROLES.t1_analyst, 'changeme') + .send() + .expect(200); + expect(body).to.eql({ + username: 't1_analyst', + has_all_requested: false, + cluster: { + monitor_ml: false, + manage_ccr: false, + manage_index_templates: false, + monitor_watcher: false, + monitor_transform: false, + read_ilm: false, + manage_api_key: false, + manage_security: false, + manage_own_api_key: false, + manage_saml: false, + all: false, + manage_ilm: false, + manage_ingest_pipelines: false, + read_ccr: false, + manage_rollup: false, + monitor: false, + manage_watcher: false, + manage: false, + manage_transform: false, + manage_token: false, + manage_ml: false, + manage_pipeline: false, + monitor_rollup: false, + transport_client: false, + create_snapshot: false, + }, + index: { + '.siem-signals-default': { + all: false, + manage_ilm: false, + read: true, + create_index: false, + read_cross_cluster: false, + index: true, + monitor: false, + delete: true, + manage: false, + delete_index: false, + create_doc: true, + view_index_metadata: false, + create: true, + manage_follow_index: false, + manage_leader_index: false, + maintenance: true, + write: true, + }, + }, + application: {}, + is_authenticated: true, + has_encryption_key: true, + }); + }); + + it('should return expected privileges for a "t2_analyst" user', async () => { + const { body } = await supertestWithoutAuth + .get(DETECTION_ENGINE_PRIVILEGES_URL) + .auth(ROLES.t2_analyst, 'changeme') + .send() + .expect(200); + expect(body).to.eql({ + username: 't2_analyst', + has_all_requested: false, + cluster: { + monitor_ml: false, + manage_ccr: false, + manage_index_templates: false, + monitor_watcher: false, + monitor_transform: false, + read_ilm: false, + manage_api_key: false, + manage_security: false, + manage_own_api_key: false, + manage_saml: false, + all: false, + manage_ilm: false, + manage_ingest_pipelines: false, + read_ccr: false, + manage_rollup: false, + monitor: false, + manage_watcher: false, + manage: false, + manage_transform: false, + manage_token: false, + manage_ml: false, + manage_pipeline: false, + monitor_rollup: false, + transport_client: false, + create_snapshot: false, + }, + index: { + '.siem-signals-default': { + all: false, + manage_ilm: false, + read: true, + create_index: false, + read_cross_cluster: false, + index: true, + monitor: false, + delete: true, + manage: false, + delete_index: false, + create_doc: true, + view_index_metadata: false, + create: true, + manage_follow_index: false, + manage_leader_index: false, + maintenance: true, + write: true, + }, + }, + application: {}, + is_authenticated: true, + has_encryption_key: true, + }); + }); + + it('should return expected privileges for a "hunter" user', async () => { + const { body } = await supertestWithoutAuth + .get(DETECTION_ENGINE_PRIVILEGES_URL) + .auth(ROLES.hunter, 'changeme') + .send() + .expect(200); + expect(body).to.eql({ + username: 'hunter', + has_all_requested: false, + cluster: { + monitor_ml: false, + manage_ccr: false, + manage_index_templates: false, + monitor_watcher: false, + monitor_transform: false, + read_ilm: false, + manage_api_key: false, + manage_security: false, + manage_own_api_key: false, + manage_saml: false, + all: false, + manage_ilm: false, + manage_ingest_pipelines: false, + read_ccr: false, + manage_rollup: false, + monitor: false, + manage_watcher: false, + manage: false, + manage_transform: false, + manage_token: false, + manage_ml: false, + manage_pipeline: false, + monitor_rollup: false, + transport_client: false, + create_snapshot: false, + }, + index: { + '.siem-signals-default': { + all: false, + manage_ilm: false, + read: true, + create_index: false, + read_cross_cluster: false, + index: true, + monitor: false, + delete: true, + manage: false, + delete_index: false, + create_doc: true, + view_index_metadata: false, + create: true, + manage_follow_index: false, + manage_leader_index: false, + maintenance: false, + write: true, + }, + }, + application: {}, + is_authenticated: true, + has_encryption_key: true, + }); + }); + + it('should return expected privileges for a "rule_author" user', async () => { + const { body } = await supertestWithoutAuth + .get(DETECTION_ENGINE_PRIVILEGES_URL) + .auth(ROLES.rule_author, 'changeme') + .send() + .expect(200); + expect(body).to.eql({ + username: 'rule_author', + has_all_requested: false, + cluster: { + monitor_ml: false, + manage_ccr: false, + manage_index_templates: false, + monitor_watcher: false, + monitor_transform: false, + read_ilm: false, + manage_api_key: false, + manage_security: false, + manage_own_api_key: false, + manage_saml: false, + all: false, + manage_ilm: false, + manage_ingest_pipelines: false, + read_ccr: false, + manage_rollup: false, + monitor: false, + manage_watcher: false, + manage: false, + manage_transform: false, + manage_token: false, + manage_ml: false, + manage_pipeline: false, + monitor_rollup: false, + transport_client: false, + create_snapshot: false, + }, + index: { + '.siem-signals-default': { + all: false, + manage_ilm: false, + read: true, + create_index: false, + read_cross_cluster: false, + index: true, + monitor: false, + delete: true, + manage: false, + delete_index: false, + create_doc: true, + view_index_metadata: true, + create: true, + manage_follow_index: false, + manage_leader_index: false, + maintenance: true, + write: true, + }, + }, + application: {}, + is_authenticated: true, + has_encryption_key: true, + }); + }); + + it('should return expected privileges for a "soc_manager" user', async () => { + const { body } = await supertestWithoutAuth + .get(DETECTION_ENGINE_PRIVILEGES_URL) + .auth(ROLES.soc_manager, 'changeme') + .send() + .expect(200); + expect(body).to.eql({ + username: 'soc_manager', + has_all_requested: false, + cluster: { + monitor_ml: false, + manage_ccr: false, + manage_index_templates: false, + monitor_watcher: false, + monitor_transform: false, + read_ilm: false, + manage_api_key: false, + manage_security: false, + manage_own_api_key: false, + manage_saml: false, + all: false, + manage_ilm: false, + manage_ingest_pipelines: false, + read_ccr: false, + manage_rollup: false, + monitor: false, + manage_watcher: false, + manage: false, + manage_transform: false, + manage_token: false, + manage_ml: false, + manage_pipeline: false, + monitor_rollup: false, + transport_client: false, + create_snapshot: false, + }, + index: { + '.siem-signals-default': { + all: false, + manage_ilm: true, + read: true, + create_index: true, + read_cross_cluster: false, + index: true, + monitor: true, + delete: true, + manage: true, + delete_index: true, + create_doc: true, + view_index_metadata: true, + create: true, + manage_follow_index: true, + manage_leader_index: true, + maintenance: true, + write: true, + }, + }, + application: {}, + is_authenticated: true, + has_encryption_key: true, + }); + }); + + it('should return expected privileges for a "platform_engineer" user', async () => { + const { body } = await supertestWithoutAuth + .get(DETECTION_ENGINE_PRIVILEGES_URL) + .auth(ROLES.platform_engineer, 'changeme') + .send() + .expect(200); + expect(body).to.eql({ + username: 'platform_engineer', + has_all_requested: false, + cluster: { + monitor_ml: true, + manage_ccr: false, + manage_index_templates: true, + monitor_watcher: true, + monitor_transform: true, + read_ilm: true, + manage_api_key: false, + manage_security: false, + manage_own_api_key: false, + manage_saml: false, + all: false, + manage_ilm: true, + manage_ingest_pipelines: true, + read_ccr: false, + manage_rollup: true, + monitor: true, + manage_watcher: true, + manage: true, + manage_transform: true, + manage_token: false, + manage_ml: true, + manage_pipeline: true, + monitor_rollup: true, + transport_client: true, + create_snapshot: true, + }, + index: { + '.siem-signals-default': { + all: true, + manage_ilm: true, + read: true, + create_index: true, + read_cross_cluster: true, + index: true, + monitor: true, + delete: true, + manage: true, + delete_index: true, + create_doc: true, + view_index_metadata: true, + create: true, + manage_follow_index: true, + manage_leader_index: true, + maintenance: true, + write: true, + }, + }, + application: {}, + is_authenticated: true, + has_encryption_key: true, + }); + }); + + it('should return expected privileges for a "detections_admin" user', async () => { + const { body } = await supertestWithoutAuth + .get(DETECTION_ENGINE_PRIVILEGES_URL) + .auth(ROLES.detections_admin, 'changeme') + .send() + .expect(200); + expect(body).to.eql({ + username: 'detections_admin', + has_all_requested: false, + cluster: { + monitor_ml: true, + manage_ccr: false, + manage_index_templates: true, + monitor_watcher: true, + monitor_transform: true, + read_ilm: true, + manage_api_key: false, + manage_security: false, + manage_own_api_key: false, + manage_saml: false, + all: false, + manage_ilm: true, + manage_ingest_pipelines: true, + read_ccr: false, + manage_rollup: true, + monitor: true, + manage_watcher: true, + manage: true, + manage_transform: true, + manage_token: false, + manage_ml: true, + manage_pipeline: true, + monitor_rollup: true, + transport_client: true, + create_snapshot: true, + }, + index: { + '.siem-signals-default': { + all: false, + manage_ilm: true, + read: true, + create_index: true, + read_cross_cluster: false, + index: true, + monitor: true, + delete: true, + manage: true, + delete_index: true, + create_doc: true, + view_index_metadata: true, + create: true, + manage_follow_index: true, + manage_leader_index: true, + maintenance: true, + write: true, + }, + }, + application: {}, + is_authenticated: true, + has_encryption_key: true, + }); + }); + }); +}; diff --git a/x-pack/test/fleet_api_integration/apis/data_streams/index.js b/x-pack/test/fleet_api_integration/apis/data_streams/index.js new file mode 100644 index 0000000000000..30c1351edc177 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/data_streams/index.js @@ -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 default function loadTests({ loadTestFile }) { + describe('Data Stream Endpoints', () => { + loadTestFile(require.resolve('./list')); + }); +} diff --git a/x-pack/test/fleet_api_integration/apis/data_streams/list.ts b/x-pack/test/fleet_api_integration/apis/data_streams/list.ts new file mode 100644 index 0000000000000..9a26b3ac73177 --- /dev/null +++ b/x-pack/test/fleet_api_integration/apis/data_streams/list.ts @@ -0,0 +1,171 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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 '../../../api_integration/ftr_provider_context'; +import { skipIfNoDockerRegistry } from '../../helpers'; + +export default function (providerContext: FtrProviderContext) { + const { getService } = providerContext; + const supertest = getService('supertest'); + const es = getService('es'); + const retry = getService('retry'); + const pkgName = 'datastreams'; + const pkgVersion = '0.1.0'; + const pkgKey = `${pkgName}-${pkgVersion}`; + const logsTemplateName = `logs-${pkgName}.test_logs`; + const metricsTemplateName = `metrics-${pkgName}.test_metrics`; + + const uninstallPackage = async (pkg: string) => { + await supertest.delete(`/api/fleet/epm/packages/${pkg}`).set('kbn-xsrf', 'xxxx'); + }; + + const installPackage = async (pkg: string) => { + return await supertest + .post(`/api/fleet/epm/packages/${pkg}`) + .set('kbn-xsrf', 'xxxx') + .send({ force: true }) + .expect(200); + }; + + const seedDataStreams = async () => { + await es.transport.request({ + method: 'POST', + path: `/${logsTemplateName}-default/_doc`, + body: { + '@timestamp': '2015-01-01', + logs_test_name: 'test', + data_stream: { + dataset: `${pkgName}.test_logs`, + namespace: 'default', + type: 'logs', + }, + }, + }); + await es.transport.request({ + method: 'POST', + path: `/${metricsTemplateName}-default/_doc`, + body: { + '@timestamp': '2015-01-01', + logs_test_name: 'test', + data_stream: { + dataset: `${pkgName}.test_metrics`, + namespace: 'default', + type: 'metrics', + }, + }, + }); + }; + + const getDataStreams = async () => { + return await supertest.get(`/api/fleet/data_streams`).set('kbn-xsrf', 'xxxx'); + }; + + describe('data_streams_list', async () => { + skipIfNoDockerRegistry(providerContext); + + beforeEach(async () => { + await installPackage(pkgKey); + }); + + afterEach(async () => { + await uninstallPackage(pkgKey); + try { + await es.transport.request({ + method: 'DELETE', + path: `/_data_stream/${logsTemplateName}-default`, + }); + await es.transport.request({ + method: 'DELETE', + path: `/_data_stream/${metricsTemplateName}-default`, + }); + } catch (e) { + // Silently swallow errors here as not all tests seed data streams + } + }); + + it("should return no data streams when there isn't any data yet", async function () { + const { body } = await getDataStreams(); + expect(body).to.eql({ data_streams: [] }); + }); + + it('should return correct data stream information', async function () { + await seedDataStreams(); + await retry.tryForTime(10000, async () => { + const { body } = await getDataStreams(); + return expect( + body.data_streams.map((dataStream: any) => { + // eslint-disable-next-line @typescript-eslint/naming-convention + const { index, size_in_bytes, ...rest } = dataStream; + return rest; + }) + ).to.eql([ + { + dataset: 'datastreams.test_logs', + namespace: 'default', + type: 'logs', + package: 'datastreams', + package_version: '0.1.0', + last_activity_ms: 1420070400000, + dashboards: [], + }, + { + dataset: 'datastreams.test_metrics', + namespace: 'default', + type: 'metrics', + package: 'datastreams', + package_version: '0.1.0', + last_activity_ms: 1420070400000, + dashboards: [], + }, + ]); + }); + }); + + it('should return correct number of data streams regardless of number of backing indices', async function () { + await seedDataStreams(); + await retry.tryForTime(10000, async () => { + const { body } = await getDataStreams(); + return expect(body.data_streams.length).to.eql(2); + }); + + // Rollover data streams to increase # of backing indices and seed the new write index + await es.transport.request({ + method: 'POST', + path: `/${logsTemplateName}-default/_rollover`, + }); + await es.transport.request({ + method: 'POST', + path: `/${metricsTemplateName}-default/_rollover`, + }); + await seedDataStreams(); + + // Wait until backing indices are created + await retry.tryForTime(10000, async () => { + const { body } = await es.transport.request({ + method: 'GET', + path: `/${logsTemplateName}-default,${metricsTemplateName}-default/_search`, + body: { + size: 0, + aggs: { + index: { + terms: { + field: '_index', + size: 100000, + }, + }, + }, + }, + }); + expect(body.aggregations.index.buckets.length).to.eql(4); + }); + + // Check that data streams still return correctly + const { body } = await getDataStreams(); + return expect(body.data_streams.length).to.eql(2); + }); + }); +} diff --git a/x-pack/test/fleet_api_integration/apis/index.js b/x-pack/test/fleet_api_integration/apis/index.js index f472599652224..061042c1fe7ad 100644 --- a/x-pack/test/fleet_api_integration/apis/index.js +++ b/x-pack/test/fleet_api_integration/apis/index.js @@ -9,8 +9,10 @@ export default function ({ loadTestFile }) { this.tags('ciGroup10'); // Fleet setup loadTestFile(require.resolve('./fleet_setup')); + // Agent setup loadTestFile(require.resolve('./agents_setup')); + // Agents loadTestFile(require.resolve('./agents/delete')); loadTestFile(require.resolve('./agents/list')); @@ -24,7 +26,7 @@ export default function ({ loadTestFile }) { loadTestFile(require.resolve('./agents/upgrade')); loadTestFile(require.resolve('./agents/reassign')); - // Enrollement API keys + // Enrollment API keys loadTestFile(require.resolve('./enrollment_api_keys/crud')); // EPM @@ -38,6 +40,9 @@ export default function ({ loadTestFile }) { // Agent policies loadTestFile(require.resolve('./agent_policy/index')); + // Data Streams + loadTestFile(require.resolve('./data_streams/index')); + // Settings loadTestFile(require.resolve('./settings/index')); }); diff --git a/x-pack/test/functional/apps/cross_cluster_replication/feature_controls/ccr_security.ts b/x-pack/test/functional/apps/cross_cluster_replication/feature_controls/ccr_security.ts index 4e234dd8e5501..6b4b9c61151ba 100644 --- a/x-pack/test/functional/apps/cross_cluster_replication/feature_controls/ccr_security.ts +++ b/x-pack/test/functional/apps/cross_cluster_replication/feature_controls/ccr_security.ts @@ -13,8 +13,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const appsMenu = getService('appsMenu'); const managementMenu = getService('managementMenu'); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/89180 - describe.skip('security', () => { + describe('security', () => { before(async () => { await esArchiver.load('empty_kibana'); await PageObjects.common.navigateToApp('home'); diff --git a/x-pack/test/functional/apps/dashboard/_async_dashboard.ts b/x-pack/test/functional/apps/dashboard/_async_dashboard.ts index fceccb4609bd7..8851c83dea1ff 100644 --- a/x-pack/test/functional/apps/dashboard/_async_dashboard.ts +++ b/x-pack/test/functional/apps/dashboard/_async_dashboard.ts @@ -12,7 +12,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const retry = getService('retry'); const browser = getService('browser'); const kibanaServer = getService('kibanaServer'); - const esArchiver = getService('esArchiver'); const log = getService('log'); const pieChart = getService('pieChart'); const find = getService('find'); @@ -30,7 +29,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { describe('sample data dashboard', function describeIndexTests() { before(async () => { - await esArchiver.emptyKibanaIndex(); await PageObjects.common.sleep(5000); await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', { useActualUrl: true, diff --git a/x-pack/test/functional/apps/dashboard/dashboard_lens_by_value.ts b/x-pack/test/functional/apps/dashboard/dashboard_lens_by_value.ts new file mode 100644 index 0000000000000..0a153aecec323 --- /dev/null +++ b/x-pack/test/functional/apps/dashboard/dashboard_lens_by_value.ts @@ -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 expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const PageObjects = getPageObjects(['common', 'dashboard', 'visualize', 'lens']); + + const find = getService('find'); + const esArchiver = getService('esArchiver'); + const dashboardPanelActions = getService('dashboardPanelActions'); + const dashboardVisualizations = getService('dashboardVisualizations'); + + describe('dashboard lens by value', 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(); + }); + + it('can add a lens panel by value', async () => { + await dashboardVisualizations.ensureNewVisualizationDialogIsShowing(); + await PageObjects.lens.createAndAddLensFromDashboard({}); + const newPanelCount = await PageObjects.dashboard.getPanelCount(); + expect(newPanelCount).to.eql(1); + }); + + it('edits to a by value lens panel are properly applied', async () => { + await PageObjects.dashboard.waitForRenderComplete(); + await dashboardPanelActions.openContextMenu(); + await dashboardPanelActions.clickEdit(); + await PageObjects.lens.switchToVisualization('donut'); + await PageObjects.lens.saveAndReturn(); + await PageObjects.dashboard.waitForRenderComplete(); + + const pieExists = await find.existsByCssSelector('.lnsPieExpression__container'); + expect(pieExists).to.be(true); + }); + + it('editing and saving a lens by value panel retains number of panels', async () => { + const originalPanelCount = await PageObjects.dashboard.getPanelCount(); + await PageObjects.dashboard.waitForRenderComplete(); + await dashboardPanelActions.openContextMenu(); + await dashboardPanelActions.clickEdit(); + await PageObjects.lens.switchToVisualization('treemap'); + await PageObjects.lens.saveAndReturn(); + await PageObjects.dashboard.waitForRenderComplete(); + const newPanelCount = await PageObjects.dashboard.getPanelCount(); + expect(newPanelCount).to.eql(originalPanelCount); + }); + + it('updates panel on dashboard when a by value panel is saved to library', async () => { + const newTitle = 'look out library, here I come!'; + const originalPanelCount = await PageObjects.dashboard.getPanelCount(); + await PageObjects.dashboard.waitForRenderComplete(); + await dashboardPanelActions.openContextMenu(); + await dashboardPanelActions.clickEdit(); + await PageObjects.lens.save(newTitle, false, true); + await PageObjects.dashboard.waitForRenderComplete(); + const newPanelCount = await PageObjects.dashboard.getPanelCount(); + expect(newPanelCount).to.eql(originalPanelCount); + const titles = await PageObjects.dashboard.getPanelTitles(); + expect(titles.indexOf(newTitle)).to.not.be(-1); + }); + }); +} diff --git a/x-pack/test/functional/apps/dashboard/index.ts b/x-pack/test/functional/apps/dashboard/index.ts index 1ba87f89762a1..d6c0c4394e24a 100644 --- a/x-pack/test/functional/apps/dashboard/index.ts +++ b/x-pack/test/functional/apps/dashboard/index.ts @@ -15,5 +15,6 @@ export default function ({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./drilldowns')); loadTestFile(require.resolve('./sync_colors')); loadTestFile(require.resolve('./_async_dashboard')); + loadTestFile(require.resolve('./dashboard_lens_by_value')); }); } 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 index bd35374643e9b..9408bee7dc868 100644 --- a/x-pack/test/functional/apps/dashboard_mode/dashboard_empty_screen.js +++ b/x-pack/test/functional/apps/dashboard_mode/dashboard_empty_screen.js @@ -7,7 +7,6 @@ import expect from '@kbn/expect'; export default function ({ getPageObjects, getService }) { - const log = getService('log'); const testSubjects = getService('testSubjects'); const esArchiver = getService('esArchiver'); const dashboardVisualizations = getService('dashboardVisualizations'); @@ -27,40 +26,12 @@ export default function ({ getPageObjects, getService }) { await PageObjects.dashboard.gotoDashboardLandingPage(); }); - async function createAndAddLens(title, saveAsNew = false, redirectToOrigin = true) { - 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: 'lnsXY_xDimensionPanel > lns-empty-dimension', - operation: 'date_histogram', - field: '@timestamp', - }); - - await PageObjects.lens.configureDimension({ - dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', - operation: 'avg', - field: 'bytes', - }); - - await PageObjects.lens.configureDimension({ - dimension: 'lnsXY_splitDimensionPanel > lns-empty-dimension', - operation: 'terms', - field: 'ip', - }); - await PageObjects.lens.save(title, saveAsNew, redirectToOrigin); - } - 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.lens.createAndAddLensFromDashboard({ title, redirectToOrigin: true }); await PageObjects.dashboard.waitForRenderComplete(); await testSubjects.exists(`embeddablePanelHeading-${title}`); }); @@ -118,7 +89,7 @@ export default function ({ getPageObjects, getService }) { await testSubjects.exists('dashboardAddNewPanelButton'); await testSubjects.click('dashboardAddNewPanelButton'); await dashboardVisualizations.ensureNewVisualizationDialogIsShowing(); - await createAndAddLens(title, false, false); + await PageObjects.lens.createAndAddLensFromDashboard({ title }); await PageObjects.lens.notLinkedToOriginatingApp(); await PageObjects.common.navigateToApp('dashboard'); }); diff --git a/x-pack/test/functional/apps/maps/embeddable/save_and_return.js b/x-pack/test/functional/apps/maps/embeddable/save_and_return.js index 13aa08cbbeb38..cd38d808ca510 100644 --- a/x-pack/test/functional/apps/maps/embeddable/save_and_return.js +++ b/x-pack/test/functional/apps/maps/embeddable/save_and_return.js @@ -82,6 +82,19 @@ export default function ({ getPageObjects, getService }) { const panelCount = await PageObjects.dashboard.getPanelCount(); expect(panelCount).to.equal(2); }); + + it('should lose its connection to the dashboard when creating new map', async () => { + await PageObjects.maps.gotoMapListingPage(); + await PageObjects.maps.openNewMap(); + await PageObjects.maps.expectMissingSaveAndReturnButton(); + + // return to origin should not be present in save modal + await testSubjects.click('mapSaveButton'); + const redirectToOriginCheckboxExists = await testSubjects.exists( + 'returnToOriginModeSwitch' + ); + expect(redirectToOriginCheckboxExists).to.be(false); + }); }); describe('save as', () => { diff --git a/x-pack/test/functional/apps/ml/data_frame_analytics/cloning.ts b/x-pack/test/functional/apps/ml/data_frame_analytics/cloning.ts index 532de930bc1a1..91ca0e6f32fd8 100644 --- a/x-pack/test/functional/apps/ml/data_frame_analytics/cloning.ts +++ b/x-pack/test/functional/apps/ml/data_frame_analytics/cloning.ts @@ -13,7 +13,8 @@ export default function ({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const ml = getService('ml'); - describe('jobs cloning supported by UI form', function () { + // Failing ES promotion, see https://github.com/elastic/kibana/issues/89980 + describe.skip('jobs cloning supported by UI form', function () { const testDataList: Array<{ suiteTitle: string; archive: string; diff --git a/x-pack/test/functional/apps/monitoring/enable_monitoring/index.js b/x-pack/test/functional/apps/monitoring/enable_monitoring/index.js index 659de2db31e71..812590f81d12f 100644 --- a/x-pack/test/functional/apps/monitoring/enable_monitoring/index.js +++ b/x-pack/test/functional/apps/monitoring/enable_monitoring/index.js @@ -13,8 +13,11 @@ export default function ({ getService, getPageObjects }) { const testSubjects = getService('testSubjects'); const clusterOverview = getService('monitoringClusterOverview'); const retry = getService('retry'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); describe('Monitoring is turned off', function () { + // You no longer enable monitoring through Kibana on cloud https://github.com/elastic/kibana/pull/88375 + this.tags(['skipCloud']); before(async () => { const browser = getService('browser'); await browser.setWindowSize(1600, 1000); @@ -37,7 +40,7 @@ export default function ({ getService, getPageObjects }) { }; await esSupertest.put('/_cluster/settings').send(disableCollection).expect(200); - await esSupertest.delete('/.monitoring-*').expect(200); + await esDeleteAllIndices('/.monitoring-*'); }); it('Monitoring enabled', async function () { diff --git a/x-pack/test/functional/apps/remote_clusters/feature_controls/remote_clusters_security.ts b/x-pack/test/functional/apps/remote_clusters/feature_controls/remote_clusters_security.ts index e6b5a7ac77d7c..b1edc74607161 100644 --- a/x-pack/test/functional/apps/remote_clusters/feature_controls/remote_clusters_security.ts +++ b/x-pack/test/functional/apps/remote_clusters/feature_controls/remote_clusters_security.ts @@ -13,8 +13,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const appsMenu = getService('appsMenu'); const managementMenu = getService('managementMenu'); - // FAILING ES PROMOTION: https://github.com/elastic/kibana/issues/89181 - describe.skip('security', () => { + describe('security', () => { before(async () => { await esArchiver.load('empty_kibana'); await PageObjects.common.navigateToApp('home'); diff --git a/x-pack/test/functional/apps/rollup_job/hybrid_index_pattern.js b/x-pack/test/functional/apps/rollup_job/hybrid_index_pattern.js index b4eaff7f3ddcf..9dd991854f29f 100644 --- a/x-pack/test/functional/apps/rollup_job/hybrid_index_pattern.js +++ b/x-pack/test/functional/apps/rollup_job/hybrid_index_pattern.js @@ -14,6 +14,7 @@ export default function ({ getService, getPageObjects }) { const find = getService('find'); const retry = getService('retry'); const PageObjects = getPageObjects(['common', 'settings']); + const esDeleteAllIndices = getService('esDeleteAllIndices'); describe('hybrid index pattern', function () { //Since rollups can only be created once with the same name (even if you delete it), @@ -107,9 +108,12 @@ export default function ({ getService, getPageObjects }) { method: 'DELETE', }); - await es.indices.delete({ index: rollupTargetIndexName }); - await es.indices.delete({ index: `${regularIndexPrefix}*` }); - await es.indices.delete({ index: `${rollupSourceIndexPrefix}*` }); + await esDeleteAllIndices([ + rollupTargetIndexName, + `${regularIndexPrefix}*`, + `${rollupSourceIndexPrefix}*`, + ]); + await esArchiver.load('empty_kibana'); }); }); diff --git a/x-pack/test/functional/apps/rollup_job/rollup_jobs.js b/x-pack/test/functional/apps/rollup_job/rollup_jobs.js index f7f92e6955799..8c245728ea00a 100644 --- a/x-pack/test/functional/apps/rollup_job/rollup_jobs.js +++ b/x-pack/test/functional/apps/rollup_job/rollup_jobs.js @@ -13,6 +13,7 @@ export default function ({ getService, getPageObjects }) { const esArchiver = getService('esArchiver'); const PageObjects = getPageObjects(['rollup', 'common', 'security']); const security = getService('security'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); describe('rollup job', function () { //Since rollups can only be created once with the same name (even if you delete it), @@ -68,8 +69,7 @@ export default function ({ getService, getPageObjects }) { }); //Delete all data indices that were created. - await es.indices.delete({ index: targetIndexName }); - await es.indices.delete({ index: rollupSourceIndexPattern }); + await esDeleteAllIndices([targetIndexName, rollupSourceIndexPattern]); await esArchiver.load('empty_kibana'); await security.testUser.restoreDefaults(); }); diff --git a/x-pack/test/functional/apps/rollup_job/tsvb.js b/x-pack/test/functional/apps/rollup_job/tsvb.js index d52f84172ae12..a03d884f287f8 100644 --- a/x-pack/test/functional/apps/rollup_job/tsvb.js +++ b/x-pack/test/functional/apps/rollup_job/tsvb.js @@ -11,6 +11,7 @@ export default function ({ getService, getPageObjects }) { const es = getService('legacyEs'); const esArchiver = getService('esArchiver'); const retry = getService('retry'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); const PageObjects = getPageObjects([ 'common', 'settings', @@ -96,8 +97,7 @@ export default function ({ getService, getPageObjects }) { method: 'DELETE', }); - await es.indices.delete({ index: rollupTargetIndexName }); - await es.indices.delete({ index: rollupSourceIndexName }); + await esDeleteAllIndices([rollupTargetIndexName, rollupSourceIndexName]); await esArchiver.load('empty_kibana'); }); }); diff --git a/x-pack/test/functional/apps/upgrade_assistant/upgrade_assistant.ts b/x-pack/test/functional/apps/upgrade_assistant/upgrade_assistant.ts index 57b8fb23613be..9e742ca5463a1 100644 --- a/x-pack/test/functional/apps/upgrade_assistant/upgrade_assistant.ts +++ b/x-pack/test/functional/apps/upgrade_assistant/upgrade_assistant.ts @@ -4,6 +4,7 @@ * 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 upgradeAssistantFunctionalTests({ @@ -14,6 +15,7 @@ export default function upgradeAssistantFunctionalTests({ const PageObjects = getPageObjects(['upgradeAssistant', 'common']); const security = getService('security'); const log = getService('log'); + const retry = getService('retry'); describe('Upgrade Checkup', function () { this.tags('includeFirefox'); @@ -24,41 +26,52 @@ export default function upgradeAssistantFunctionalTests({ }); after(async () => { - await PageObjects.upgradeAssistant.expectTelemetryHasFinish(); + await PageObjects.upgradeAssistant.waitForTelemetryHidden(); await esArchiver.unload('empty_kibana'); await security.testUser.restoreDefaults(); }); it('allows user to navigate to upgrade checkup', async () => { await PageObjects.upgradeAssistant.navigateToPage(); - await PageObjects.upgradeAssistant.expectUpgradeAssistant(); }); it('allows user to toggle deprecation logging', async () => { - await PageObjects.upgradeAssistant.navigateToPage(); log.debug('expect initial state to be ON'); - await PageObjects.upgradeAssistant.expectDeprecationLoggingLabel('On'); - log.debug('Now toggle to off'); - await PageObjects.upgradeAssistant.toggleDeprecationLogging(); - await PageObjects.common.sleep(2000); - log.debug('expect state to be OFF after toggle'); - await PageObjects.upgradeAssistant.expectDeprecationLoggingLabel('Off'); - await PageObjects.upgradeAssistant.toggleDeprecationLogging(); - await PageObjects.common.sleep(2000); - log.debug('expect state to be ON after toggle'); - await PageObjects.upgradeAssistant.expectDeprecationLoggingLabel('On'); + expect(await PageObjects.upgradeAssistant.deprecationLoggingEnabledLabel()).to.be('On'); + expect(await PageObjects.upgradeAssistant.isDeprecationLoggingEnabled()).to.be(true); + + await retry.try(async () => { + log.debug('Now toggle to off'); + await PageObjects.upgradeAssistant.toggleDeprecationLogging(); + + log.debug('expect state to be OFF after toggle'); + expect(await PageObjects.upgradeAssistant.isDeprecationLoggingEnabled()).to.be(false); + expect(await PageObjects.upgradeAssistant.deprecationLoggingEnabledLabel()).to.be('Off'); + }); + + log.debug('Now toggle back on.'); + await retry.try(async () => { + await PageObjects.upgradeAssistant.toggleDeprecationLogging(); + log.debug('expect state to be ON after toggle'); + expect(await PageObjects.upgradeAssistant.isDeprecationLoggingEnabled()).to.be(true); + expect(await PageObjects.upgradeAssistant.deprecationLoggingEnabledLabel()).to.be('On'); + }); }); it('allows user to open cluster tab', async () => { await PageObjects.upgradeAssistant.navigateToPage(); await PageObjects.upgradeAssistant.clickTab('cluster'); - await PageObjects.upgradeAssistant.expectIssueSummary('You have no cluster issues.'); + expect(await PageObjects.upgradeAssistant.issueSummaryText()).to.be( + 'You have no cluster issues.' + ); }); it('allows user to open indices tab', async () => { await PageObjects.upgradeAssistant.navigateToPage(); await PageObjects.upgradeAssistant.clickTab('indices'); - await PageObjects.upgradeAssistant.expectIssueSummary('You have no index issues.'); + expect(await PageObjects.upgradeAssistant.issueSummaryText()).to.be( + 'You have no index issues.' + ); }); }); } diff --git a/x-pack/test/functional/es_archives/data/search_sessions/data.json.gz b/x-pack/test/functional/es_archives/data/search_sessions/data.json.gz index 28260ee99e4dc..51e8c09f19247 100644 Binary files a/x-pack/test/functional/es_archives/data/search_sessions/data.json.gz and b/x-pack/test/functional/es_archives/data/search_sessions/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/data/search_sessions/mappings.json b/x-pack/test/functional/es_archives/data/search_sessions/mappings.json index 24bbcbea23385..4492bcae7047d 100644 --- a/x-pack/test/functional/es_archives/data/search_sessions/mappings.json +++ b/x-pack/test/functional/es_archives/data/search_sessions/mappings.json @@ -310,6 +310,9 @@ "created": { "type": "date" }, + "touched": { + "type": "date" + }, "expires": { "type": "date" }, @@ -324,6 +327,9 @@ "name": { "type": "keyword" }, + "persisted": { + "type": "boolean" + }, "restoreState": { "enabled": false, "type": "object" diff --git a/x-pack/test/functional/es_archives/visualize/default/data.json b/x-pack/test/functional/es_archives/visualize/default/data.json index 26b033e28b4da..fe29bad0fa381 100644 --- a/x-pack/test/functional/es_archives/visualize/default/data.json +++ b/x-pack/test/functional/es_archives/visualize/default/data.json @@ -125,8 +125,26 @@ { "type": "doc", "value": { - "index": ".kibana", - "type": "doc", + "id": "custom-space:index-pattern:metricbeat-*", + "index": ".kibana_1", + "source": { + "index-pattern": { + "fieldFormatMap": "{\"aerospike.namespace.device.available.pct\":{\"id\":\"percent\",\"params\":{}},\"aerospike.namespace.device.free.pct\":{\"id\":\"percent\",\"params\":{}},\"aerospike.namespace.device.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aerospike.namespace.device.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aerospike.namespace.memory.free.pct\":{\"id\":\"percent\",\"params\":{}},\"aerospike.namespace.memory.used.data.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aerospike.namespace.memory.used.index.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aerospike.namespace.memory.used.sindex.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aerospike.namespace.memory.used.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.ec2.diskio.read.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.ec2.diskio.write.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.ec2.network.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.ec2.network.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.cpu.total.pct\":{\"id\":\"percent\",\"params\":{}},\"aws.rds.disk_usage.bin_log.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.free_local_storage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.free_storage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.freeable_memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.latency.commit\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.ddl\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.dml\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.insert\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.read\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.select\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.update\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.latency.write\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.replica_lag.sec\":{\"id\":\"duration\",\"params\":{}},\"aws.rds.swap_usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.rds.volume_used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.s3_daily_storage.bucket.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.s3_request.downloaded.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.s3_request.latency.first_byte.ms\":{\"id\":\"duration\",\"params\":{}},\"aws.s3_request.latency.total_request.ms\":{\"id\":\"duration\",\"params\":{}},\"aws.s3_request.requests.select_returned.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.s3_request.requests.select_scanned.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.s3_request.uploaded.bytes\":{\"id\":\"bytes\",\"params\":{}},\"aws.sqs.oldest_message_age.sec\":{\"id\":\"duration\",\"params\":{}},\"aws.sqs.sent_message_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_disk.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_disk.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_disk.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.degraded.ratio\":{\"id\":\"percent\",\"params\":{}},\"ceph.cluster_status.misplace.ratio\":{\"id\":\"percent\",\"params\":{}},\"ceph.cluster_status.pg.avail_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.pg.data_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.pg.total_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.pg.used_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.traffic.read_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.cluster_status.traffic.write_bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.monitor_health.store_stats.log.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.monitor_health.store_stats.misc.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.monitor_health.store_stats.sst.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.monitor_health.store_stats.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.osd_df.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.osd_df.total.byte\":{\"id\":\"bytes\",\"params\":{}},\"ceph.osd_df.used.byte\":{\"id\":\"bytes\",\"params\":{}},\"ceph.osd_df.used.pct\":{\"id\":\"percent\",\"params\":{}},\"ceph.pool_disk.stats.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"ceph.pool_disk.stats.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"client.bytes\":{\"id\":\"bytes\",\"params\":{}},\"client.nat.port\":{\"id\":\"string\",\"params\":{}},\"client.port\":{\"id\":\"string\",\"params\":{}},\"coredns.stats.dns.request.duration.ns.sum\":{\"id\":\"duration\",\"params\":{}},\"couchbase.bucket.data.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.bucket.disk.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.bucket.memory.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.bucket.quota.ram.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.bucket.quota.use.pct\":{\"id\":\"percent\",\"params\":{}},\"couchbase.cluster.hdd.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.hdd.quota.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.hdd.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.hdd.used.by_data.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.hdd.used.value.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.quota.total.per_node.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.quota.total.value.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.quota.used.per_node.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.quota.used.value.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.used.by_data.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.cluster.ram.used.value.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.node.couch.docs.data_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.node.couch.docs.disk_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"couchbase.node.mcd_memory.allocated.bytes\":{\"id\":\"bytes\",\"params\":{}},\"destination.bytes\":{\"id\":\"bytes\",\"params\":{}},\"destination.nat.port\":{\"id\":\"string\",\"params\":{}},\"destination.port\":{\"id\":\"string\",\"params\":{}},\"docker.cpu.core.*.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.cpu.kernel.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.cpu.system.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.cpu.total.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.cpu.user.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.diskio.read.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.diskio.summary.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.diskio.write.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.commit.peak\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.commit.total\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.limit\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.private_working_set.total\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.rss.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.memory.rss.total\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.usage.max\":{\"id\":\"bytes\",\"params\":{}},\"docker.memory.usage.pct\":{\"id\":\"percent\",\"params\":{}},\"docker.memory.usage.total\":{\"id\":\"bytes\",\"params\":{}},\"docker.network.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.network.inbound.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.network.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"docker.network.outbound.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.summary.primaries.segments.memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.summary.primaries.store.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.summary.total.segments.memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.summary.total.store.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.total.segments.memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.index.total.store.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.jvm.memory.heap.init.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.jvm.memory.heap.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.jvm.memory.nonheap.init.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.jvm.memory.nonheap.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.fs.summary.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.fs.summary.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.fs.summary.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.indices.segments.memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.old.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.old.peak.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.old.peak_max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.old.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.survivor.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.survivor.peak.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.survivor.peak_max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.survivor.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.young.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.young.peak.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.young.peak_max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"elasticsearch.node.stats.jvm.mem.pools.young.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"etcd.disk.mvcc_db_total_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"etcd.memory.go_memstats_alloc.bytes\":{\"id\":\"bytes\",\"params\":{}},\"etcd.network.client_grpc_received.bytes\":{\"id\":\"bytes\",\"params\":{}},\"etcd.network.client_grpc_sent.bytes\":{\"id\":\"bytes\",\"params\":{}},\"event.duration\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"nanoseconds\",\"outputFormat\":\"asMilliseconds\",\"outputPrecision\":1}},\"event.sequence\":{\"id\":\"string\",\"params\":{}},\"event.severity\":{\"id\":\"string\",\"params\":{}},\"golang.heap.allocations.active\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.allocations.allocated\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.allocations.idle\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.allocations.total\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.gc.next_gc_limit\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.system.obtained\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.system.released\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.system.stack\":{\"id\":\"bytes\",\"params\":{}},\"golang.heap.system.total\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.info.idle.pct\":{\"id\":\"percent\",\"params\":{}},\"haproxy.info.memory.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.info.ssl.frontend.session_reuse.pct\":{\"id\":\"percent\",\"params\":{}},\"haproxy.stat.compressor.bypassed.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.compressor.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.compressor.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.compressor.response.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"haproxy.stat.throttle.pct\":{\"id\":\"percent\",\"params\":{}},\"http.request.body.bytes\":{\"id\":\"bytes\",\"params\":{}},\"http.request.bytes\":{\"id\":\"bytes\",\"params\":{}},\"http.response.body.bytes\":{\"id\":\"bytes\",\"params\":{}},\"http.response.bytes\":{\"id\":\"bytes\",\"params\":{}},\"http.response.status_code\":{\"id\":\"string\",\"params\":{}},\"kibana.stats.process.memory.heap.size_limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kibana.stats.process.memory.heap.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kibana.stats.process.memory.heap.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.apiserver.http.request.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.apiserver.http.response.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.apiserver.process.memory.resident.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.apiserver.process.memory.virtual.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.cpu.usage.limit.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.container.cpu.usage.node.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.container.logs.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.logs.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.logs.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.request.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.memory.usage.limit.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.container.memory.usage.node.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.container.memory.workingset.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.rootfs.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.rootfs.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.container.rootfs.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.controllermanager.http.request.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.controllermanager.http.response.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.controllermanager.process.memory.resident.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.controllermanager.process.memory.virtual.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.fs.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.fs.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.fs.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.allocatable.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.memory.workingset.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.network.rx.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.network.tx.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.runtime.imagefs.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.runtime.imagefs.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.node.runtime.imagefs.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.cpu.usage.limit.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.pod.cpu.usage.node.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.pod.memory.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.memory.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.memory.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.memory.usage.limit.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.pod.memory.usage.node.pct\":{\"id\":\"percent\",\"params\":{}},\"kubernetes.pod.memory.working_set.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.network.rx.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.pod.network.tx.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.proxy.http.request.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.proxy.http.response.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.proxy.process.memory.resident.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.proxy.process.memory.virtual.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.scheduler.http.request.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.scheduler.http.response.size.bytes.sum\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.scheduler.process.memory.resident.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.scheduler.process.memory.virtual.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.system.memory.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.system.memory.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.system.memory.workingset.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.volume.fs.available.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.volume.fs.capacity.bytes\":{\"id\":\"bytes\",\"params\":{}},\"kubernetes.volume.fs.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.avg_obj_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.data_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.extent_free_list.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.file_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.index_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.dbstats.storage_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.replstatus.headroom.max\":{\"id\":\"duration\",\"params\":{}},\"mongodb.replstatus.headroom.min\":{\"id\":\"duration\",\"params\":{}},\"mongodb.replstatus.lag.max\":{\"id\":\"duration\",\"params\":{}},\"mongodb.replstatus.lag.min\":{\"id\":\"duration\",\"params\":{}},\"mongodb.replstatus.oplog.size.allocated\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.replstatus.oplog.size.used\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.extra_info.heap_usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.network.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.network.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.cache.dirty.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.cache.maximum.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.cache.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.log.max_file_size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.log.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mongodb.status.wired_tiger.log.write.bytes\":{\"id\":\"bytes\",\"params\":{}},\"mysql.status.bytes.received\":{\"id\":\"bytes\",\"params\":{}},\"mysql.status.bytes.sent\":{\"id\":\"bytes\",\"params\":{}},\"nats.stats.cpu\":{\"id\":\"percent\",\"params\":{}},\"nats.stats.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"nats.stats.mem.bytes\":{\"id\":\"bytes\",\"params\":{}},\"nats.stats.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"nats.stats.uptime\":{\"id\":\"duration\",\"params\":{}},\"nats.subscriptions.cache.hit_rate\":{\"id\":\"percent\",\"params\":{}},\"network.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.data_file.size.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.data_file.size.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.data_file.size.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.space.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.space.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"oracle.tablespace.space.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"process.pgid\":{\"id\":\"string\",\"params\":{}},\"process.pid\":{\"id\":\"string\",\"params\":{}},\"process.ppid\":{\"id\":\"string\",\"params\":{}},\"process.thread.id\":{\"id\":\"string\",\"params\":{}},\"rabbitmq.connection.frame_max\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.disk.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.disk.free.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.gc.reclaimed.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.io.read.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.io.write.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.node.mem.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"rabbitmq.queue.consumers.utilisation.pct\":{\"id\":\"percent\",\"params\":{}},\"rabbitmq.queue.memory.bytes\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.allocator_stats.active\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.allocator_stats.allocated\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.allocator_stats.fragmentation.bytes\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.allocator_stats.resident\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.allocator_stats.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.fragmentation.bytes\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.max.value\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.used.dataset\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.used.lua\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.used.peak\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.used.rss\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.memory.used.value\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.aof.buffer.size\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.aof.copy_on_write.last_size\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.aof.rewrite.buffer.size\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.aof.rewrite.current_time.sec\":{\"id\":\"duration\",\"params\":{}},\"redis.info.persistence.aof.rewrite.last_time.sec\":{\"id\":\"duration\",\"params\":{}},\"redis.info.persistence.aof.size.base\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.aof.size.current\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.persistence.rdb.bgsave.current_time.sec\":{\"id\":\"duration\",\"params\":{}},\"redis.info.persistence.rdb.bgsave.last_time.sec\":{\"id\":\"duration\",\"params\":{}},\"redis.info.persistence.rdb.copy_on_write.last_size\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.replication.backlog.size\":{\"id\":\"bytes\",\"params\":{}},\"redis.info.replication.master.last_io_seconds_ago\":{\"id\":\"duration\",\"params\":{}},\"redis.info.replication.master.sync.last_io_seconds_ago\":{\"id\":\"duration\",\"params\":{}},\"redis.info.replication.master.sync.left_bytes\":{\"id\":\"bytes\",\"params\":{}},\"server.bytes\":{\"id\":\"bytes\",\"params\":{}},\"server.nat.port\":{\"id\":\"string\",\"params\":{}},\"server.port\":{\"id\":\"string\",\"params\":{}},\"source.bytes\":{\"id\":\"bytes\",\"params\":{}},\"source.nat.port\":{\"id\":\"string\",\"params\":{}},\"source.port\":{\"id\":\"string\",\"params\":{}},\"system.core.idle.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.iowait.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.irq.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.nice.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.softirq.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.steal.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.system.pct\":{\"id\":\"percent\",\"params\":{}},\"system.core.user.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.idle.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.idle.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.iowait.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.iowait.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.irq.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.irq.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.nice.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.nice.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.softirq.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.softirq.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.steal.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.steal.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.system.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.system.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.total.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.total.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.user.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.cpu.user.pct\":{\"id\":\"percent\",\"params\":{}},\"system.diskio.iostat.read.per_sec.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.diskio.iostat.write.per_sec.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.diskio.read.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.diskio.write.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.entropy.pct\":{\"id\":\"percent\",\"params\":{}},\"system.filesystem.available\":{\"id\":\"bytes\",\"params\":{}},\"system.filesystem.free\":{\"id\":\"bytes\",\"params\":{}},\"system.filesystem.total\":{\"id\":\"bytes\",\"params\":{}},\"system.filesystem.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.filesystem.used.pct\":{\"id\":\"percent\",\"params\":{}},\"system.fsstat.total_size.free\":{\"id\":\"bytes\",\"params\":{}},\"system.fsstat.total_size.total\":{\"id\":\"bytes\",\"params\":{}},\"system.fsstat.total_size.used\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.actual.free\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.actual.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.actual.used.pct\":{\"id\":\"percent\",\"params\":{}},\"system.memory.free\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.hugepages.default_size\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.hugepages.free\":{\"id\":\"number\",\"params\":{}},\"system.memory.hugepages.reserved\":{\"id\":\"number\",\"params\":{}},\"system.memory.hugepages.surplus\":{\"id\":\"number\",\"params\":{}},\"system.memory.hugepages.total\":{\"id\":\"number\",\"params\":{}},\"system.memory.hugepages.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.hugepages.used.pct\":{\"id\":\"percent\",\"params\":{}},\"system.memory.swap.free\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.swap.total\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.swap.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.swap.used.pct\":{\"id\":\"percent\",\"params\":{}},\"system.memory.total\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.memory.used.pct\":{\"id\":\"percent\",\"params\":{}},\"system.network.in.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.network.out.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.blkio.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem.usage.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem_tcp.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem_tcp.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.kmem_tcp.usage.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.mem.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.mem.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.mem.usage.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.memsw.limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.memsw.usage.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.memsw.usage.max.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.active_anon.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.active_file.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.cache.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.hierarchical_memory_limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.hierarchical_memsw_limit.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.inactive_anon.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.inactive_file.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.mapped_file.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.rss_huge.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.swap.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cgroup.memory.stats.unevictable.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.cpu.total.norm.pct\":{\"id\":\"percent\",\"params\":{}},\"system.process.cpu.total.pct\":{\"id\":\"percent\",\"params\":{}},\"system.process.memory.rss.bytes\":{\"id\":\"bytes\",\"params\":{}},\"system.process.memory.rss.pct\":{\"id\":\"percent\",\"params\":{}},\"system.process.memory.share\":{\"id\":\"bytes\",\"params\":{}},\"system.process.memory.size\":{\"id\":\"bytes\",\"params\":{}},\"system.socket.summary.tcp.memory\":{\"id\":\"bytes\",\"params\":{}},\"system.socket.summary.udp.memory\":{\"id\":\"bytes\",\"params\":{}},\"system.uptime.duration.ms\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"milliseconds\"}},\"url.port\":{\"id\":\"string\",\"params\":{}},\"vsphere.datastore.capacity.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.datastore.capacity.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.datastore.capacity.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.datastore.capacity.used.pct\":{\"id\":\"percent\",\"params\":{}},\"vsphere.host.memory.free.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.host.memory.total.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.host.memory.used.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.virtualmachine.memory.free.guest.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.virtualmachine.memory.total.guest.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.virtualmachine.memory.used.guest.bytes\":{\"id\":\"bytes\",\"params\":{}},\"vsphere.virtualmachine.memory.used.host.bytes\":{\"id\":\"bytes\",\"params\":{}},\"windows.service.uptime.ms\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"milliseconds\"}}}", + "timeFieldName": "@timestamp", + "title": "metricbeat-*" + }, + "migrationVersion": { + "index-pattern": "7.6.0" + }, + "type": "index-pattern", + "updated_at": "2020-01-22T15:34:59.061Z" + } + } +} + +{ + "type": "doc", + "value": { "id": "index-pattern:logstash-*", "index": ".kibana_1", "source": { @@ -279,4 +297,4 @@ "updated_at": "2019-07-17T17:54:26.378Z" } } -} +} \ No newline at end of file diff --git a/x-pack/test/functional/page_objects/gis_page.ts b/x-pack/test/functional/page_objects/gis_page.ts index 7767499cb6bd3..7f321e65278a4 100644 --- a/x-pack/test/functional/page_objects/gis_page.ts +++ b/x-pack/test/functional/page_objects/gis_page.ts @@ -170,6 +170,10 @@ export function GisPageProvider({ getService, getPageObjects }: FtrProviderConte await testSubjects.click('mapSaveAndReturnButton'); } + async expectMissingSaveAndReturnButton() { + await testSubjects.missingOrFail('mapSaveAndReturnButton'); + } + async expectMissingSaveButton() { await testSubjects.missingOrFail('mapSaveButton'); } diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts index 17f9fb036129a..292e91866d690 100644 --- a/x-pack/test/functional/page_objects/lens_page.ts +++ b/x-pack/test/functional/page_objects/lens_page.ts @@ -16,7 +16,7 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont const find = getService('find'); const comboBox = getService('comboBox'); const browser = getService('browser'); - const PageObjects = getPageObjects(['header', 'timePicker', 'common', 'visualize']); + const PageObjects = getPageObjects(['header', 'timePicker', 'common', 'visualize', 'dashboard']); return logWrapper('lensPage', log, { /** @@ -586,5 +586,49 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont // TODO: target dimensionTrigger color element after merging https://github.com/elastic/kibana/pull/76871 await testSubjects.getAttribute('colorPickerAnchor', color); }, + + /** + * Creates and saves a lens visualization from a dashboard + * + * @param title - title for the new lens. If left undefined, the panel will be created by value + * @param redirectToOrigin - whether to redirect back to the dashboard after saving the panel + */ + async createAndAddLensFromDashboard({ + title, + redirectToOrigin, + }: { + title?: string; + redirectToOrigin?: boolean; + }) { + log.debug(`createAndAddLens${title}`); + const inViewMode = await PageObjects.dashboard.getIsInViewMode(); + if (inViewMode) { + await PageObjects.dashboard.switchToEditMode(); + } + await PageObjects.visualize.clickLensWidget(); + await this.goToTimeRange(); + await this.configureDimension({ + dimension: 'lnsXY_xDimensionPanel > lns-empty-dimension', + operation: 'date_histogram', + field: '@timestamp', + }); + + await this.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'avg', + field: 'bytes', + }); + + await this.configureDimension({ + dimension: 'lnsXY_splitDimensionPanel > lns-empty-dimension', + operation: 'terms', + field: 'ip', + }); + if (title) { + await this.save(title, false, redirectToOrigin); + } else { + await this.saveAndReturn(); + } + }, }); } diff --git a/x-pack/test/functional/page_objects/search_sessions_management_page.ts b/x-pack/test/functional/page_objects/search_sessions_management_page.ts index 99c3be82a214d..d57405f1f03ab 100644 --- a/x-pack/test/functional/page_objects/search_sessions_management_page.ts +++ b/x-pack/test/functional/page_objects/search_sessions_management_page.ts @@ -7,6 +7,7 @@ import { FtrProviderContext } from '../ftr_provider_context'; export function SearchSessionsPageProvider({ getService, getPageObjects }: FtrProviderContext) { + const log = getService('log'); const find = getService('find'); const testSubjects = getService('testSubjects'); const PageObjects = getPageObjects(['common']); @@ -21,7 +22,7 @@ export function SearchSessionsPageProvider({ getService, getPageObjects }: FtrPr }, async getList() { - const table = await find.byCssSelector('table'); + const table = await testSubjects.find('searchSessionsMgmtTable'); const allRows = await table.findAllByTestSubject('searchSessionsRow'); return Promise.all( @@ -37,15 +38,18 @@ export function SearchSessionsPageProvider({ getService, getPageObjects }: FtrPr expires: $.findTestSubject('sessionManagementExpiresCol').text(), app: $.findTestSubject('sessionManagementAppIcon').attr('data-test-app-id'), view: async () => { + log.debug('management ui: view the session'); await viewCell.click(); }, reload: async () => { + log.debug('management ui: reload the status'); await actionsCell.click(); await find.clickByCssSelector( '[data-test-subj="sessionManagementPopoverAction-reload"]' ); }, cancel: async () => { + log.debug('management ui: cancel the session'); await actionsCell.click(); await find.clickByCssSelector( '[data-test-subj="sessionManagementPopoverAction-cancel"]' diff --git a/x-pack/test/functional/page_objects/upgrade_assistant_page.ts b/x-pack/test/functional/page_objects/upgrade_assistant_page.ts index 3fe60747505a7..739b796c5a623 100644 --- a/x-pack/test/functional/page_objects/upgrade_assistant_page.ts +++ b/x-pack/test/functional/page_objects/upgrade_assistant_page.ts @@ -4,7 +4,6 @@ * 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 function UpgradeAssistantPageProvider({ getPageObjects, getService }: FtrProviderContext) { @@ -24,34 +23,32 @@ export function UpgradeAssistantPageProvider({ getPageObjects, getService }: Ftr return await retry.try(async () => { await common.navigateToApp('settings'); await testSubjects.click('upgrade_assistant'); + retry.waitFor('url to contain /upgrade_assistant', async () => { + const url = await browser.getCurrentUrl(); + return url.includes('/upgrade_assistant'); + }); }); } - async expectUpgradeAssistant() { - return await retry.try(async () => { - log.debug(`expectUpgradeAssistant()`); - expect(await testSubjects.exists('upgradeAssistantRoot')).to.equal(true); - const url = await browser.getCurrentUrl(); - expect(url).to.contain(`/upgrade_assistant`); - }); + async toggleDeprecationLogging() { + log.debug('toggleDeprecationLogging()'); + await testSubjects.click('upgradeAssistantDeprecationToggle'); } - async toggleDeprecationLogging() { - return await retry.try(async () => { - log.debug('toggleDeprecationLogging()'); - await testSubjects.click('upgradeAssistantDeprecationToggle'); - }); + async isDeprecationLoggingEnabled() { + const isDeprecationEnabled = await testSubjects.getAttribute( + 'upgradeAssistantDeprecationToggle', + 'aria-checked' + ); + log.debug(`Deprecation enabled == ${isDeprecationEnabled}`); + return isDeprecationEnabled === 'true'; } - async expectDeprecationLoggingLabel(labelText: string) { - return await retry.try(async () => { - log.debug('expectDeprecationLoggingLabel()'); - const label = await find.byCssSelector( - '[data-test-subj="upgradeAssistantDeprecationToggle"] ~ span' - ); - const value = await label.getVisibleText(); - expect(value).to.equal(labelText); - }); + async deprecationLoggingEnabledLabel() { + const loggingEnabledLabel = await find.byCssSelector( + '[data-test-subj="upgradeAssistantDeprecationToggle"] ~ span' + ); + return await loggingEnabledLabel.getVisibleText(); } async clickTab(tabId: string) { @@ -61,22 +58,20 @@ export function UpgradeAssistantPageProvider({ getPageObjects, getService }: Ftr }); } - async expectIssueSummary(summary: string) { - return await retry.try(async () => { - log.debug('expectIssueSummary()'); - const summaryElText = await testSubjects.getVisibleText('upgradeAssistantIssueSummary'); - expect(summaryElText).to.eql(summary); + async waitForTelemetryHidden() { + const self = this; + retry.waitFor('Telemetry to disappear.', async () => { + return (await self.isTelemetryExists()) === false; }); } - async expectTelemetryHasFinish() { - return await retry.try(async () => { - log.debug('expectTelemetryHasFinish'); - const isTelemetryFinished = !(await testSubjects.exists( - 'upgradeAssistantTelemetryRunning' - )); - expect(isTelemetryFinished).to.equal(true); - }); + async issueSummaryText() { + log.debug('expectIssueSummary()'); + return await testSubjects.getVisibleText('upgradeAssistantIssueSummary'); + } + + async isTelemetryExists() { + return await testSubjects.exists('upgradeAssistantTelemetryRunning'); } } diff --git a/x-pack/test/functional/services/ml/api.ts b/x-pack/test/functional/services/ml/api.ts index e50fb1818273d..788cab1acb919 100644 --- a/x-pack/test/functional/services/ml/api.ts +++ b/x-pack/test/functional/services/ml/api.ts @@ -38,6 +38,7 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { const retry = getService('retry'); const esSupertest = getService('esSupertest'); const kbnSupertest = getService('supertest'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); return { async hasJobResults(jobId: string): Promise { @@ -163,7 +164,7 @@ export function MachineLearningAPIProvider({ getService }: FtrProviderContext) { }, async cleanMlIndices() { - await this.deleteIndices('.ml-*'); + await esDeleteAllIndices('.ml-*'); }, async getJobState(jobId: string): Promise { diff --git a/x-pack/test/functional/services/transform/api.ts b/x-pack/test/functional/services/transform/api.ts index d97db93c31b3b..7b40f1629622f 100644 --- a/x-pack/test/functional/services/transform/api.ts +++ b/x-pack/test/functional/services/transform/api.ts @@ -22,6 +22,7 @@ export function TransformAPIProvider({ getService }: FtrProviderContext) { const log = getService('log'); const retry = getService('retry'); const esSupertest = getService('esSupertest'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); return { async createIndices(indices: string) { @@ -102,7 +103,7 @@ export function TransformAPIProvider({ getService }: FtrProviderContext) { // Delete all transform related notifications to clear messages tabs // in the transforms list expanded rows. - await this.deleteIndices('.transform-notifications-*'); + await esDeleteAllIndices('.transform-notifications-*'); }, async getTransformStats(transformId: string): Promise { diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts index 35e2e03969023..07e0ef62ea4db 100644 --- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts @@ -21,6 +21,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const retry = getService('retry'); const find = getService('find'); const supertest = getService('supertest'); + const comboBox = getService('comboBox'); const objectRemover = new ObjectRemover(supertest); async function createActionManualCleanup(overwrites: Record = {}) { @@ -313,15 +314,70 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { describe('Edit alert with deleted connector', function () { const testRunUuid = uuid.v4(); - after(async () => { + afterEach(async () => { await objectRemover.removeAll(); }); - it('should show and update deleted connectors', async () => { + it('should show and update deleted connectors when there are existing connectors of the same type', async () => { const action = await createActionManualCleanup({ name: `slack-${testRunUuid}-${0}`, }); + await pageObjects.common.navigateToApp('triggersActions'); + const alert = await createAlwaysFiringAlert({ + name: testRunUuid, + actions: [ + { + group: 'default', + id: action.id, + params: { level: 'info', message: ' {{context.message}}' }, + }, + ], + }); + + // refresh to see alert + await browser.refresh(); + await pageObjects.header.waitUntilLoadingHasFinished(); + + // verify content + await testSubjects.existOrFail('alertsList'); + + // delete connector + await pageObjects.triggersActionsUI.changeTabs('connectorsTab'); + await pageObjects.triggersActionsUI.searchConnectors(action.name); + await testSubjects.click('deleteConnector'); + await testSubjects.existOrFail('deleteIdsConfirmation'); + await testSubjects.click('deleteIdsConfirmation > confirmModalConfirmButton'); + await testSubjects.missingOrFail('deleteIdsConfirmation'); + + const toastTitle = await pageObjects.common.closeToast(); + expect(toastTitle).to.eql('Deleted 1 connector'); + + // click on first alert + await pageObjects.triggersActionsUI.changeTabs('alertsTab'); + await pageObjects.triggersActionsUI.clickOnAlertInAlertsList(alert.name); + + const editButton = await testSubjects.find('openEditAlertFlyoutButton'); + await editButton.click(); + expect(await testSubjects.exists('hasActionsDisabled')).to.eql(false); + + expect(await testSubjects.exists('addNewActionConnectorActionGroup-0')).to.eql(false); + expect(await testSubjects.exists('alertActionAccordion-0')).to.eql(true); + + await comboBox.set('selectActionConnector-.slack-0', 'Slack#xyztest (preconfigured)'); + expect(await testSubjects.exists('addNewActionConnectorActionGroup-0')).to.eql(true); + }); + + it('should show and update deleted connectors when there are no existing connectors of the same type', async () => { + const action = await createActionManualCleanup({ + name: `index-${testRunUuid}-${0}`, + actionTypeId: '.index', + config: { + index: `index-${testRunUuid}-${0}`, + }, + secrets: {}, + }); + await pageObjects.common.navigateToApp('triggersActions'); const alert = await createAlwaysFiringAlert({ name: testRunUuid, @@ -373,7 +429,17 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await testSubjects.click('createActionConnectorButton-0'); await testSubjects.existOrFail('connectorAddModal'); await testSubjects.setValue('nameInput', 'new connector'); - await testSubjects.setValue('slackWebhookUrlInput', 'https://test'); + await retry.try(async () => { + // At times we find the driver controlling the ComboBox in tests + // can select the wrong item, this ensures we always select the correct index + await comboBox.set('connectorIndexesComboBox', 'test-index'); + expect( + await comboBox.isOptionSelected( + await testSubjects.find('connectorIndexesComboBox'), + 'test-index' + ) + ).to.be(true); + }); await testSubjects.click('connectorAddModal > saveActionButtonModal'); await testSubjects.missingOrFail('deleteIdsConfirmation'); 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 index 8600cb6c852f5..54ce70911903b 100644 --- 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 @@ -10,9 +10,9 @@ export default ({ loadTestFile, getService }: FtrProviderContext) => { describe('Actions and Triggers app', function () { this.tags('ciGroup10'); loadTestFile(require.resolve('./home_page')); - loadTestFile(require.resolve('./connectors')); loadTestFile(require.resolve('./alerts_list')); loadTestFile(require.resolve('./alert_create_flyout')); loadTestFile(require.resolve('./details')); + loadTestFile(require.resolve('./connectors')); }); }; diff --git a/x-pack/test/load/runner.ts b/x-pack/test/load/runner.ts index e9a1cadfddc55..52448a6b32a99 100644 --- a/x-pack/test/load/runner.ts +++ b/x-pack/test/load/runner.ts @@ -7,23 +7,57 @@ import { withProcRunner } from '@kbn/dev-utils'; import { resolve } from 'path'; import { REPO_ROOT } from '@kbn/utils'; +import Fs from 'fs'; +import { createFlagError } from '@kbn/dev-utils'; import { FtrProviderContext } from './../functional/ftr_provider_context'; +const baseSimulationPath = 'src/test/scala/org/kibanaLoadTest/simulation'; +const simulationPackage = 'org.kibanaLoadTest.simulation'; +const simulationFIleExtension = '.scala'; +const gatlingProjectRootPath: string = + process.env.GATLING_PROJECT_PATH || resolve(REPO_ROOT, '../kibana-load-testing'); +const simulationEntry: string = process.env.GATLING_SIMULATIONS || 'DemoJourney'; + +if (!Fs.existsSync(gatlingProjectRootPath)) { + throw createFlagError( + `Incorrect path to load testing project: '${gatlingProjectRootPath}'\n + Clone 'elastic/kibana-load-testing' and set path using 'GATLING_PROJECT_PATH' env var` + ); +} + +const dropEmptyLines = (s: string) => s.split(',').filter((i) => i.length > 0); +const simulationClasses = dropEmptyLines(simulationEntry); +const simulationsRootPath = resolve(gatlingProjectRootPath, baseSimulationPath); + +simulationClasses.map((className) => { + const simulationClassPath = resolve( + simulationsRootPath, + className.replace('.', '/') + simulationFIleExtension + ); + if (!Fs.existsSync(simulationClassPath)) { + throw createFlagError(`Simulation class is not found: '${simulationClassPath}'`); + } +}); + +/** + * + * GatlingTestRunner is used to run load simulation against local Kibana instance + * + * Use GATLING_SIMULATIONS to pass comma-separated class names + * Use GATLING_PROJECT_PATH to override path to 'kibana-load-testing' project + */ export async function GatlingTestRunner({ getService }: FtrProviderContext) { const log = getService('log'); - const gatlingProjectRootPath = resolve(REPO_ROOT, '../kibana-load-testing'); await withProcRunner(log, async (procs) => { - await procs.run('gatling', { + await procs.run('mvn: clean compile', { cmd: 'mvn', args: [ - 'clean', - '-q', + '-Dmaven.wagon.http.retryHandler.count=3', '-Dmaven.test.failure.ignore=true', - 'compile', - 'gatling:test', '-q', - '-Dgatling.simulationClass=org.kibanaLoadTest.simulation.DemoJourney', + 'clean', + 'compile', ], cwd: gatlingProjectRootPath, env: { @@ -31,5 +65,20 @@ export async function GatlingTestRunner({ getService }: FtrProviderContext) { }, wait: true, }); + for (const simulationClass of simulationClasses) { + await procs.run('gatling: test', { + cmd: 'mvn', + args: [ + 'gatling:test', + '-q', + `-Dgatling.simulationClass=${simulationPackage}.${simulationClass}`, + ], + cwd: gatlingProjectRootPath, + env: { + ...process.env, + }, + wait: true, + }); + } }); } diff --git a/x-pack/test/reporting_api_integration/reporting_without_security.config.ts b/x-pack/test/reporting_api_integration/reporting_without_security.config.ts index 4a95a15169b59..11182bbcdb3b0 100644 --- a/x-pack/test/reporting_api_integration/reporting_without_security.config.ts +++ b/x-pack/test/reporting_api_integration/reporting_without_security.config.ts @@ -32,6 +32,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { kbnTestServer: { ...apiConfig.get('kbnTestServer'), serverArgs: [ + `--migrations.enableV2=false`, `--elasticsearch.hosts=${formatUrl(esTestConfig.getUrlParts())}`, `--logging.json=false`, `--server.maxPayloadBytes=1679958`, diff --git a/x-pack/test/saved_object_tagging/functional/tests/maps_integration.ts b/x-pack/test/saved_object_tagging/functional/tests/maps_integration.ts index 32b9cc378db45..8b7a6affb02ea 100644 --- a/x-pack/test/saved_object_tagging/functional/tests/maps_integration.ts +++ b/x-pack/test/saved_object_tagging/functional/tests/maps_integration.ts @@ -33,7 +33,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { await searchFilter.click(); }; - describe('maps integration', () => { + // Failing: See https://github.com/elastic/kibana/issues/89073 + describe.skip('maps integration', () => { before(async () => { await esArchiver.load('maps'); }); diff --git a/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts b/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts index bd9b66b48be50..1054c86b46af6 100644 --- a/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts +++ b/x-pack/test/security_api_integration/tests/session_idle/cleanup.ts @@ -15,6 +15,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); const es = getService('es'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); const config = getService('config'); const log = getService('log'); const randomness = getService('randomness'); @@ -73,7 +74,7 @@ export default function ({ getService }: FtrProviderContext) { describe('Session Idle cleanup', () => { beforeEach(async () => { await es.cluster.health({ index: '.kibana_security_session*', wait_for_status: 'green' }); - await es.indices.delete({ index: '.kibana_security_session*' }, { ignore: [404] }); + await esDeleteAllIndices('.kibana_security_session*'); }); it('should properly clean up session expired because of idle timeout', async function () { diff --git a/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts b/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts index 7abab0c0b2e15..6ed5b7a5467a0 100644 --- a/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts +++ b/x-pack/test/security_api_integration/tests/session_lifespan/cleanup.ts @@ -15,6 +15,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService }: FtrProviderContext) { const supertest = getService('supertestWithoutAuth'); const es = getService('es'); + const esDeleteAllIndices = getService('esDeleteAllIndices'); const config = getService('config'); const randomness = getService('randomness'); const { username: basicUsername, password: basicPassword } = adminTestUser; @@ -68,7 +69,7 @@ export default function ({ getService }: FtrProviderContext) { describe('Session Lifespan cleanup', () => { beforeEach(async () => { await es.cluster.health({ index: '.kibana_security_session*', wait_for_status: 'green' }); - await es.indices.delete({ index: '.kibana_security_session*' }, { ignore: [404] }); + await esDeleteAllIndices('.kibana_security_session*'); }); it('should properly clean up session expired because of lifespan', async function () { diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts index 77f32063d41be..b9f12a84c9987 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/endpoint_list.ts @@ -242,11 +242,14 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const tableData = await pageObjects.endpointPageUtils.tableData('endpointListTable'); expect(tableData).to.eql(expectedDataFromQuery); }); - it('for the kql query: HostDetails.Endpoint.policy.applied.id : "C2A9093E-E289-4C0A-AA44-8C32A414FA7A", table shows 2 items', async () => { + it('for the kql filtering for policy.applied.id : "C2A9093E-E289-4C0A-AA44-8C32A414FA7A", table shows 2 items', async () => { const adminSearchBar = await testSubjects.find('adminSearchBar'); await adminSearchBar.clearValueWithKeyboard(); await adminSearchBar.type( - 'HostDetails.Endpoint.policy.applied.id : "C2A9093E-E289-4C0A-AA44-8C32A414FA7A" ' + // schema depends on applied package + 'Endpoint.policy.applied.id : "C2A9093E-E289-4C0A-AA44-8C32A414FA7A" ' + + 'or ' + + 'HostDetails.Endpoint.policy.applied.id : "C2A9093E-E289-4C0A-AA44-8C32A414FA7A" ' ); const querySubmitButton = await testSubjects.find('querySubmitButton'); await querySubmitButton.click(); diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts index 50f3374b7b84d..827310fe1d4c1 100644 --- a/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts +++ b/x-pack/test/security_solution_endpoint_api_int/apis/metadata.ts @@ -136,7 +136,7 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'xxx') .send({ filters: { - kql: 'not HostDetails.host.ip:10.46.229.234', + kql: 'not (HostDetails.host.ip:10.46.229.234 or host.ip:10.46.229.234)', }, }) .expect(200); @@ -162,7 +162,7 @@ export default function ({ getService }: FtrProviderContext) { }, ], filters: { - kql: `not HostDetails.host.ip:${notIncludedIp}`, + kql: `not (HostDetails.host.ip:${notIncludedIp} or host.ip:${notIncludedIp})`, }, }) .expect(200); @@ -192,7 +192,7 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'xxx') .send({ filters: { - kql: `HostDetails.host.os.Ext.variant:${variantValue}`, + kql: `HostDetails.host.os.Ext.variant:${variantValue} or host.os.Ext.variant:${variantValue}`, }, }) .expect(200); @@ -214,7 +214,7 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'xxx') .send({ filters: { - kql: `HostDetails.host.ip:${targetEndpointIp}`, + kql: `HostDetails.host.ip:${targetEndpointIp} or host.ip:${targetEndpointIp}`, }, }) .expect(200); @@ -236,7 +236,7 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'xxx') .send({ filters: { - kql: `not HostDetails.Endpoint.policy.applied.status:success`, + kql: `not (HostDetails.Endpoint.policy.applied.status:success or Endpoint.policy.applied.status:success)`, }, }) .expect(200); @@ -257,7 +257,7 @@ export default function ({ getService }: FtrProviderContext) { .set('kbn-xsrf', 'xxx') .send({ filters: { - kql: `HostDetails.elastic.agent.id:${targetElasticAgentId}`, + kql: `HostDetails.elastic.agent.id:${targetElasticAgentId} or elastic.agent.id:${targetElasticAgentId}`, }, }) .expect(200); diff --git a/x-pack/test/send_search_to_background_integration/services/search_sessions.ts b/x-pack/test/send_search_to_background_integration/services/search_sessions.ts index 7041de6462243..f3bac27cd2ac9 100644 --- a/x-pack/test/send_search_to_background_integration/services/search_sessions.ts +++ b/x-pack/test/send_search_to_background_integration/services/search_sessions.ts @@ -58,23 +58,27 @@ export function SearchSessionsProvider({ getService }: FtrProviderContext) { } public async viewSearchSessions() { + log.debug('viewSearchSessions'); await this.ensurePopoverOpened(); await testSubjects.click('searchSessionIndicatorviewSearchSessionsLink'); } public async save() { + log.debug('save the search session'); await this.ensurePopoverOpened(); await testSubjects.click('searchSessionIndicatorSaveBtn'); await this.ensurePopoverClosed(); } public async cancel() { + log.debug('cancel the search session'); await this.ensurePopoverOpened(); await testSubjects.click('searchSessionIndicatorCancelBtn'); await this.ensurePopoverClosed(); } public async refresh() { + log.debug('refresh the status'); await this.ensurePopoverOpened(); await testSubjects.click('searchSessionIndicatorRefreshBtn'); await this.ensurePopoverClosed(); @@ -85,8 +89,12 @@ export function SearchSessionsProvider({ getService }: FtrProviderContext) { } private async ensurePopoverOpened() { + log.debug('ensurePopoverOpened'); const isAlreadyOpen = await testSubjects.exists(SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ); - if (isAlreadyOpen) return; + if (isAlreadyOpen) { + log.debug('Popover is already open'); + return; + } return retry.waitFor(`searchSessions popover opened`, async () => { await testSubjects.click(SEARCH_SESSION_INDICATOR_TEST_SUBJ); return await testSubjects.exists(SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ); @@ -94,6 +102,7 @@ export function SearchSessionsProvider({ getService }: FtrProviderContext) { } private async ensurePopoverClosed() { + log.debug('ensurePopoverClosed'); const isAlreadyClosed = !(await testSubjects.exists( SEARCH_SESSIONS_POPOVER_CONTENT_TEST_SUBJ )); @@ -110,7 +119,7 @@ export function SearchSessionsProvider({ getService }: FtrProviderContext) { * Alternatively, a test can navigate to `Management > Search Sessions` and use the UI to delete any created tests. */ public async deleteAllSearchSessions() { - log.debug('Deleting created searcg sessions'); + log.debug('Deleting created search sessions'); // ignores 409 errs and keeps retrying await retry.tryForTime(10000, async () => { const { body } = await supertest diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background.ts b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background.ts index 3e417551c3cb9..35ee15472f346 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background.ts @@ -80,6 +80,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); // load URL to restore a saved session + // TODO: replace with clicking on "Re-run link" const url = await browser.getCurrentUrl(); const savedSessionURL = `${url}&searchSessionId=${savedSessionId}`; await browser.get(savedSessionURL); diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background_relative_time.ts b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background_relative_time.ts index 25291fd74b322..5d5cdb29523bd 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background_relative_time.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background_relative_time.ts @@ -19,13 +19,13 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'home', 'timePicker', 'maps', + 'searchSessionsManagement', ]); const dashboardPanelActions = getService('dashboardPanelActions'); const inspector = getService('inspector'); const pieChart = getService('pieChart'); const find = getService('find'); const dashboardExpect = getService('dashboardExpect'); - const browser = getService('browser'); const searchSessions = getService('searchSessions'); describe('send to background with relative time', () => { @@ -59,23 +59,20 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await PageObjects.timePicker.pauseAutoRefresh(); // sample data has auto-refresh on await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.dashboard.waitForRenderComplete(); - await checkSampleDashboardLoaded(); await searchSessions.expectState('completed'); await searchSessions.save(); await searchSessions.expectState('backgroundCompleted'); - const savedSessionId = await dashboardPanelActions.getSearchSessionIdByTitle( - '[Flights] Airline Carrier' - ); - const resolvedTimeRange = await getResolvedTimeRangeFromPanel('[Flights] Airline Carrier'); + + await checkSampleDashboardLoaded(); // load URL to restore a saved session - const url = await browser.getCurrentUrl(); - const savedSessionURL = `${url}&searchSessionId=${savedSessionId}` - .replace('now-24h', `'${resolvedTimeRange.gte}'`) - .replace('now', `'${resolvedTimeRange.lte}'`); - log.debug('Trying to restore session by URL:', savedSessionId); - await browser.get(savedSessionURL); + await PageObjects.searchSessionsManagement.goTo(); + const searchSessionList = await PageObjects.searchSessionsManagement.getList(); + + // navigate to dashboard + await searchSessionList[0].view(); + await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.dashboard.waitForRenderComplete(); await checkSampleDashboardLoaded(); @@ -87,16 +84,6 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // HELPERS - async function getResolvedTimeRangeFromPanel( - panelTitle: string - ): Promise<{ gte: string; lte: string }> { - await dashboardPanelActions.openInspectorByTitle(panelTitle); - await inspector.openInspectorRequestsView(); - await (await inspector.getOpenRequestDetailRequestButton()).click(); - const request = JSON.parse(await inspector.getCodeEditorValue()); - return request.query.bool.filter.find((f: any) => f.range).range.timestamp; - } - async function checkSampleDashboardLoaded() { log.debug('Checking no error labels'); await testSubjects.missingOrFail('embeddableErrorLabel'); diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts b/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts index e3797550984aa..9efba47a8a606 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/management/search_sessions/sessions_management.ts @@ -9,6 +9,7 @@ import { FtrProviderContext } from '../../../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); + const retry = getService('retry'); const PageObjects = getPageObjects([ 'common', 'header', @@ -18,13 +19,17 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ]); const searchSessions = getService('searchSessions'); const esArchiver = getService('esArchiver'); - const retry = getService('retry'); + const log = getService('log'); // FLAKY: https://github.com/elastic/kibana/issues/89069 - describe.skip('Search search sessions Management UI', () => { + describe.skip('Search sessions Management UI', () => { describe('New search sessions', () => { before(async () => { await PageObjects.common.navigateToApp('dashboard'); + log.debug('wait for dashboard landing page'); + retry.tryForTime(10000, async () => { + testSubjects.existOrFail('dashboardLandingPage'); + }); }); after(async () => { @@ -32,6 +37,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('Saves a session and verifies it in the Management app', async () => { + log.debug('loading the "Not Delayed" dashboard'); await PageObjects.dashboard.loadSavedDashboard('Not Delayed'); await PageObjects.dashboard.waitForRenderComplete(); await searchSessions.expectState('completed'); @@ -47,6 +53,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); // find there is only one item in the table which is the newly saved session + log.debug('find the newly saved session'); const searchSessionList = await PageObjects.searchSessionsManagement.getList(); expect(searchSessionList.length).to.be(1); expect(searchSessionList[0].expires).not.to.eql('--'); @@ -63,6 +70,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await searchSessions.expectState('restored'); }); + // NOTE: this test depends on the previous one passing it('Reloads as new session from management', async () => { await PageObjects.searchSessionsManagement.goTo(); diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index 4c77a12c5c01a..c5723d10109f6 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -21,6 +21,7 @@ { "path": "../../src/plugins/es_ui_shared/tsconfig.json" }, { "path": "../../src/plugins/expressions/tsconfig.json" }, { "path": "../../src/plugins/home/tsconfig.json" }, + { "path": "../../src/plugins/kibana_overview/tsconfig.json" }, { "path": "../../src/plugins/kibana_react/tsconfig.json" }, { "path": "../../src/plugins/kibana_usage_collection/tsconfig.json" }, { "path": "../../src/plugins/kibana_utils/tsconfig.json" }, @@ -40,6 +41,7 @@ { "path": "../plugins/actions/tsconfig.json" }, { "path": "../plugins/alerts/tsconfig.json" }, + { "path": "../plugins/code/tsconfig.json" }, { "path": "../plugins/console_extensions/tsconfig.json" }, { "path": "../plugins/data_enhanced/tsconfig.json" }, { "path": "../plugins/enterprise_search/tsconfig.json" }, @@ -50,8 +52,10 @@ { "path": "../plugins/event_log/tsconfig.json" }, { "path": "../plugins/licensing/tsconfig.json" }, { "path": "../plugins/lens/tsconfig.json" }, + { "path": "../plugins/ml/tsconfig.json" }, { "path": "../plugins/task_manager/tsconfig.json" }, { "path": "../plugins/telemetry_collection_xpack/tsconfig.json" }, + { "path": "../plugins/transform/tsconfig.json" }, { "path": "../plugins/triggers_actions_ui/tsconfig.json" }, { "path": "../plugins/ui_actions_enhanced/tsconfig.json" }, { "path": "../plugins/spaces/tsconfig.json" }, @@ -62,9 +66,15 @@ { "path": "../plugins/cloud/tsconfig.json" }, { "path": "../plugins/saved_objects_tagging/tsconfig.json" }, { "path": "../plugins/global_search_bar/tsconfig.json" }, + { "path": "../plugins/observability/tsconfig.json" }, { "path": "../plugins/ingest_pipelines/tsconfig.json" }, { "path": "../plugins/license_management/tsconfig.json" }, + { "path": "../plugins/snapshot_restore/tsconfig.json" }, + { "path": "../plugins/grokdebugger/tsconfig.json" }, { "path": "../plugins/painless_lab/tsconfig.json" }, - { "path": "../plugins/watcher/tsconfig.json" } + { "path": "../plugins/upgrade_assistant/tsconfig.json" }, + { "path": "../plugins/watcher/tsconfig.json" }, + { "path": "../plugins/runtime_fields/tsconfig.json" }, + { "path": "../plugins/index_management/tsconfig.json" } ] } diff --git a/x-pack/test/upgrade_assistant_integration/upgrade_assistant/reindexing.js b/x-pack/test/upgrade_assistant_integration/upgrade_assistant/reindexing.js index f57d2184d4b8d..c70b90b384d13 100644 --- a/x-pack/test/upgrade_assistant_integration/upgrade_assistant/reindexing.js +++ b/x-pack/test/upgrade_assistant_integration/upgrade_assistant/reindexing.js @@ -71,6 +71,8 @@ export default function ({ getService }) { expect(indexSummary[newIndexName]).to.be.an('object'); // The original index name is aliased to the new one expect(indexSummary[newIndexName].aliases.dummydata).to.be.an('object'); + // Verify mappings exist on new index + expect(indexSummary[newIndexName].mappings.properties).to.be.an('object'); // The number of documents in the new index matches what we expect expect((await es.count({ index: lastState.newIndexName })).body.count).to.be(3); diff --git a/x-pack/tsconfig.json b/x-pack/tsconfig.json index 7ff1e10e8a58b..624a65bb4df82 100644 --- a/x-pack/tsconfig.json +++ b/x-pack/tsconfig.json @@ -9,6 +9,7 @@ "plugins/apm/scripts/**/*", "plugins/canvas/**/*", "plugins/console_extensions/**/*", + "plugins/code/**/*", "plugins/data_enhanced/**/*", "plugins/discover_enhanced/**/*", "plugins/dashboard_enhanced/**/*", @@ -16,6 +17,7 @@ "plugins/global_search_providers/**/*", "plugins/graph/**/*", "plugins/features/**/*", + "plugins/file_upload/**/*", "plugins/embeddable_enhanced/**/*", "plugins/event_log/**/*", "plugins/enterprise_search/**/*", @@ -24,11 +26,14 @@ "plugins/maps/**/*", "plugins/maps_file_upload/**/*", "plugins/maps_legacy_licensing/**/*", + "plugins/ml/**/*", + "plugins/observability/**/*", "plugins/reporting/**/*", "plugins/searchprofiler/**/*", "plugins/security_solution/cypress/**/*", "plugins/task_manager/**/*", "plugins/telemetry_collection_xpack/**/*", + "plugins/transform/**/*", "plugins/translations/**/*", "plugins/triggers_actions_ui/**/*", "plugins/ui_actions_enhanced/**/*", @@ -42,8 +47,13 @@ "plugins/global_search_bar/**/*", "plugins/ingest_pipelines/**/*", "plugins/license_management/**/*", + "plugins/snapshot_restore/**/*", "plugins/painless_lab/**/*", "plugins/watcher/**/*", + "plugins/runtime_fields/**/*", + "plugins/index_management/**/*", + "plugins/grokdebugger/**/*", + "plugins/upgrade_assistant/**/*", "test/**/*" ], "compilerOptions": { @@ -63,8 +73,10 @@ { "path": "../src/plugins/es_ui_shared/tsconfig.json" }, { "path": "../src/plugins/expressions/tsconfig.json" }, { "path": "../src/plugins/home/tsconfig.json" }, + { "path": "../src/plugins/index_pattern_management/tsconfig.json" }, { "path": "../src/plugins/inspector/tsconfig.json" }, { "path": "../src/plugins/kibana_legacy/tsconfig.json" }, + { "path": "../src/plugins/kibana_overview/tsconfig.json" }, { "path": "../src/plugins/kibana_react/tsconfig.json" }, { "path": "../src/plugins/kibana_usage_collection/tsconfig.json" }, { "path": "../src/plugins/kibana_utils/tsconfig.json" }, @@ -84,12 +96,12 @@ { "path": "../src/plugins/ui_actions/tsconfig.json" }, { "path": "../src/plugins/url_forwarding/tsconfig.json" }, { "path": "../src/plugins/usage_collection/tsconfig.json" }, - { "path": "../src/plugins/index_pattern_management/tsconfig.json" }, - { "path": "./plugins/actions/tsconfig.json"}, - { "path": "./plugins/alerts/tsconfig.json"}, + { "path": "./plugins/actions/tsconfig.json" }, + { "path": "./plugins/alerts/tsconfig.json" }, { "path": "./plugins/beats_management/tsconfig.json" }, { "path": "./plugins/canvas/tsconfig.json" }, { "path": "./plugins/cloud/tsconfig.json" }, + { "path": "./plugins/code/tsconfig.json" }, { "path": "./plugins/console_extensions/tsconfig.json" }, { "path": "./plugins/data_enhanced/tsconfig.json" }, { "path": "./plugins/discover_enhanced/tsconfig.json" }, @@ -98,32 +110,37 @@ { "path": "./plugins/enterprise_search/tsconfig.json" }, { "path": "./plugins/event_log/tsconfig.json" }, { "path": "./plugins/features/tsconfig.json" }, + { "path": "./plugins/file_upload/tsconfig.json" }, { "path": "./plugins/global_search_bar/tsconfig.json" }, { "path": "./plugins/global_search_providers/tsconfig.json" }, { "path": "./plugins/global_search/tsconfig.json" }, { "path": "./plugins/graph/tsconfig.json" }, + { "path": "./plugins/grokdebugger/tsconfig.json" }, + { "path": "./plugins/ingest_pipelines/tsconfig.json" }, { "path": "./plugins/lens/tsconfig.json" }, { "path": "./plugins/license_management/tsconfig.json" }, { "path": "./plugins/licensing/tsconfig.json" }, { "path": "./plugins/maps_file_upload/tsconfig.json" }, { "path": "./plugins/maps_legacy_licensing/tsconfig.json" }, { "path": "./plugins/maps/tsconfig.json" }, + { "path": "./plugins/ml/tsconfig.json" }, + { "path": "./plugins/observability/tsconfig.json" }, { "path": "./plugins/painless_lab/tsconfig.json" }, { "path": "./plugins/saved_objects_tagging/tsconfig.json" }, { "path": "./plugins/searchprofiler/tsconfig.json" }, { "path": "./plugins/security/tsconfig.json" }, + { "path": "./plugins/snapshot_restore/tsconfig.json" }, { "path": "./plugins/spaces/tsconfig.json" }, { "path": "./plugins/stack_alerts/tsconfig.json" }, { "path": "./plugins/task_manager/tsconfig.json" }, { "path": "./plugins/telemetry_collection_xpack/tsconfig.json" }, + { "path": "./plugins/transform/tsconfig.json" }, { "path": "./plugins/translations/tsconfig.json" }, - { "path": "./plugins/triggers_actions_ui/tsconfig.json"}, - { "path": "./plugins/stack_alerts/tsconfig.json"}, - { "path": "./plugins/ingest_pipelines/tsconfig.json"}, - { "path": "./plugins/license_management/tsconfig.json" }, - { "path": "./plugins/painless_lab/tsconfig.json" }, { "path": "./plugins/triggers_actions_ui/tsconfig.json" }, { "path": "./plugins/ui_actions_enhanced/tsconfig.json" }, + { "path": "./plugins/upgrade_assistant/tsconfig.json" }, + { "path": "./plugins/runtime_fields/tsconfig.json" }, + { "path": "./plugins/index_management/tsconfig.json" }, { "path": "./plugins/watcher/tsconfig.json" } ] } diff --git a/x-pack/tsconfig.refs.json b/x-pack/tsconfig.refs.json index eff35147a1da9..fbe8d7dd9af7c 100644 --- a/x-pack/tsconfig.refs.json +++ b/x-pack/tsconfig.refs.json @@ -1,11 +1,12 @@ { "include": [], "references": [ - { "path": "./plugins/actions/tsconfig.json"}, - { "path": "./plugins/alerts/tsconfig.json"}, + { "path": "./plugins/actions/tsconfig.json" }, + { "path": "./plugins/alerts/tsconfig.json" }, { "path": "./plugins/beats_management/tsconfig.json" }, { "path": "./plugins/canvas/tsconfig.json" }, { "path": "./plugins/cloud/tsconfig.json" }, + { "path": "./plugins/code/tsconfig.json" }, { "path": "./plugins/console_extensions/tsconfig.json" }, { "path": "./plugins/dashboard_enhanced/tsconfig.json" }, { "path": "./plugins/data_enhanced/tsconfig.json" }, @@ -13,41 +14,40 @@ { "path": "./plugins/embeddable_enhanced/tsconfig.json" }, { "path": "./plugins/encrypted_saved_objects/tsconfig.json" }, { "path": "./plugins/enterprise_search/tsconfig.json" }, - { "path": "./plugins/event_log/tsconfig.json"}, + { "path": "./plugins/event_log/tsconfig.json" }, { "path": "./plugins/features/tsconfig.json" }, + { "path": "./plugins/file_upload/tsconfig.json" }, { "path": "./plugins/global_search_bar/tsconfig.json" }, { "path": "./plugins/global_search_providers/tsconfig.json" }, { "path": "./plugins/global_search/tsconfig.json" }, { "path": "./plugins/graph/tsconfig.json" }, + { "path": "./plugins/grokdebugger/tsconfig.json" }, + { "path": "./plugins/ingest_pipelines/tsconfig.json" }, { "path": "./plugins/lens/tsconfig.json" }, { "path": "./plugins/license_management/tsconfig.json" }, { "path": "./plugins/licensing/tsconfig.json" }, { "path": "./plugins/maps_file_upload/tsconfig.json" }, { "path": "./plugins/maps_legacy_licensing/tsconfig.json" }, { "path": "./plugins/maps/tsconfig.json" }, + { "path": "./plugins/ml/tsconfig.json" }, + { "path": "./plugins/observability/tsconfig.json" }, { "path": "./plugins/painless_lab/tsconfig.json" }, { "path": "./plugins/reporting/tsconfig.json" }, { "path": "./plugins/saved_objects_tagging/tsconfig.json" }, { "path": "./plugins/searchprofiler/tsconfig.json" }, { "path": "./plugins/security/tsconfig.json" }, + { "path": "./plugins/snapshot_restore/tsconfig.json" }, { "path": "./plugins/spaces/tsconfig.json" }, - { "path": "./plugins/stack_alerts/tsconfig.json"}, + { "path": "./plugins/stack_alerts/tsconfig.json" }, { "path": "./plugins/task_manager/tsconfig.json" }, { "path": "./plugins/telemetry_collection_xpack/tsconfig.json" }, + { "path": "./plugins/transform/tsconfig.json" }, { "path": "./plugins/translations/tsconfig.json" }, - { "path": "./plugins/triggers_actions_ui/tsconfig.json"}, - { "path": "./plugins/spaces/tsconfig.json" }, - { "path": "./plugins/security/tsconfig.json" }, - { "path": "./plugins/stack_alerts/tsconfig.json"}, - { "path": "./plugins/encrypted_saved_objects/tsconfig.json" }, - { "path": "./plugins/beats_management/tsconfig.json" }, - { "path": "./plugins/cloud/tsconfig.json" }, - { "path": "./plugins/saved_objects_tagging/tsconfig.json" }, - { "path": "./plugins/global_search_bar/tsconfig.json" }, - { "path": "./plugins/ingest_pipelines/tsconfig.json" }, - { "path": "./plugins/license_management/tsconfig.json" }, - { "path": "./plugins/painless_lab/tsconfig.json" }, + { "path": "./plugins/triggers_actions_ui/tsconfig.json" }, { "path": "./plugins/ui_actions_enhanced/tsconfig.json" }, + { "path": "./plugins/upgrade_assistant/tsconfig.json" }, + { "path": "./plugins/runtime_fields/tsconfig.json" }, + { "path": "./plugins/index_management/tsconfig.json" }, { "path": "./plugins/watcher/tsconfig.json" } ] } diff --git a/yarn.lock b/yarn.lock index 1b8cc2f8dc6e2..9be907922c2a6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6131,10 +6131,10 @@ dependencies: "@types/node" "*" -"@types/mocha@^7.0.2": - version "7.0.2" - resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-7.0.2.tgz#b17f16cf933597e10d6d78eae3251e692ce8b0ce" - integrity sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w== +"@types/mocha@^8.2.0": + version "8.2.0" + resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-8.2.0.tgz#3eb56d13a1de1d347ecb1957c6860c911704bc44" + integrity sha512-/Sge3BymXo4lKc31C8OINJgXLaw+7vL1/L1pGiBNpGrBiT8FQiaFpSYV0uhTaG4y78vcMBTMFsWaHDvuD+xGzQ== "@types/mock-fs@^4.10.0": version "4.10.0" @@ -6988,6 +6988,11 @@ "@typescript-eslint/types" "4.3.0" eslint-visitor-keys "^2.0.0" +"@ungap/promise-all-settled@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" + integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== + "@webassemblyjs/ast@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" @@ -7355,6 +7360,14 @@ aggregate-error@^3.0.0: clean-stack "^2.0.0" indent-string "^4.0.0" +aggregate-error@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" + integrity sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA== + dependencies: + clean-stack "^2.0.0" + indent-string "^4.0.0" + airbnb-js-shims@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/airbnb-js-shims/-/airbnb-js-shims-2.2.1.tgz#db481102d682b98ed1daa4c5baa697a05ce5c040" @@ -7492,10 +7505,10 @@ ansi-align@^3.0.0: dependencies: string-width "^3.0.0" -ansi-colors@3.2.3, ansi-colors@^3.0.0: - version "3.2.3" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" - integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw== +ansi-colors@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== ansi-colors@^1.0.1: version "1.1.0" @@ -7504,6 +7517,11 @@ ansi-colors@^1.0.1: dependencies: ansi-wrap "^0.1.0" +ansi-colors@^3.0.0: + version "3.2.4" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf" + integrity sha512-hHUXGagefjN2iRrID63xckIvotOXOojhQKWIPUZ4mNUZ9nLZW+7FMNoE1lOkEhNWYsx/7ysGIuJYCiMAA9FnrA== + ansi-escapes@^3.0.0, ansi-escapes@^3.1.0, ansi-escapes@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-3.2.0.tgz#8780b98ff9dbf5638152d1f1fe5c1d7b4442976b" @@ -10193,7 +10211,7 @@ cheerio@^1.0.0-rc.3: lodash "^4.15.0" parse5 "^3.0.1" -chokidar@2.1.2, chokidar@3.3.0, chokidar@^2.0.0, chokidar@^2.0.4, chokidar@^2.1.1, chokidar@^2.1.2, chokidar@^2.1.8, chokidar@^3.2.2, chokidar@^3.3.0, chokidar@^3.4.0, chokidar@^3.4.1, chokidar@^3.4.3: +chokidar@2.1.2, chokidar@3.4.3, chokidar@^2.0.0, chokidar@^2.0.4, chokidar@^2.1.1, chokidar@^2.1.2, chokidar@^2.1.8, chokidar@^3.2.2, chokidar@^3.3.0, chokidar@^3.4.0, chokidar@^3.4.1, chokidar@^3.4.3: version "3.4.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.4.3.tgz#c1df38231448e45ca4ac588e6c79573ba6a57d5b" integrity sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ== @@ -12093,19 +12111,19 @@ debug@3.1.0: dependencies: ms "2.0.0" -debug@3.2.6, debug@3.X, debug@^3.0.0, debug@^3.0.1, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6: - version "3.2.6" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" - integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== +debug@3.X, debug@^3.0.0, debug@^3.0.1, debug@^3.1.0, debug@^3.1.1, debug@^3.2.5, debug@^3.2.6: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== dependencies: ms "^2.1.1" -debug@4, debug@4.1.1, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" - integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== +debug@4, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0: + version "4.3.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" + integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== dependencies: - ms "^2.1.1" + ms "2.1.2" debug@4.1.0: version "4.1.0" @@ -12114,10 +12132,17 @@ debug@4.1.0: dependencies: ms "^2.1.1" -debug@^4.2.0: - version "4.3.1" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" - integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== +debug@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + +debug@4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.2.0.tgz#7f150f93920e94c58f5574c2fd01a3110effe7f1" + integrity sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg== dependencies: ms "2.1.2" @@ -12139,6 +12164,11 @@ decamelize@^1.0.0, decamelize@^1.1.0, decamelize@^1.1.1, decamelize@^1.1.2, deca resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= +decamelize@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" + integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== + decimal.js@^10.2.0: version "10.2.1" resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.1.tgz#238ae7b0f0c793d3e3cea410108b35a2c01426a3" @@ -12579,20 +12609,20 @@ diff-sequences@^26.6.2: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-26.6.2.tgz#48ba99157de1923412eed41db6b6d4aa9ca7c0b1" integrity sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q== -diff@3.5.0, diff@^3.0.0, diff@^3.5.0: - version "3.5.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" - integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== +diff@4.0.2, diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== diff@^1.3.2: version "1.4.0" resolved "https://registry.yarnpkg.com/diff/-/diff-1.4.0.tgz#7f28d2eb9ee7b15a97efd89ce63dcfdaa3ccbabf" integrity sha1-fyjS657nsVqX79ic5j3P2qPMur8= -diff@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.1.tgz#0c667cb467ebbb5cea7f14f135cc2dba7780a8ff" - integrity sha512-s2+XdvhPCOF01LRQBC8hf4vhbVmI2CGS5aZnxLJlT5FtdhPCDFq80q++zK2KlrVorVDdL5BOGZ/VfLrVtYNF+Q== +diff@^3.0.0, diff@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== diffie-hellman@^5.0.0: version "5.0.2" @@ -13518,21 +13548,21 @@ escape-html@^1.0.3, escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= -escape-string-regexp@1.0.5, escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" - integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= - escape-string-regexp@2.0.0, escape-string-regexp@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344" integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w== -escape-string-regexp@^4.0.0: +escape-string-regexp@4.0.0, escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== +escape-string-regexp@^1.0.0, escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + escodegen@^1.11.0, escodegen@^1.11.1, escodegen@^1.12.0, escodegen@^1.14.1: version "1.14.3" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" @@ -14561,13 +14591,6 @@ find-root@^1.1.0: resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== -find-up@3.0.0, find-up@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" - integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== - dependencies: - locate-path "^3.0.0" - find-up@4.1.0, find-up@^4.0.0, find-up@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" @@ -14576,6 +14599,14 @@ find-up@4.1.0, find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" +find-up@5.0.0, find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + find-up@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" @@ -14591,13 +14622,12 @@ find-up@^2.0.0, find-up@^2.1.0: dependencies: locate-path "^2.0.0" -find-up@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" + locate-path "^3.0.0" findup-sync@^2.0.0: version "2.0.0" @@ -14659,12 +14689,10 @@ flat-cache@^3.0.4: flatted "^3.1.0" rimraf "^3.0.2" -flat@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.1.tgz#a392059cc382881ff98642f5da4dde0a959f309b" - integrity sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA== - dependencies: - is-buffer "~2.0.3" +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== flatted@^2.0.0: version "2.0.0" @@ -15406,26 +15434,26 @@ glob@7.1.4: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^6.0.1, glob@^6.0.4: - version "6.0.4" - resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" - integrity sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI= +glob@7.1.6, glob@^7.0.0, glob@^7.0.3, glob@^7.1.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.1, glob@~7.1.4, glob@~7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== dependencies: + fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "2 || 3" + minimatch "^3.0.4" once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.0, glob@^7.0.3, glob@^7.1.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.1, glob@~7.1.4, glob@~7.1.6: - version "7.1.6" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" - integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== +glob@^6.0.1, glob@^6.0.4: + version "6.0.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-6.0.4.tgz#0f08860f6a155127b2fadd4f9ce24b1aab6e4d22" + integrity sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI= dependencies: - fs.realpath "^1.0.0" inflight "^1.0.4" inherits "2" - minimatch "^3.0.4" + minimatch "2 || 3" once "^1.3.0" path-is-absolute "^1.0.0" @@ -17378,7 +17406,7 @@ is-buffer@^1.0.2, is-buffer@^1.1.0, is-buffer@^1.1.4, is-buffer@^1.1.5, is-buffe resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-buffer@^2.0.0, is-buffer@~2.0.3: +is-buffer@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.3.tgz#4ecf3fcf749cbd1e472689e109ac66261a25e725" integrity sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw== @@ -17713,7 +17741,7 @@ is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" integrity sha1-caUMhCnfync8kqOQpKA7OfzVHT4= -is-plain-obj@^2.0.0: +is-plain-obj@^2.0.0, is-plain-obj@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== @@ -18817,7 +18845,7 @@ js-yaml@3.13.1, js-yaml@^3.10.0, js-yaml@^3.13.1, js-yaml@^3.9.0, js-yaml@~3.13. argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^3.14.0, js-yaml@~3.14.0: +js-yaml@3.14.0, js-yaml@^3.14.0, js-yaml@~3.14.0: version "3.14.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.0.tgz#a7a34170f26a21bb162424d8adacb4113a69e482" integrity sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A== @@ -19949,12 +19977,12 @@ log-symbols@2.2.0, log-symbols@^2.1.0, log-symbols@^2.2.0: dependencies: chalk "^2.0.1" -log-symbols@3.0.0, log-symbols@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" - integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== +log-symbols@4.0.0, log-symbols@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920" + integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA== dependencies: - chalk "^2.4.2" + chalk "^4.0.0" log-symbols@^1.0.2: version "1.0.2" @@ -19963,12 +19991,12 @@ log-symbols@^1.0.2: dependencies: chalk "^1.0.0" -log-symbols@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.0.0.tgz#69b3cc46d20f448eccdb75ea1fa733d9e821c920" - integrity sha512-FN8JBzLx6CzeMrB0tg6pqlGU1wCrXW+ZXGH481kfsBqer0hToTIiHdjH4Mq8xJUbvATujKCvaREGWpGUionraA== +log-symbols@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" + integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== dependencies: - chalk "^4.0.0" + chalk "^2.4.2" log-update@2.3.0, log-update@^2.3.0: version "2.3.0" @@ -20837,13 +20865,6 @@ mkdirp@0.5.1: dependencies: minimist "0.0.8" -mkdirp@0.5.3: - version "0.5.3" - resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.3.tgz#5a514b7179259287952881e94410ec5465659f8c" - integrity sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg== - dependencies: - minimist "^1.2.5" - mkdirp@0.5.4, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@^0.5.3, mkdirp@^0.5.4, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.4.tgz#fd01504a6797ec5c9be81ff43d204961ed64a512" @@ -20861,10 +20882,10 @@ mkdirp@^1.0.3, mkdirp@^1.0.4, mkdirp@~1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -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" - integrity sha512-qeDvKlZyAH2YJE1vhryvjUQ06t2hcnwwu4k5Ddwn0GQINhgEYFhlGM0DwYCVUHq5cuo32qAW6HDsTHt7zz99Ng== +mocha-junit-reporter@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/mocha-junit-reporter/-/mocha-junit-reporter-2.0.0.tgz#3bf990fce7a42c0d2b718f188553a25d9f24b9a2" + integrity sha512-20HoWh2HEfhqmigfXOKUhZQyX23JImskc37ZOhIjBKoBEsb+4cAFRJpAVhFpnvsztLklW/gFVzsrobjLwmX4lA== dependencies: debug "^2.2.0" md5 "^2.1.0" @@ -20872,80 +20893,80 @@ mocha-junit-reporter@^1.23.1: strip-ansi "^4.0.0" xml "^1.0.0" -mocha@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.1.1.tgz#89fbb30d09429845b1bb893a830bf5771049a441" - integrity sha512-3qQsu3ijNS3GkWcccT5Zw0hf/rWvu1fTN9sPvEd81hlwsr30GX2GcDSSoBxo24IR8FelmrAydGC6/1J5QQP4WA== +mocha@^8.2.1: + version "8.2.1" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-8.2.1.tgz#f2fa68817ed0e53343d989df65ccd358bc3a4b39" + integrity sha512-cuLBVfyFfFqbNR0uUKbDGXKGk+UDFe6aR4os78XIrMQpZl/nv7JYHcvP5MFIAb374b2zFXsdgEGwmzMtP0Xg8w== dependencies: - ansi-colors "3.2.3" + "@ungap/promise-all-settled" "1.1.2" + ansi-colors "4.1.1" browser-stdout "1.3.1" - chokidar "3.3.0" - debug "3.2.6" - diff "3.5.0" - escape-string-regexp "1.0.5" - find-up "3.0.0" - glob "7.1.3" + chokidar "3.4.3" + debug "4.2.0" + diff "4.0.2" + escape-string-regexp "4.0.0" + find-up "5.0.0" + glob "7.1.6" growl "1.10.5" he "1.2.0" - js-yaml "3.13.1" - log-symbols "3.0.0" + js-yaml "3.14.0" + log-symbols "4.0.0" minimatch "3.0.4" - mkdirp "0.5.3" - ms "2.1.1" - node-environment-flags "1.0.6" - object.assign "4.1.0" - strip-json-comments "2.0.1" - supports-color "6.0.0" - which "1.3.1" + ms "2.1.2" + nanoid "3.1.12" + serialize-javascript "5.0.1" + strip-json-comments "3.1.1" + supports-color "7.2.0" + which "2.0.2" wide-align "1.1.3" + workerpool "6.0.2" yargs "13.3.2" yargs-parser "13.1.2" - yargs-unparser "1.6.0" + yargs-unparser "2.0.0" -mochawesome-merge@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/mochawesome-merge/-/mochawesome-merge-4.1.0.tgz#25a514460c6e106e2c8399daaec2d085b6e89b56" - integrity sha512-cDMzSmYu1dRKcr+ZrjjUEuXSiirU8LTG6R8hrAPlZ7zy1EeL7LLpi+a156obxzqh8quTWmYxKtUbTF2PQt0l7A== +mochawesome-merge@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/mochawesome-merge/-/mochawesome-merge-4.2.0.tgz#970ea141165039bfdd6772b1232a1ef6ba99af1a" + integrity sha512-FSMzagh+8hTShhFXdBLE4/zS2WALcDruoD0bmtiwHEjfyQszR/iEGFTgbuM5ewA5At3qeSGwGsT0k2Stt64NdQ== dependencies: fs-extra "^7.0.1" glob "^7.1.6" uuid "^3.3.2" yargs "^15.3.1" -mochawesome-report-generator@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/mochawesome-report-generator/-/mochawesome-report-generator-4.0.1.tgz#0a010d1ecf379eb26ba05300feb59e2665076080" - integrity sha512-hQbmQt8/yCT68GjrQFat+Diqeuka3haNllexYfja1+y0hpwi3yCJwFpQCdWK9ezzcXL3Nu80f2I6SZeyspwsqg== +mochawesome-report-generator@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/mochawesome-report-generator/-/mochawesome-report-generator-5.1.0.tgz#b8809e7661ac31732fa7ae7210380f704f7c68f6" + integrity sha512-5cI4Jh+sD+jIxc7q94961vnm/6VKDI7TFUPt9dps6oAc4y4WMpEeeOlmgKKM81q2eGaviNUYw+acFalGK6EJ9g== dependencies: chalk "^2.4.2" dateformat "^3.0.2" + escape-html "^1.0.3" fs-extra "^7.0.0" fsu "^1.0.2" lodash.isfunction "^3.0.8" opener "^1.4.2" prop-types "^15.7.2" - react "^16.8.5" - react-dom "^16.8.5" tcomb "^3.2.17" tcomb-validation "^3.3.0" validator "^10.11.0" yargs "^13.2.2" -mochawesome@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/mochawesome/-/mochawesome-4.1.0.tgz#57cdb9509a9fc54790884ec867e109644ba949ee" - integrity sha512-U23K19mLqmuBqFyIBl7FVkcIuG/2JYStCj+91WmxK1/psLgHlWBEZsNe25U0x4t1Eqgu55aHv+0utLwzfhnupw== +mochawesome@^6.2.1: + version "6.2.1" + resolved "https://registry.yarnpkg.com/mochawesome/-/mochawesome-6.2.1.tgz#38ed75cf1b2ff86e7c894d6f33499183a49dd0b7" + integrity sha512-zew/N1Gb4JYCTl3scu9i8OW0ay7b0ZOGczCrSW/P+XdBrLntqI5/JlJiYV1/Nn/SY4qahddsIE+qWL8ACNerPA== dependencies: - chalk "^2.4.1" + chalk "^4.0.0" diff "^4.0.1" json-stringify-safe "^5.0.1" lodash.isempty "^4.4.0" lodash.isfunction "^3.0.9" lodash.isobject "^3.0.2" lodash.isstring "^4.0.1" - mochawesome-report-generator "^4.0.0" - strip-ansi "^5.0.0" - uuid "^3.3.2" + mochawesome-report-generator "^5.1.0" + strip-ansi "^6.0.0" + uuid "^7.0.3" mock-fs@^4.12.0: version "4.12.0" @@ -21218,6 +21239,11 @@ nano-time@1.0.0: dependencies: big-integer "^1.6.16" +nanoid@3.1.12: + version "3.1.12" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.12.tgz#6f7736c62e8d39421601e4a0c77623a97ea69654" + integrity sha512-1qstj9z5+x491jfiC4Nelk+f8XBad7LN20PmyWINJEMRSf3wcAjAWysw1qaA8z6NSKe2sjq1hRSDpBH5paCb6A== + nanomatch@^1.2.9: version "1.2.9" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.9.tgz#879f7150cb2dab7a471259066c104eee6e0fa7c2" @@ -21385,14 +21411,6 @@ node-emoji@^1.10.0: dependencies: lodash.toarray "^4.4.0" -node-environment-flags@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz#a30ac13621f6f7d674260a54dede048c3982c088" - integrity sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw== - dependencies: - object.getownpropertydescriptors "^2.0.3" - semver "^5.7.0" - node-fetch@2.1.2, node-fetch@^1.0.1, node-fetch@^2.3.0, node-fetch@^2.6.0, node-fetch@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" @@ -21891,7 +21909,7 @@ object-visit@^1.0.0: dependencies: isobject "^3.0.0" -object.assign@4.1.0, object.assign@^4.0.4, object.assign@^4.1.0: +object.assign@^4.0.4, object.assign@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== @@ -22791,9 +22809,9 @@ path2d-polyfill@^0.4.2: integrity sha512-JSeAnUfkFjl+Ml/EZL898ivMSbGHrOH63Mirx5EQ1ycJiryHDmj1Q7Are+uEPvenVGCUN9YbolfGfyUewJfJEg== pathval@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.0.tgz#b942e6d4bde653005ef6b71361def8727d0645e0" - integrity sha1-uULm1L3mUwBe9rcTYd74cn0GReA= + version "1.1.1" + resolved "https://registry.yarnpkg.com/pathval/-/pathval-1.1.1.tgz#8534e77a77ce7ac5a2512ea21e0fdb8fcf6c3d8d" + integrity sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ== pbf@3.2.1, pbf@^3.0.5, pbf@^3.2.1: version "3.2.1" @@ -23996,7 +24014,7 @@ react-docgen@^5.0.0: node-dir "^0.1.10" strip-indent "^3.0.0" -react-dom@^16.12.0, react-dom@^16.8.3, react-dom@^16.8.5: +react-dom@^16.12.0, react-dom@^16.8.3: version "16.12.0" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.12.0.tgz#0da4b714b8d13c2038c9396b54a92baea633fe11" integrity sha512-LMxFfAGrcS3kETtQaCkTKjMiifahaMySFDn71fZUNpPHZQEzmk/GiAeIT8JSOrHB23fnuCOMruL2a8NYlw+8Gw== @@ -24551,7 +24569,7 @@ react-window@^1.8.5: "@babel/runtime" "^7.0.0" memoize-one ">=3.1.1 <6" -react@^16.12.0, react@^16.8.3, react@^16.8.5: +react@^16.12.0, react@^16.8.3: version "16.12.0" resolved "https://registry.yarnpkg.com/react/-/react-16.12.0.tgz#0c0a9c6a142429e3614834d5a778e18aa78a0b83" integrity sha512-fglqy3k5E+81pA8s+7K0/T3DBCF0ZDOher1elBFzF7O6arXJgzyu/FW+COxFvAWXJoJN9KIZbT2LXlukwphYTA== @@ -26062,6 +26080,13 @@ serialize-error@^2.1.0: resolved "https://registry.yarnpkg.com/serialize-error/-/serialize-error-2.1.0.tgz#50b679d5635cdf84667bdc8e59af4e5b81d5f60a" integrity sha1-ULZ51WNc34Rme9yOWa9OW4HV9go= +serialize-javascript@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-5.0.1.tgz#7886ec848049a462467a97d3d918ebb2aaf934f4" + integrity sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA== + dependencies: + randombytes "^2.1.0" + serialize-javascript@^3.0.0, serialize-javascript@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-3.1.0.tgz#8bf3a9170712664ef2561b44b691eafe399214ea" @@ -27258,16 +27283,16 @@ strip-indent@^3.0.0: dependencies: min-indent "^1.0.0" -strip-json-comments@2.0.1, strip-json-comments@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" - integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= - -strip-json-comments@^3.0.1, strip-json-comments@^3.1.1: +strip-json-comments@3.1.1, strip-json-comments@^3.0.1, strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + strong-log-transformer@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/strong-log-transformer/-/strong-log-transformer-2.1.0.tgz#0f5ed78d325e0421ac6f90f7f10e691d6ae3ae10" @@ -27474,12 +27499,12 @@ supertest@^3.1.0: methods "~1.1.2" superagent "3.8.2" -supports-color@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.0.0.tgz#76cfe742cf1f41bb9b1c29ad03068c05b4c0e40a" - integrity sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg== +supports-color@7.2.0, supports-color@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== dependencies: - has-flag "^3.0.0" + has-flag "^4.0.0" supports-color@^0.2.0: version "0.2.0" @@ -27512,13 +27537,6 @@ supports-color@^7.0.0, supports-color@^7.1.0: dependencies: has-flag "^4.0.0" -supports-color@^7.2.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" - integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== - dependencies: - has-flag "^4.0.0" - supports-hyperlinks@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-1.0.1.tgz#71daedf36cc1060ac5100c351bb3da48c29c0ef7" @@ -28604,9 +28622,9 @@ typescript@4.1.3, typescript@^3.2.2, typescript@^3.3.3333, typescript@^3.5.3, ty integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg== ua-parser-js@^0.7.18: - version "0.7.22" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.22.tgz#960df60a5f911ea8f1c818f3747b99c6e177eae3" - integrity sha512-YUxzMjJ5T71w6a8WWVcMGM6YWOTX27rCoIQgLXiWaxqXSx9D7DNjiGWn1aJIRSQ5qr0xuhra77bSIh6voR/46Q== + version "0.7.23" + resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.23.tgz#704d67f951e13195fbcd3d78818577f5bc1d547b" + integrity sha512-m4hvMLxgGHXG3O3fQVAyyAQpZzDOvwnhOTjYz5Xmr7r/+LpkNy3vJXdVRWgd1TkAb7NGROZuSy96CrlNVjA7KA== uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.5" @@ -29368,6 +29386,11 @@ uuid@^3.1.0, uuid@^3.3.2, uuid@^3.3.3, uuid@^3.4.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== +uuid@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" + integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== + uuid@^8.0.0, uuid@^8.3.0: version "8.3.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-8.3.0.tgz#ab738085ca22dc9a8c92725e459b1d507df5d6ea" @@ -30430,14 +30453,14 @@ which-typed-array@^1.1.2: has-symbols "^1.0.1" is-typed-array "^1.1.3" -which@1, which@1.3.1, which@^1.2.14, which@^1.2.9, which@^1.3.1, which@~1.3.0: +which@1, which@^1.2.14, which@^1.2.9, which@^1.3.1, which@~1.3.0: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== dependencies: isexe "^2.0.0" -which@^2.0.1, which@^2.0.2: +which@2.0.2, which@^2.0.1, which@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== @@ -30549,6 +30572,11 @@ worker-rpc@^0.1.0: dependencies: microevent.ts "~0.1.1" +workerpool@6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.0.2.tgz#e241b43d8d033f1beb52c7851069456039d1d438" + integrity sha512-DSNyvOpFKrNusaaUwk+ej6cBj1bmhLcBfj80elGk+ZIo5JSkq+unB1dLKEOcNfJDZgjGICfhQ0Q5TbP0PvF4+Q== + wrap-ansi@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" @@ -30776,9 +30804,9 @@ xregexp@4.2.4: integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== y18n@^3.2.0, y18n@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" - integrity sha1-bRX7qITAhnnA136I53WegR4H+kE= + version "3.2.2" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.2.tgz#85c901bd6470ce71fc4bb723ad209b70f7f28696" + integrity sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ== y18n@^4.0.0: version "4.0.1" @@ -30844,14 +30872,15 @@ yargs-parser@^20.2.3: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== -yargs-unparser@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f" - integrity sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw== +yargs-unparser@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" + integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== dependencies: - flat "^4.1.0" - lodash "^4.17.15" - yargs "^13.3.0" + camelcase "^6.0.0" + decamelize "^4.0.0" + flat "^5.0.2" + is-plain-obj "^2.1.0" yargs@13.3.2, yargs@^13.2.2, yargs@^13.3.0, yargs@^13.3.2: version "13.3.2"