diff --git a/.ci/teamcity/bootstrap.sh b/.ci/teamcity/bootstrap.sh deleted file mode 100755 index fc57811bb207..000000000000 --- 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 751ec5a03ee7..000000000000 --- 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 387ec0c12678..000000000000 --- 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 f2a4a2056821..000000000000 --- 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 43b65d4e188b..000000000000 --- 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 d7282b310f81..000000000000 --- 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 5c0815bdd955..000000000000 --- 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 62ea3fbe9b04..000000000000 --- 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 6703ffffb565..000000000000 --- 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 136d281647cc..000000000000 --- 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 5acc4b2ae351..000000000000 --- 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 f4e1da502734..000000000000 --- 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 034dd6d647ad..000000000000 --- 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 5799a0b44133..000000000000 --- 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 9d1c898090de..000000000000 --- 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 d465e8f4c52b..000000000000 --- 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 636dc35555f6..000000000000 --- 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 2953661eca1f..000000000000 --- 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 0df9329167ff..000000000000 --- 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 2868db9d067b..000000000000 --- 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 140233f29e6a..000000000000 --- 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 4b8759639223..000000000000 --- 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 26c2c563210e..000000000000 --- 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 5922a72bd5e8..000000000000 --- 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 b900d1b6d6b4..000000000000 --- 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 f5b57ce3b06e..000000000000 --- 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 46048f6c82d5..000000000000 --- 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 f983713e80f4..000000000000 --- 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 63e54987f788..000000000000 --- 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 bcc79e696d78..000000000000 --- 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 09693d7ebdc5..000000000000 --- 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 37241bdbdc07..000000000000 --- 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 3ef14b166335..000000000000 --- 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 28e3c9247f1d..000000000000 --- 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 3b2fb7ea912b..000000000000 --- 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 5e2a6c17c005..000000000000 --- 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 0dee07d00d2b..000000000000 --- 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 4c51d2ff2988..000000000000 --- 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 3570bf01e49c..000000000000 --- 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 ddeef77907c4..000000000000 --- 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 882ad119a3db..000000000000 --- 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 8f607323102b..000000000000 --- 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 b805a2aa6fe6..000000000000 --- 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 06dd3607a679..000000000000 --- 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 f43f84059e25..000000000000 --- 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/.github/CODEOWNERS b/.github/CODEOWNERS index 9e31bd31b403..3884f975c813 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/.teamcity/.editorconfig b/.teamcity/.editorconfig deleted file mode 100644 index db789a8c72de..000000000000 --- 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 c8f78f457596..000000000000 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 77c0bc5bc4cd..000000000000 --- 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 6068d34e7809..000000000000 --- 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 28108d019327..000000000000 --- 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 a550fb9e3d37..000000000000 --- 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 de3f96a5c790..000000000000 --- 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 0a8abf4a149c..000000000000 --- 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 de94e292bd63..000000000000 --- 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 37336316c4c9..000000000000 --- 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 9a8f25f5ba01..000000000000 --- 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 7f19304428d7..000000000000 --- 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 f50a0e903775..000000000000 --- 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 4a4bb8651a7c..000000000000 --- 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 174b73d53de6..000000000000 --- 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 997cf1771cc8..000000000000 --- 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 f0a9c60cf3e4..000000000000 --- 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 f4683e6cf0c1..000000000000 --- 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 2c3b0d348591..000000000000 --- 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 948e2ab5782f..000000000000 --- 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 2429967d2493..000000000000 --- 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 dc2f7756efeb..000000000000 --- 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 61505d4757fa..000000000000 --- 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 1c3b85257c28..000000000000 --- 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 d0c849ff5f99..000000000000 --- 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 9303439d49f3..000000000000 --- 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 f80a97873b24..000000000000 --- 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 4c0307e9eca5..000000000000 --- 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 8e4a7acd77b7..000000000000 --- 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 a04512fb2aba..000000000000 --- 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 50fd73c17ba4..000000000000 --- 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 1c188cd4c175..000000000000 --- 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 931cca2554a2..000000000000 --- 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 2db8314fa44f..000000000000 --- 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 d8189fd35896..000000000000 --- 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 7fbf863820e4..000000000000 --- 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 9506d98cbe50..000000000000 --- 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 c33c9c2678ca..000000000000 --- 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 7d44e41493b2..000000000000 --- 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 6ea15bf5350e..000000000000 --- 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 8246b60823ff..000000000000 --- 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 a5aa47d5cae4..000000000000 --- 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 5cddcf18e067..000000000000 --- 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 1f7f364600e2..000000000000 --- 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 2e3c151950db..000000000000 --- 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 96982b38fb01..000000000000 --- 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 d094cabab86b..000000000000 --- 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 6a1b5a4e9c0f..000000000000 --- 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/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 215a4f3a4ebb..6587d5dc422b 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -419,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 79c603165cae..51e8d1a0b6be 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/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md index 4bbc76b78ba0..f576d795b93a 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.tabbedaggcolumn.aggconfig.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.tabbedaggcolumn.aggconfig.md deleted file mode 100644 index b010667af79e..000000000000 --- 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 86f8b0131204..000000000000 --- 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 578a2b159f9e..000000000000 --- 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 ce20c1c50b98..000000000000 --- 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 28519d95c437..000000000000 --- 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 8256291d368c..000000000000 --- 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 51b1bfa9b436..000000000000 --- 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 19a973b18d75..000000000000 --- 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.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.md index 27a386a714fc..6847808d51bd 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 @@ -66,8 +66,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 @@ -112,6 +110,5 @@ | [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) | \* | | [TimeRange](./kibana-plugin-plugins-data-server.timerange.md) | | 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 9870f7380e16..000000000000 --- 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 4f5a964a07a0..000000000000 --- 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 5e47f745fa17..000000000000 --- 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 8a07e2708066..000000000000 --- 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 d592aeff8963..000000000000 --- 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 55f079c581c8..000000000000 --- 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 1bb055a2a3ce..000000000000 --- 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 b783919a2657..000000000000 --- 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/package.json b/package.json index 920e0c8ba519..a5754e13d2a0 100644 --- a/package.json +++ b/package.json @@ -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", 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 56c75c5aca41..db7c79c7dfc5 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'; diff --git a/packages/kbn-es-archiver/src/actions/load.ts b/packages/kbn-es-archiver/src/actions/load.ts index c32496ad4269..592fb3ff90af 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,7 +92,7 @@ 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... diff --git a/packages/kbn-es-archiver/src/actions/save.ts b/packages/kbn-es-archiver/src/actions/save.ts index d88871f5b422..63b09ef8981a 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 07cbf2aec39f..94b8387d3df0 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 e3114b4e78cf..cba2a25b9e27 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 f101c5d6867f..9176de60544f 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 { 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 3cdf3e24c328..000000000000 --- 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 e28711ad140b..217c5aaf767f 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 7c236214fb03..a375753628e4 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 b2b8d9d310ab..a86359262bd4 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 873ea881382b..75e96fbbc922 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 4d42daa71ef2..de5fbd15c1f6 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 cf6a64306835..57c1efdbb712 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 362e92c5dd76..ba70a8dc2dfb 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 e42928da2566..597db5a980de 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 1b3b09afb783..95633740032f 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 e60e6b6d4771..a526039df45d 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 c1ff48e19744..2e624ef7adba 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 0459a4301cf6..d370e49d0bca 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,9 +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 resp = await client.cat.indices({ index: '.kibana*', format: 'json' }); const isKibanaIndex = (index: string) => /^\.kibana(:?_\d*)?$/.test(index); - return kibanaIndices.map((x: { index: string }) => x.index).filter(isKibanaIndex); + + 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)); @@ -102,27 +113,31 @@ export async function cleanKibanaIndices({ } while (true) { - const resp = await client.deleteByQuery({ - index: `.kibana`, - 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: [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; @@ -140,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-pm/dist/index.js b/packages/kbn-pm/dist/index.js index e8c6fa4d5a01..d641b50537d5 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -59495,12 +59495,12 @@ 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 globby = __webpack_require__(518); +const hasGlob = __webpack_require__(714); +const cpFile = __webpack_require__(716); +const junk = __webpack_require__(726); +const pFilter = __webpack_require__(727); +const CpyError = __webpack_require__(729); const defaultOptions = { ignoreJunk: true @@ -59656,7 +59656,7 @@ module.exports = (source, destination, { "use strict"; -const AggregateError = __webpack_require__(242); +const AggregateError = __webpack_require__(516); module.exports = async ( iterable, @@ -59744,12 +59744,108 @@ module.exports = async ( "use strict"; +const indentString = __webpack_require__(517); +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; + + +/***/ }), +/* 517 */ +/***/ (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)); +}; + + +/***/ }), +/* 518 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + const fs = __webpack_require__(134); -const arrayUnion = __webpack_require__(517); +const arrayUnion = __webpack_require__(519); const glob = __webpack_require__(147); -const fastGlob = __webpack_require__(519); -const dirGlob = __webpack_require__(705); -const gitignore = __webpack_require__(708); +const fastGlob = __webpack_require__(521); +const dirGlob = __webpack_require__(707); +const gitignore = __webpack_require__(710); const DEFAULT_FILTER = () => false; @@ -59894,12 +59990,12 @@ module.exports.gitignore = gitignore; /***/ }), -/* 517 */ +/* 519 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var arrayUniq = __webpack_require__(518); +var arrayUniq = __webpack_require__(520); module.exports = function () { return arrayUniq([].concat.apply([], arguments)); @@ -59907,7 +60003,7 @@ module.exports = function () { /***/ }), -/* 518 */ +/* 520 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59976,10 +60072,10 @@ if ('Set' in global) { /***/ }), -/* 519 */ +/* 521 */ /***/ (function(module, exports, __webpack_require__) { -const pkg = __webpack_require__(520); +const pkg = __webpack_require__(522); module.exports = pkg.async; module.exports.default = pkg.async; @@ -59992,19 +60088,19 @@ module.exports.generateTasks = pkg.generateTasks; /***/ }), -/* 520 */ +/* 522 */ /***/ (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__(523); +var taskManager = __webpack_require__(524); +var reader_async_1 = __webpack_require__(678); +var reader_stream_1 = __webpack_require__(702); +var reader_sync_1 = __webpack_require__(703); +var arrayUtils = __webpack_require__(705); +var streamUtils = __webpack_require__(706); /** * Synchronous API. */ @@ -60070,7 +60166,7 @@ function isString(source) { /***/ }), -/* 521 */ +/* 523 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60108,13 +60204,13 @@ exports.prepare = prepare; /***/ }), -/* 522 */ +/* 524 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var patternUtils = __webpack_require__(523); +var patternUtils = __webpack_require__(525); /** * Generate tasks based on parent directory of each pattern. */ @@ -60205,16 +60301,16 @@ exports.convertPatternGroupToTask = convertPatternGroupToTask; /***/ }), -/* 523 */ +/* 525 */ /***/ (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__(526); var isGlob = __webpack_require__(172); -var micromatch = __webpack_require__(527); +var micromatch = __webpack_require__(529); var GLOBSTAR = '**'; /** * Return true for static pattern. @@ -60360,15 +60456,15 @@ exports.matchAny = matchAny; /***/ }), -/* 524 */ +/* 526 */ /***/ (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__(527); +var pathDirname = __webpack_require__(528); var isWin32 = __webpack_require__(121).platform() === 'win32'; module.exports = function globParent(str) { @@ -60391,7 +60487,7 @@ module.exports = function globParent(str) { /***/ }), -/* 525 */ +/* 527 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -60422,7 +60518,7 @@ module.exports = function isGlob(str) { /***/ }), -/* 526 */ +/* 528 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60572,7 +60668,7 @@ module.exports.win32 = win32; /***/ }), -/* 527 */ +/* 529 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60583,18 +60679,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__(530); +var toRegex = __webpack_require__(531); +var extend = __webpack_require__(644); /** * 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__(646); +var parsers = __webpack_require__(673); +var cache = __webpack_require__(674); +var utils = __webpack_require__(675); var MAX_LENGTH = 1024 * 64; /** @@ -61456,7 +61552,7 @@ module.exports = micromatch; /***/ }), -/* 528 */ +/* 530 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61466,18 +61562,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__(531); +var unique = __webpack_require__(553); +var extend = __webpack_require__(554); /** * 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__(556); +var parsers = __webpack_require__(569); +var Braces = __webpack_require__(573); +var utils = __webpack_require__(557); var MAX_LENGTH = 1024 * 64; var cache = {}; @@ -61781,16 +61877,16 @@ module.exports = braces; /***/ }), -/* 529 */ +/* 531 */ /***/ (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__(532); +var define = __webpack_require__(538); +var extend = __webpack_require__(546); +var not = __webpack_require__(550); var MAX_LENGTH = 1024 * 64; /** @@ -61943,10 +62039,10 @@ module.exports.makeRe = makeRe; /***/ }), -/* 530 */ +/* 532 */ /***/ (function(module, exports, __webpack_require__) { -var parse = __webpack_require__(531); +var parse = __webpack_require__(533); var types = parse.types; module.exports = function (re, opts) { @@ -61992,13 +62088,13 @@ function isRegExp (x) { /***/ }), -/* 531 */ +/* 533 */ /***/ (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__(534); +var types = __webpack_require__(535); +var sets = __webpack_require__(536); +var positions = __webpack_require__(537); module.exports = function(regexpStr) { @@ -62280,11 +62376,11 @@ module.exports.types = types; /***/ }), -/* 532 */ +/* 534 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(533); -var sets = __webpack_require__(534); +var types = __webpack_require__(535); +var sets = __webpack_require__(536); // All of these are private and only used by randexp. @@ -62397,7 +62493,7 @@ exports.error = function(regexp, msg) { /***/ }), -/* 533 */ +/* 535 */ /***/ (function(module, exports) { module.exports = { @@ -62413,10 +62509,10 @@ module.exports = { /***/ }), -/* 534 */ +/* 536 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(533); +var types = __webpack_require__(535); var INTS = function() { return [{ type: types.RANGE , from: 48, to: 57 }]; @@ -62501,10 +62597,10 @@ exports.anyChar = function() { /***/ }), -/* 535 */ +/* 537 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(533); +var types = __webpack_require__(535); exports.wordBoundary = function() { return { type: types.POSITION, value: 'b' }; @@ -62524,7 +62620,7 @@ exports.end = function() { /***/ }), -/* 536 */ +/* 538 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62537,8 +62633,8 @@ exports.end = function() { -var isobject = __webpack_require__(537); -var isDescriptor = __webpack_require__(538); +var isobject = __webpack_require__(539); +var isDescriptor = __webpack_require__(540); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -62569,7 +62665,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 537 */ +/* 539 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62588,7 +62684,7 @@ module.exports = function isObject(val) { /***/ }), -/* 538 */ +/* 540 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62601,9 +62697,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__(541); +var isAccessor = __webpack_require__(542); +var isData = __webpack_require__(544); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -62617,7 +62713,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 539 */ +/* 541 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -62752,7 +62848,7 @@ function isBuffer(val) { /***/ }), -/* 540 */ +/* 542 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62765,7 +62861,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(541); +var typeOf = __webpack_require__(543); // accessor descriptor properties var accessor = { @@ -62828,7 +62924,7 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 541 */ +/* 543 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -62963,7 +63059,7 @@ function isBuffer(val) { /***/ }), -/* 542 */ +/* 544 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62976,7 +63072,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(543); +var typeOf = __webpack_require__(545); module.exports = function isDataDescriptor(obj, prop) { // data descriptor properties @@ -63019,7 +63115,7 @@ module.exports = function isDataDescriptor(obj, prop) { /***/ }), -/* 543 */ +/* 545 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -63154,14 +63250,14 @@ function isBuffer(val) { /***/ }), -/* 544 */ +/* 546 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(545); -var assignSymbols = __webpack_require__(547); +var isExtendable = __webpack_require__(547); +var assignSymbols = __webpack_require__(549); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -63221,7 +63317,7 @@ function isEnum(obj, key) { /***/ }), -/* 545 */ +/* 547 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63234,7 +63330,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(546); +var isPlainObject = __webpack_require__(548); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -63242,7 +63338,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 546 */ +/* 548 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63255,7 +63351,7 @@ module.exports = function isExtendable(val) { -var isObject = __webpack_require__(537); +var isObject = __webpack_require__(539); function isObjectObject(o) { return isObject(o) === true @@ -63286,7 +63382,7 @@ module.exports = function isPlainObject(o) { /***/ }), -/* 547 */ +/* 549 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63333,14 +63429,14 @@ module.exports = function(receiver, objects) { /***/ }), -/* 548 */ +/* 550 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(549); -var safe = __webpack_require__(530); +var extend = __webpack_require__(551); +var safe = __webpack_require__(532); /** * The main export is a function that takes a `pattern` string and an `options` object. @@ -63412,14 +63508,14 @@ module.exports = toRegex; /***/ }), -/* 549 */ +/* 551 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(550); -var assignSymbols = __webpack_require__(547); +var isExtendable = __webpack_require__(552); +var assignSymbols = __webpack_require__(549); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -63479,7 +63575,7 @@ function isEnum(obj, key) { /***/ }), -/* 550 */ +/* 552 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63492,7 +63588,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(546); +var isPlainObject = __webpack_require__(548); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -63500,7 +63596,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 551 */ +/* 553 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63550,13 +63646,13 @@ module.exports.immutable = function uniqueImmutable(arr) { /***/ }), -/* 552 */ +/* 554 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(553); +var isObject = __webpack_require__(555); module.exports = function extend(o/*, objects*/) { if (!isObject(o)) { o = {}; } @@ -63590,7 +63686,7 @@ function hasOwn(obj, key) { /***/ }), -/* 553 */ +/* 555 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63610,13 +63706,13 @@ module.exports = function isExtendable(val) { /***/ }), -/* 554 */ +/* 556 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(555); +var utils = __webpack_require__(557); module.exports = function(braces, options) { braces.compiler @@ -63899,25 +63995,25 @@ function hasQueue(node) { /***/ }), -/* 555 */ +/* 557 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var splitString = __webpack_require__(556); +var splitString = __webpack_require__(558); 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__(554); +utils.flatten = __webpack_require__(561); +utils.isObject = __webpack_require__(539); +utils.fillRange = __webpack_require__(562); +utils.repeat = __webpack_require__(568); +utils.unique = __webpack_require__(553); utils.define = function(obj, key, val) { Object.defineProperty(obj, key, { @@ -64249,7 +64345,7 @@ utils.escapeRegex = function(str) { /***/ }), -/* 556 */ +/* 558 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64262,7 +64358,7 @@ utils.escapeRegex = function(str) { -var extend = __webpack_require__(557); +var extend = __webpack_require__(559); module.exports = function(str, options, fn) { if (typeof str !== 'string') { @@ -64427,14 +64523,14 @@ function keepEscaping(opts, str, idx) { /***/ }), -/* 557 */ +/* 559 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(558); -var assignSymbols = __webpack_require__(547); +var isExtendable = __webpack_require__(560); +var assignSymbols = __webpack_require__(549); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -64494,7 +64590,7 @@ function isEnum(obj, key) { /***/ }), -/* 558 */ +/* 560 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64507,7 +64603,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(546); +var isPlainObject = __webpack_require__(548); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -64515,7 +64611,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 559 */ +/* 561 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64544,7 +64640,7 @@ function flat(arr, res) { /***/ }), -/* 560 */ +/* 562 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64558,10 +64654,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__(563); +var extend = __webpack_require__(554); +var repeat = __webpack_require__(566); +var toRegex = __webpack_require__(567); /** * Return a range of numbers or letters. @@ -64759,7 +64855,7 @@ module.exports = fillRange; /***/ }), -/* 561 */ +/* 563 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64772,7 +64868,7 @@ module.exports = fillRange; -var typeOf = __webpack_require__(562); +var typeOf = __webpack_require__(564); module.exports = function isNumber(num) { var type = typeOf(num); @@ -64788,10 +64884,10 @@ module.exports = function isNumber(num) { /***/ }), -/* 562 */ +/* 564 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(563); +var isBuffer = __webpack_require__(565); var toString = Object.prototype.toString; /** @@ -64910,7 +65006,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 563 */ +/* 565 */ /***/ (function(module, exports) { /*! @@ -64937,7 +65033,7 @@ function isSlowBuffer (obj) { /***/ }), -/* 564 */ +/* 566 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -65014,7 +65110,7 @@ function repeat(str, num) { /***/ }), -/* 565 */ +/* 567 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -65027,8 +65123,8 @@ function repeat(str, num) { -var repeat = __webpack_require__(564); -var isNumber = __webpack_require__(561); +var repeat = __webpack_require__(566); +var isNumber = __webpack_require__(563); var cache = {}; function toRegexRange(min, max, options) { @@ -65315,7 +65411,7 @@ module.exports = toRegexRange; /***/ }), -/* 566 */ +/* 568 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -65340,14 +65436,14 @@ module.exports = function repeat(ele, num) { /***/ }), -/* 567 */ +/* 569 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Node = __webpack_require__(568); -var utils = __webpack_require__(555); +var Node = __webpack_require__(570); +var utils = __webpack_require__(557); /** * Braces parsers @@ -65707,15 +65803,15 @@ function concatNodes(pos, node, parent, options) { /***/ }), -/* 568 */ +/* 570 */ /***/ (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__(539); +var define = __webpack_require__(571); +var utils = __webpack_require__(572); var ownNames; /** @@ -66206,7 +66302,7 @@ exports = module.exports = Node; /***/ }), -/* 569 */ +/* 571 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -66219,7 +66315,7 @@ exports = module.exports = Node; -var isDescriptor = __webpack_require__(538); +var isDescriptor = __webpack_require__(540); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -66244,13 +66340,13 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 570 */ +/* 572 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(562); +var typeOf = __webpack_require__(564); var utils = module.exports; /** @@ -67270,17 +67366,17 @@ function assert(val, message) { /***/ }), -/* 571 */ +/* 573 */ /***/ (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__(554); +var Snapdragon = __webpack_require__(574); +var compilers = __webpack_require__(556); +var parsers = __webpack_require__(569); +var utils = __webpack_require__(557); /** * Customize Snapdragon parser and renderer @@ -67381,17 +67477,17 @@ module.exports = Braces; /***/ }), -/* 572 */ +/* 574 */ /***/ (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__(575); +var define = __webpack_require__(602); +var Compiler = __webpack_require__(612); +var Parser = __webpack_require__(641); +var utils = __webpack_require__(621); var regexCache = {}; var cache = {}; @@ -67562,20 +67658,20 @@ module.exports.Parser = Parser; /***/ }), -/* 573 */ +/* 575 */ /***/ (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__(576); +var CacheBase = __webpack_require__(577); +var Emitter = __webpack_require__(578); +var isObject = __webpack_require__(539); +var merge = __webpack_require__(596); +var pascal = __webpack_require__(599); +var cu = __webpack_require__(600); /** * Optionally define a custom `cache` namespace to use. @@ -68004,7 +68100,7 @@ module.exports.namespace = namespace; /***/ }), -/* 574 */ +/* 576 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68017,7 +68113,7 @@ module.exports.namespace = namespace; -var isDescriptor = __webpack_require__(538); +var isDescriptor = __webpack_require__(540); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -68042,21 +68138,21 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 575 */ +/* 577 */ /***/ (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__(539); +var Emitter = __webpack_require__(578); +var visit = __webpack_require__(579); +var toPath = __webpack_require__(582); +var union = __webpack_require__(583); +var del = __webpack_require__(587); +var get = __webpack_require__(585); +var has = __webpack_require__(592); +var set = __webpack_require__(595); /** * Create a `Cache` constructor that when instantiated will @@ -68310,7 +68406,7 @@ module.exports.namespace = namespace; /***/ }), -/* 576 */ +/* 578 */ /***/ (function(module, exports, __webpack_require__) { @@ -68479,7 +68575,7 @@ Emitter.prototype.hasListeners = function(event){ /***/ }), -/* 577 */ +/* 579 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68492,8 +68588,8 @@ Emitter.prototype.hasListeners = function(event){ -var visit = __webpack_require__(578); -var mapVisit = __webpack_require__(579); +var visit = __webpack_require__(580); +var mapVisit = __webpack_require__(581); module.exports = function(collection, method, val) { var result; @@ -68516,7 +68612,7 @@ module.exports = function(collection, method, val) { /***/ }), -/* 578 */ +/* 580 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68529,7 +68625,7 @@ module.exports = function(collection, method, val) { -var isObject = __webpack_require__(537); +var isObject = __webpack_require__(539); module.exports = function visit(thisArg, method, target, val) { if (!isObject(thisArg) && typeof thisArg !== 'function') { @@ -68556,14 +68652,14 @@ module.exports = function visit(thisArg, method, target, val) { /***/ }), -/* 579 */ +/* 581 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(112); -var visit = __webpack_require__(578); +var visit = __webpack_require__(580); /** * Map `visit` over an array of objects. @@ -68600,7 +68696,7 @@ function isObject(val) { /***/ }), -/* 580 */ +/* 582 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68613,7 +68709,7 @@ function isObject(val) { -var typeOf = __webpack_require__(562); +var typeOf = __webpack_require__(564); module.exports = function toPath(args) { if (typeOf(args) !== 'arguments') { @@ -68640,16 +68736,16 @@ function filter(arr) { /***/ }), -/* 581 */ +/* 583 */ /***/ (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__(555); +var union = __webpack_require__(584); +var get = __webpack_require__(585); +var set = __webpack_require__(586); module.exports = function unionValue(obj, prop, value) { if (!isObject(obj)) { @@ -68677,7 +68773,7 @@ function arrayify(val) { /***/ }), -/* 582 */ +/* 584 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68713,7 +68809,7 @@ module.exports = function union(init) { /***/ }), -/* 583 */ +/* 585 */ /***/ (function(module, exports) { /*! @@ -68769,7 +68865,7 @@ function toString(val) { /***/ }), -/* 584 */ +/* 586 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68782,10 +68878,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__(558); +var extend = __webpack_require__(554); +var isPlainObject = __webpack_require__(548); +var isObject = __webpack_require__(555); module.exports = function(obj, prop, val) { if (!isObject(obj)) { @@ -68831,7 +68927,7 @@ function isValidKey(key) { /***/ }), -/* 585 */ +/* 587 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68844,8 +68940,8 @@ function isValidKey(key) { -var isObject = __webpack_require__(537); -var has = __webpack_require__(586); +var isObject = __webpack_require__(539); +var has = __webpack_require__(588); module.exports = function unset(obj, prop) { if (!isObject(obj)) { @@ -68870,7 +68966,7 @@ module.exports = function unset(obj, prop) { /***/ }), -/* 586 */ +/* 588 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68883,9 +68979,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__(589); +var hasValues = __webpack_require__(591); +var get = __webpack_require__(585); module.exports = function(obj, prop, noZero) { if (isObject(obj)) { @@ -68896,7 +68992,7 @@ module.exports = function(obj, prop, noZero) { /***/ }), -/* 587 */ +/* 589 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68909,7 +69005,7 @@ module.exports = function(obj, prop, noZero) { -var isArray = __webpack_require__(588); +var isArray = __webpack_require__(590); module.exports = function isObject(val) { return val != null && typeof val === 'object' && isArray(val) === false; @@ -68917,7 +69013,7 @@ module.exports = function isObject(val) { /***/ }), -/* 588 */ +/* 590 */ /***/ (function(module, exports) { var toString = {}.toString; @@ -68928,7 +69024,7 @@ module.exports = Array.isArray || function (arr) { /***/ }), -/* 589 */ +/* 591 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68971,7 +69067,7 @@ module.exports = function hasValue(o, noZero) { /***/ }), -/* 590 */ +/* 592 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68984,9 +69080,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__(539); +var hasValues = __webpack_require__(593); +var get = __webpack_require__(585); module.exports = function(val, prop) { return hasValues(isObject(val) && prop ? get(val, prop) : val); @@ -68994,7 +69090,7 @@ module.exports = function(val, prop) { /***/ }), -/* 591 */ +/* 593 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69007,8 +69103,8 @@ module.exports = function(val, prop) { -var typeOf = __webpack_require__(592); -var isNumber = __webpack_require__(561); +var typeOf = __webpack_require__(594); +var isNumber = __webpack_require__(563); module.exports = function hasValue(val) { // is-number checks for NaN and other edge cases @@ -69061,10 +69157,10 @@ module.exports = function hasValue(val) { /***/ }), -/* 592 */ +/* 594 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(563); +var isBuffer = __webpack_require__(565); var toString = Object.prototype.toString; /** @@ -69186,7 +69282,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 593 */ +/* 595 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69199,10 +69295,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__(558); +var extend = __webpack_require__(554); +var isPlainObject = __webpack_require__(548); +var isObject = __webpack_require__(555); module.exports = function(obj, prop, val) { if (!isObject(obj)) { @@ -69248,14 +69344,14 @@ function isValidKey(key) { /***/ }), -/* 594 */ +/* 596 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(595); -var forIn = __webpack_require__(596); +var isExtendable = __webpack_require__(597); +var forIn = __webpack_require__(598); function mixinDeep(target, objects) { var len = arguments.length, i = 0; @@ -69319,7 +69415,7 @@ module.exports = mixinDeep; /***/ }), -/* 595 */ +/* 597 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69332,7 +69428,7 @@ module.exports = mixinDeep; -var isPlainObject = __webpack_require__(546); +var isPlainObject = __webpack_require__(548); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -69340,7 +69436,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 596 */ +/* 598 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69363,7 +69459,7 @@ module.exports = function forIn(obj, fn, thisArg) { /***/ }), -/* 597 */ +/* 599 */ /***/ (function(module, exports) { /*! @@ -69390,14 +69486,14 @@ module.exports = pascalcase; /***/ }), -/* 598 */ +/* 600 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(112); -var utils = __webpack_require__(599); +var utils = __webpack_require__(601); /** * Expose class utils @@ -69762,7 +69858,7 @@ cu.bubble = function(Parent, events) { /***/ }), -/* 599 */ +/* 601 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69776,10 +69872,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__(584); +utils.define = __webpack_require__(602); +utils.isObj = __webpack_require__(539); +utils.staticExtend = __webpack_require__(609); /** @@ -69790,7 +69886,7 @@ module.exports = utils; /***/ }), -/* 600 */ +/* 602 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69803,7 +69899,7 @@ module.exports = utils; -var isDescriptor = __webpack_require__(601); +var isDescriptor = __webpack_require__(603); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -69828,7 +69924,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 601 */ +/* 603 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69841,9 +69937,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__(604); +var isAccessor = __webpack_require__(605); +var isData = __webpack_require__(607); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -69857,7 +69953,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 602 */ +/* 604 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -70010,7 +70106,7 @@ function isBuffer(val) { /***/ }), -/* 603 */ +/* 605 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70023,7 +70119,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(604); +var typeOf = __webpack_require__(606); // accessor descriptor properties var accessor = { @@ -70086,10 +70182,10 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 604 */ +/* 606 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(563); +var isBuffer = __webpack_require__(565); var toString = Object.prototype.toString; /** @@ -70208,7 +70304,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 605 */ +/* 607 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70221,7 +70317,7 @@ module.exports = function kindOf(val) { -var typeOf = __webpack_require__(606); +var typeOf = __webpack_require__(608); // data descriptor properties var data = { @@ -70270,10 +70366,10 @@ module.exports = isDataDescriptor; /***/ }), -/* 606 */ +/* 608 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(563); +var isBuffer = __webpack_require__(565); var toString = Object.prototype.toString; /** @@ -70392,7 +70488,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 607 */ +/* 609 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70405,8 +70501,8 @@ module.exports = function kindOf(val) { -var copy = __webpack_require__(608); -var define = __webpack_require__(600); +var copy = __webpack_require__(610); +var define = __webpack_require__(602); var util = __webpack_require__(112); /** @@ -70489,15 +70585,15 @@ module.exports = extend; /***/ }), -/* 608 */ +/* 610 */ /***/ (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__(564); +var copyDescriptor = __webpack_require__(611); +var define = __webpack_require__(602); /** * Copy static properties, prototype properties, and descriptors from one object to another. @@ -70670,7 +70766,7 @@ module.exports.has = has; /***/ }), -/* 609 */ +/* 611 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70758,16 +70854,16 @@ function isObject(val) { /***/ }), -/* 610 */ +/* 612 */ /***/ (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__(613); +var define = __webpack_require__(602); +var debug = __webpack_require__(615)('snapdragon:compiler'); +var utils = __webpack_require__(621); /** * Create a new `Compiler` with the given `options`. @@ -70921,7 +71017,7 @@ Compiler.prototype = { // source map support if (opts.sourcemap) { - var sourcemaps = __webpack_require__(638); + var sourcemaps = __webpack_require__(640); sourcemaps(this); this.mapVisit(this.ast.nodes); this.applySourceMaps(); @@ -70942,7 +71038,7 @@ module.exports = Compiler; /***/ }), -/* 611 */ +/* 613 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70955,7 +71051,7 @@ module.exports = Compiler; -var utils = __webpack_require__(612); +var utils = __webpack_require__(614); module.exports = function base(app, opts) { if (!utils.isObject(app) && typeof app !== 'function') { @@ -71070,7 +71166,7 @@ module.exports = function base(app, opts) { /***/ }), -/* 612 */ +/* 614 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -71084,8 +71180,8 @@ var utils = {}; * Lazily required module dependencies */ -utils.define = __webpack_require__(600); -utils.isObject = __webpack_require__(537); +utils.define = __webpack_require__(602); +utils.isObject = __webpack_require__(539); utils.isString = function(val) { @@ -71100,7 +71196,7 @@ module.exports = utils; /***/ }), -/* 613 */ +/* 615 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -71109,14 +71205,14 @@ module.exports = utils; */ if (typeof process !== 'undefined' && process.type === 'renderer') { - module.exports = __webpack_require__(614); + module.exports = __webpack_require__(616); } else { - module.exports = __webpack_require__(617); + module.exports = __webpack_require__(619); } /***/ }), -/* 614 */ +/* 616 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -71125,7 +71221,7 @@ if (typeof process !== 'undefined' && process.type === 'renderer') { * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(615); +exports = module.exports = __webpack_require__(617); exports.log = log; exports.formatArgs = formatArgs; exports.save = save; @@ -71307,7 +71403,7 @@ function localstorage() { /***/ }), -/* 615 */ +/* 617 */ /***/ (function(module, exports, __webpack_require__) { @@ -71323,7 +71419,7 @@ exports.coerce = coerce; exports.disable = disable; exports.enable = enable; exports.enabled = enabled; -exports.humanize = __webpack_require__(616); +exports.humanize = __webpack_require__(618); /** * The currently active debug mode names, and names to skip. @@ -71515,7 +71611,7 @@ function coerce(val) { /***/ }), -/* 616 */ +/* 618 */ /***/ (function(module, exports) { /** @@ -71673,7 +71769,7 @@ function plural(ms, n, name) { /***/ }), -/* 617 */ +/* 619 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -71689,7 +71785,7 @@ var util = __webpack_require__(112); * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(615); +exports = module.exports = __webpack_require__(617); exports.init = init; exports.log = log; exports.formatArgs = formatArgs; @@ -71868,7 +71964,7 @@ function createWritableStdioStream (fd) { case 'PIPE': case 'TCP': - var net = __webpack_require__(618); + var net = __webpack_require__(620); stream = new net.Socket({ fd: fd, readable: false, @@ -71927,13 +72023,13 @@ exports.enable(load()); /***/ }), -/* 618 */ +/* 620 */ /***/ (function(module, exports) { module.exports = require("net"); /***/ }), -/* 619 */ +/* 621 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -71943,9 +72039,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__(554); +exports.SourceMap = __webpack_require__(622); +exports.sourceMapResolve = __webpack_require__(633); /** * Convert backslash in the given string to forward slashes @@ -71988,7 +72084,7 @@ exports.last = function(arr, n) { /***/ }), -/* 620 */ +/* 622 */ /***/ (function(module, exports, __webpack_require__) { /* @@ -71996,13 +72092,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__(623).SourceMapGenerator; +exports.SourceMapConsumer = __webpack_require__(629).SourceMapConsumer; +exports.SourceNode = __webpack_require__(632).SourceNode; /***/ }), -/* 621 */ +/* 623 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -72012,10 +72108,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__(624); +var util = __webpack_require__(626); +var ArraySet = __webpack_require__(627).ArraySet; +var MappingList = __webpack_require__(628).MappingList; /** * An instance of the SourceMapGenerator represents a source map which is @@ -72424,7 +72520,7 @@ exports.SourceMapGenerator = SourceMapGenerator; /***/ }), -/* 622 */ +/* 624 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -72464,7 +72560,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__(625); // 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, @@ -72570,7 +72666,7 @@ exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) { /***/ }), -/* 623 */ +/* 625 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -72643,7 +72739,7 @@ exports.decode = function (charCode) { /***/ }), -/* 624 */ +/* 626 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -73066,7 +73162,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate /***/ }), -/* 625 */ +/* 627 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -73076,7 +73172,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(624); +var util = __webpack_require__(626); var has = Object.prototype.hasOwnProperty; var hasNativeMap = typeof Map !== "undefined"; @@ -73193,7 +73289,7 @@ exports.ArraySet = ArraySet; /***/ }), -/* 626 */ +/* 628 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -73203,7 +73299,7 @@ exports.ArraySet = ArraySet; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(624); +var util = __webpack_require__(626); /** * Determine whether mappingB is after mappingA with respect to generated @@ -73278,7 +73374,7 @@ exports.MappingList = MappingList; /***/ }), -/* 627 */ +/* 629 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -73288,11 +73384,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__(626); +var binarySearch = __webpack_require__(630); +var ArraySet = __webpack_require__(627).ArraySet; +var base64VLQ = __webpack_require__(624); +var quickSort = __webpack_require__(631).quickSort; function SourceMapConsumer(aSourceMap) { var sourceMap = aSourceMap; @@ -74366,7 +74462,7 @@ exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer; /***/ }), -/* 628 */ +/* 630 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -74483,7 +74579,7 @@ exports.search = function search(aNeedle, aHaystack, aCompare, aBias) { /***/ }), -/* 629 */ +/* 631 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -74603,7 +74699,7 @@ exports.quickSort = function (ary, comparator) { /***/ }), -/* 630 */ +/* 632 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -74613,8 +74709,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__(623).SourceMapGenerator; +var util = __webpack_require__(626); // Matches a Windows-style `\r\n` newline or a `\n` newline used by all other // operating systems these days (capturing the result). @@ -75022,17 +75118,17 @@ exports.SourceNode = SourceNode; /***/ }), -/* 631 */ +/* 633 */ /***/ (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__(634) +var resolveUrl = __webpack_require__(635) +var decodeUriComponent = __webpack_require__(636) +var urix = __webpack_require__(638) +var atob = __webpack_require__(639) @@ -75330,7 +75426,7 @@ module.exports = { /***/ }), -/* 632 */ +/* 634 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;// Copyright 2014 Simon Lydell @@ -75393,7 +75489,7 @@ void (function(root, factory) { /***/ }), -/* 633 */ +/* 635 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -75411,13 +75507,13 @@ module.exports = resolveUrl /***/ }), -/* 634 */ +/* 636 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var decodeUriComponent = __webpack_require__(635) +var decodeUriComponent = __webpack_require__(637) function customDecodeUriComponent(string) { // `decodeUriComponent` turns `+` into ` `, but that's not wanted. @@ -75428,7 +75524,7 @@ module.exports = customDecodeUriComponent /***/ }), -/* 635 */ +/* 637 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75529,7 +75625,7 @@ module.exports = function (encodedURI) { /***/ }), -/* 636 */ +/* 638 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -75552,7 +75648,7 @@ module.exports = urix /***/ }), -/* 637 */ +/* 639 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75566,7 +75662,7 @@ module.exports = atob.atob = atob; /***/ }), -/* 638 */ +/* 640 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75574,8 +75670,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__(602); +var utils = __webpack_require__(621); /** * Expose `mixin()`. @@ -75718,19 +75814,19 @@ exports.comment = function(node) { /***/ }), -/* 639 */ +/* 641 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(611); +var use = __webpack_require__(613); 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__(642); +var define = __webpack_require__(602); +var debug = __webpack_require__(615)('snapdragon:parser'); +var Position = __webpack_require__(643); +var utils = __webpack_require__(621); /** * Create a new `Parser` with the given `input` and `options`. @@ -76258,7 +76354,7 @@ module.exports = Parser; /***/ }), -/* 640 */ +/* 642 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -76365,13 +76461,13 @@ MapCache.prototype.del = function mapDelete(key) { /***/ }), -/* 641 */ +/* 643 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var define = __webpack_require__(600); +var define = __webpack_require__(602); /** * Store position for a node @@ -76386,14 +76482,14 @@ module.exports = function Position(start, parser) { /***/ }), -/* 642 */ +/* 644 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(643); -var assignSymbols = __webpack_require__(547); +var isExtendable = __webpack_require__(645); +var assignSymbols = __webpack_require__(549); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -76453,7 +76549,7 @@ function isEnum(obj, key) { /***/ }), -/* 643 */ +/* 645 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -76466,7 +76562,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(546); +var isPlainObject = __webpack_require__(548); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -76474,14 +76570,14 @@ module.exports = function isExtendable(val) { /***/ }), -/* 644 */ +/* 646 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var nanomatch = __webpack_require__(645); -var extglob = __webpack_require__(660); +var nanomatch = __webpack_require__(647); +var extglob = __webpack_require__(662); module.exports = function(snapdragon) { var compilers = snapdragon.compiler.compilers; @@ -76558,7 +76654,7 @@ function escapeExtglobs(compiler) { /***/ }), -/* 645 */ +/* 647 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -76569,17 +76665,17 @@ function escapeExtglobs(compiler) { */ var util = __webpack_require__(112); -var toRegex = __webpack_require__(529); -var extend = __webpack_require__(646); +var toRegex = __webpack_require__(531); +var extend = __webpack_require__(648); /** * 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__(650); +var parsers = __webpack_require__(651); +var cache = __webpack_require__(654); +var utils = __webpack_require__(656); var MAX_LENGTH = 1024 * 64; /** @@ -77403,14 +77499,14 @@ module.exports = nanomatch; /***/ }), -/* 646 */ +/* 648 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(647); -var assignSymbols = __webpack_require__(547); +var isExtendable = __webpack_require__(649); +var assignSymbols = __webpack_require__(549); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -77470,7 +77566,7 @@ function isEnum(obj, key) { /***/ }), -/* 647 */ +/* 649 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77483,7 +77579,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(546); +var isPlainObject = __webpack_require__(548); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -77491,7 +77587,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 648 */ +/* 650 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77837,15 +77933,15 @@ module.exports = function(nanomatch, options) { /***/ }), -/* 649 */ +/* 651 */ /***/ (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__(550); +var toRegex = __webpack_require__(531); +var isOdd = __webpack_require__(652); /** * Characters to use in negation regex (we want to "not" match @@ -78231,7 +78327,7 @@ module.exports.not = NOT_REGEX; /***/ }), -/* 650 */ +/* 652 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78244,7 +78340,7 @@ module.exports.not = NOT_REGEX; -var isNumber = __webpack_require__(651); +var isNumber = __webpack_require__(653); module.exports = function isOdd(i) { if (!isNumber(i)) { @@ -78258,7 +78354,7 @@ module.exports = function isOdd(i) { /***/ }), -/* 651 */ +/* 653 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78286,14 +78382,14 @@ module.exports = function isNumber(num) { /***/ }), -/* 652 */ +/* 654 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(653))(); +module.exports = new (__webpack_require__(655))(); /***/ }), -/* 653 */ +/* 655 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78306,7 +78402,7 @@ module.exports = new (__webpack_require__(653))(); -var MapCache = __webpack_require__(640); +var MapCache = __webpack_require__(642); /** * Create a new `FragmentCache` with an optional object to use for `caches`. @@ -78428,7 +78524,7 @@ exports = module.exports = FragmentCache; /***/ }), -/* 654 */ +/* 656 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78441,14 +78537,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__(657)(); +var Snapdragon = __webpack_require__(574); +utils.define = __webpack_require__(658); +utils.diff = __webpack_require__(659); +utils.extend = __webpack_require__(648); +utils.pick = __webpack_require__(660); +utils.typeOf = __webpack_require__(661); +utils.unique = __webpack_require__(553); /** * Returns true if the given value is effectively an empty string @@ -78814,7 +78910,7 @@ utils.unixify = function(options) { /***/ }), -/* 655 */ +/* 657 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*! @@ -78842,7 +78938,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /***/ }), -/* 656 */ +/* 658 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78855,8 +78951,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__(539); +var isDescriptor = __webpack_require__(540); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -78887,7 +78983,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 657 */ +/* 659 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78941,7 +79037,7 @@ function diffArray(one, two) { /***/ }), -/* 658 */ +/* 660 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78954,7 +79050,7 @@ function diffArray(one, two) { -var isObject = __webpack_require__(537); +var isObject = __webpack_require__(539); module.exports = function pick(obj, keys) { if (!isObject(obj) && typeof obj !== 'function') { @@ -78983,7 +79079,7 @@ module.exports = function pick(obj, keys) { /***/ }), -/* 659 */ +/* 661 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -79118,7 +79214,7 @@ function isBuffer(val) { /***/ }), -/* 660 */ +/* 662 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79128,18 +79224,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__(554); +var unique = __webpack_require__(553); +var toRegex = __webpack_require__(531); /** * 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__(663); +var parsers = __webpack_require__(669); +var Extglob = __webpack_require__(672); +var utils = __webpack_require__(671); var MAX_LENGTH = 1024 * 64; /** @@ -79456,13 +79552,13 @@ module.exports = extglob; /***/ }), -/* 661 */ +/* 663 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(662); +var brackets = __webpack_require__(664); /** * Extglob compilers @@ -79632,7 +79728,7 @@ module.exports = function(extglob) { /***/ }), -/* 662 */ +/* 664 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79642,17 +79738,17 @@ module.exports = function(extglob) { * Local dependencies */ -var compilers = __webpack_require__(663); -var parsers = __webpack_require__(665); +var compilers = __webpack_require__(665); +var parsers = __webpack_require__(667); /** * 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__(615)('expand-brackets'); +var extend = __webpack_require__(554); +var Snapdragon = __webpack_require__(574); +var toRegex = __webpack_require__(531); /** * Parses the given POSIX character class `pattern` and returns a @@ -79850,13 +79946,13 @@ module.exports = brackets; /***/ }), -/* 663 */ +/* 665 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var posix = __webpack_require__(664); +var posix = __webpack_require__(666); module.exports = function(brackets) { brackets.compiler @@ -79944,7 +80040,7 @@ module.exports = function(brackets) { /***/ }), -/* 664 */ +/* 666 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79973,14 +80069,14 @@ module.exports = { /***/ }), -/* 665 */ +/* 667 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(666); -var define = __webpack_require__(600); +var utils = __webpack_require__(668); +var define = __webpack_require__(602); /** * Text regex @@ -80199,14 +80295,14 @@ module.exports.TEXT_REGEX = TEXT_REGEX; /***/ }), -/* 666 */ +/* 668 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var toRegex = __webpack_require__(529); -var regexNot = __webpack_require__(548); +var toRegex = __webpack_require__(531); +var regexNot = __webpack_require__(550); var cached; /** @@ -80240,15 +80336,15 @@ exports.createRegex = function(pattern, include) { /***/ }), -/* 667 */ +/* 669 */ /***/ (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__(664); +var define = __webpack_require__(670); +var utils = __webpack_require__(671); /** * Characters to use in text regex (we want to "not" match @@ -80403,7 +80499,7 @@ module.exports = parsers; /***/ }), -/* 668 */ +/* 670 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80416,7 +80512,7 @@ module.exports = parsers; -var isDescriptor = __webpack_require__(538); +var isDescriptor = __webpack_require__(540); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -80441,14 +80537,14 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 669 */ +/* 671 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var regex = __webpack_require__(548); -var Cache = __webpack_require__(653); +var regex = __webpack_require__(550); +var Cache = __webpack_require__(655); /** * Utils @@ -80517,7 +80613,7 @@ utils.createRegex = function(str) { /***/ }), -/* 670 */ +/* 672 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80527,16 +80623,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__(574); +var define = __webpack_require__(670); +var extend = __webpack_require__(554); /** * Local dependencies */ -var compilers = __webpack_require__(661); -var parsers = __webpack_require__(667); +var compilers = __webpack_require__(663); +var parsers = __webpack_require__(669); /** * Customize Snapdragon parser and renderer @@ -80602,16 +80698,16 @@ module.exports = Extglob; /***/ }), -/* 671 */ +/* 673 */ /***/ (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__(662); +var nanomatch = __webpack_require__(647); +var regexNot = __webpack_require__(550); +var toRegex = __webpack_require__(531); var not; /** @@ -80692,14 +80788,14 @@ function textRegex(pattern) { /***/ }), -/* 672 */ +/* 674 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(653))(); +module.exports = new (__webpack_require__(655))(); /***/ }), -/* 673 */ +/* 675 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80712,13 +80808,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__(574); +utils.define = __webpack_require__(676); +utils.diff = __webpack_require__(659); +utils.extend = __webpack_require__(644); +utils.pick = __webpack_require__(660); +utils.typeOf = __webpack_require__(677); +utils.unique = __webpack_require__(553); /** * Returns true if the platform is windows, or `path.sep` is `\\`. @@ -81015,7 +81111,7 @@ utils.unixify = function(options) { /***/ }), -/* 674 */ +/* 676 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81028,8 +81124,8 @@ utils.unixify = function(options) { -var isobject = __webpack_require__(537); -var isDescriptor = __webpack_require__(538); +var isobject = __webpack_require__(539); +var isDescriptor = __webpack_require__(540); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -81060,7 +81156,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 675 */ +/* 677 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -81195,7 +81291,7 @@ function isBuffer(val) { /***/ }), -/* 676 */ +/* 678 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81214,9 +81310,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__(679); +var reader_1 = __webpack_require__(692); +var fs_stream_1 = __webpack_require__(696); var ReaderAsync = /** @class */ (function (_super) { __extends(ReaderAsync, _super); function ReaderAsync() { @@ -81277,15 +81373,15 @@ exports.default = ReaderAsync; /***/ }), -/* 677 */ +/* 679 */ /***/ (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__(680); +const readdirAsync = __webpack_require__(688); +const readdirStream = __webpack_require__(691); module.exports = exports = readdirAsyncPath; exports.readdir = exports.readdirAsync = exports.async = readdirAsyncPath; @@ -81369,7 +81465,7 @@ function readdirStreamStat (dir, options) { /***/ }), -/* 678 */ +/* 680 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81377,11 +81473,11 @@ function readdirStreamStat (dir, options) { module.exports = readdirSync; -const DirectoryReader = __webpack_require__(679); +const DirectoryReader = __webpack_require__(681); let syncFacade = { - fs: __webpack_require__(684), - forEach: __webpack_require__(685), + fs: __webpack_require__(686), + forEach: __webpack_require__(687), sync: true }; @@ -81410,7 +81506,7 @@ function readdirSync (dir, options, internalOptions) { /***/ }), -/* 679 */ +/* 681 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81419,9 +81515,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__(682); +const stat = __webpack_require__(684); +const call = __webpack_require__(685); /** * Asynchronously reads the contents of a directory and streams the results @@ -81797,14 +81893,14 @@ module.exports = DirectoryReader; /***/ }), -/* 680 */ +/* 682 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const globToRegExp = __webpack_require__(681); +const globToRegExp = __webpack_require__(683); module.exports = normalizeOptions; @@ -81981,7 +82077,7 @@ function normalizeOptions (options, internalOptions) { /***/ }), -/* 681 */ +/* 683 */ /***/ (function(module, exports) { module.exports = function (glob, opts) { @@ -82118,13 +82214,13 @@ module.exports = function (glob, opts) { /***/ }), -/* 682 */ +/* 684 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const call = __webpack_require__(683); +const call = __webpack_require__(685); module.exports = stat; @@ -82199,7 +82295,7 @@ function symlinkStat (fs, path, lstats, callback) { /***/ }), -/* 683 */ +/* 685 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82260,14 +82356,14 @@ function callOnce (fn) { /***/ }), -/* 684 */ +/* 686 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); -const call = __webpack_require__(683); +const call = __webpack_require__(685); /** * A facade around {@link fs.readdirSync} that allows it to be called @@ -82331,7 +82427,7 @@ exports.lstat = function (path, callback) { /***/ }), -/* 685 */ +/* 687 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82360,7 +82456,7 @@ function syncForEach (array, iterator, done) { /***/ }), -/* 686 */ +/* 688 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82368,12 +82464,12 @@ function syncForEach (array, iterator, done) { module.exports = readdirAsync; -const maybe = __webpack_require__(687); -const DirectoryReader = __webpack_require__(679); +const maybe = __webpack_require__(689); +const DirectoryReader = __webpack_require__(681); let asyncFacade = { fs: __webpack_require__(134), - forEach: __webpack_require__(688), + forEach: __webpack_require__(690), async: true }; @@ -82415,7 +82511,7 @@ function readdirAsync (dir, options, callback, internalOptions) { /***/ }), -/* 687 */ +/* 689 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82442,7 +82538,7 @@ module.exports = function maybe (cb, promise) { /***/ }), -/* 688 */ +/* 690 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82478,7 +82574,7 @@ function asyncForEach (array, iterator, done) { /***/ }), -/* 689 */ +/* 691 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82486,11 +82582,11 @@ function asyncForEach (array, iterator, done) { module.exports = readdirStream; -const DirectoryReader = __webpack_require__(679); +const DirectoryReader = __webpack_require__(681); let streamFacade = { fs: __webpack_require__(134), - forEach: __webpack_require__(688), + forEach: __webpack_require__(690), async: true }; @@ -82510,16 +82606,16 @@ function readdirStream (dir, options, internalOptions) { /***/ }), -/* 690 */ +/* 692 */ /***/ (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__(693); +var entry_1 = __webpack_require__(695); +var pathUtil = __webpack_require__(694); var Reader = /** @class */ (function () { function Reader(options) { this.options = options; @@ -82585,14 +82681,14 @@ exports.default = Reader; /***/ }), -/* 691 */ +/* 693 */ /***/ (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__(694); +var patternUtils = __webpack_require__(525); var DeepFilter = /** @class */ (function () { function DeepFilter(options, micromatchOptions) { this.options = options; @@ -82675,7 +82771,7 @@ exports.default = DeepFilter; /***/ }), -/* 692 */ +/* 694 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82706,14 +82802,14 @@ exports.makeAbsolute = makeAbsolute; /***/ }), -/* 693 */ +/* 695 */ /***/ (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__(694); +var patternUtils = __webpack_require__(525); var EntryFilter = /** @class */ (function () { function EntryFilter(options, micromatchOptions) { this.options = options; @@ -82798,7 +82894,7 @@ exports.default = EntryFilter; /***/ }), -/* 694 */ +/* 696 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82818,8 +82914,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__(697); +var fs_1 = __webpack_require__(701); var FileSystemStream = /** @class */ (function (_super) { __extends(FileSystemStream, _super); function FileSystemStream() { @@ -82869,14 +82965,14 @@ exports.default = FileSystemStream; /***/ }), -/* 695 */ +/* 697 */ /***/ (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__(698); +const statProvider = __webpack_require__(700); /** * Asynchronous API. */ @@ -82907,13 +83003,13 @@ exports.statSync = statSync; /***/ }), -/* 696 */ +/* 698 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsAdapter = __webpack_require__(697); +const fsAdapter = __webpack_require__(699); function prepare(opts) { const options = Object.assign({ fs: fsAdapter.getFileSystemAdapter(opts ? opts.fs : undefined), @@ -82926,7 +83022,7 @@ exports.prepare = prepare; /***/ }), -/* 697 */ +/* 699 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82949,7 +83045,7 @@ exports.getFileSystemAdapter = getFileSystemAdapter; /***/ }), -/* 698 */ +/* 700 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83001,7 +83097,7 @@ exports.isFollowedSymlink = isFollowedSymlink; /***/ }), -/* 699 */ +/* 701 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83032,7 +83128,7 @@ exports.default = FileSystem; /***/ }), -/* 700 */ +/* 702 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83052,9 +83148,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__(679); +var reader_1 = __webpack_require__(692); +var fs_stream_1 = __webpack_require__(696); var TransformStream = /** @class */ (function (_super) { __extends(TransformStream, _super); function TransformStream(reader) { @@ -83122,7 +83218,7 @@ exports.default = ReaderStream; /***/ }), -/* 701 */ +/* 703 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83141,9 +83237,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__(679); +var reader_1 = __webpack_require__(692); +var fs_sync_1 = __webpack_require__(704); var ReaderSync = /** @class */ (function (_super) { __extends(ReaderSync, _super); function ReaderSync() { @@ -83203,7 +83299,7 @@ exports.default = ReaderSync; /***/ }), -/* 702 */ +/* 704 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83222,8 +83318,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__(697); +var fs_1 = __webpack_require__(701); var FileSystemSync = /** @class */ (function (_super) { __extends(FileSystemSync, _super); function FileSystemSync() { @@ -83269,7 +83365,7 @@ exports.default = FileSystemSync; /***/ }), -/* 703 */ +/* 705 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83285,7 +83381,7 @@ exports.flatten = flatten; /***/ }), -/* 704 */ +/* 706 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83306,13 +83402,13 @@ exports.merge = merge; /***/ }), -/* 705 */ +/* 707 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const pathType = __webpack_require__(706); +const pathType = __webpack_require__(708); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; @@ -83378,13 +83474,13 @@ module.exports.sync = (input, opts) => { /***/ }), -/* 706 */ +/* 708 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); -const pify = __webpack_require__(707); +const pify = __webpack_require__(709); function type(fn, fn2, fp) { if (typeof fp !== 'string') { @@ -83427,7 +83523,7 @@ exports.symlinkSync = typeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 707 */ +/* 709 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83518,17 +83614,17 @@ module.exports = (obj, opts) => { /***/ }), -/* 708 */ +/* 710 */ /***/ (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__(521); +const gitIgnore = __webpack_require__(711); +const pify = __webpack_require__(712); +const slash = __webpack_require__(713); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -83626,7 +83722,7 @@ module.exports.sync = options => { /***/ }), -/* 709 */ +/* 711 */ /***/ (function(module, exports) { // A simple implementation of make-array @@ -84095,7 +84191,7 @@ module.exports = options => new IgnoreBase(options) /***/ }), -/* 710 */ +/* 712 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84170,7 +84266,7 @@ module.exports = (input, options) => { /***/ }), -/* 711 */ +/* 713 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84188,7 +84284,7 @@ module.exports = input => { /***/ }), -/* 712 */ +/* 714 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84201,7 +84297,7 @@ module.exports = input => { -var isGlob = __webpack_require__(713); +var isGlob = __webpack_require__(715); module.exports = function hasGlob(val) { if (val == null) return false; @@ -84221,7 +84317,7 @@ module.exports = function hasGlob(val) { /***/ }), -/* 713 */ +/* 715 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -84252,17 +84348,17 @@ module.exports = function isGlob(str) { /***/ }), -/* 714 */ +/* 716 */ /***/ (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__(717); +const CpFileError = __webpack_require__(720); +const fs = __webpack_require__(722); +const ProgressEmitter = __webpack_require__(725); const cpFileAsync = async (source, destination, options, progressEmitter) => { let readError; @@ -84376,12 +84472,12 @@ module.exports.sync = (source, destination, options) => { /***/ }), -/* 715 */ +/* 717 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pTimeout = __webpack_require__(716); +const pTimeout = __webpack_require__(718); const symbolAsyncIterator = Symbol.asyncIterator || '@@asyncIterator'; @@ -84672,12 +84768,12 @@ module.exports.iterator = (emitter, event, options) => { /***/ }), -/* 716 */ +/* 718 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pFinally = __webpack_require__(717); +const pFinally = __webpack_require__(719); class TimeoutError extends Error { constructor(message) { @@ -84723,7 +84819,7 @@ module.exports.TimeoutError = TimeoutError; /***/ }), -/* 717 */ +/* 719 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84745,12 +84841,12 @@ module.exports = (promise, onFinally) => { /***/ }), -/* 718 */ +/* 720 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(719); +const NestedError = __webpack_require__(721); class CpFileError extends NestedError { constructor(message, nested) { @@ -84764,7 +84860,7 @@ module.exports = CpFileError; /***/ }), -/* 719 */ +/* 721 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(112).inherits; @@ -84820,16 +84916,16 @@ module.exports = NestedError; /***/ }), -/* 720 */ +/* 722 */ /***/ (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__(723); +const pEvent = __webpack_require__(717); +const CpFileError = __webpack_require__(720); const stat = promisify(fs.stat); const lstat = promisify(fs.lstat); @@ -84926,7 +85022,7 @@ exports.copyFileSync = (source, destination, flags) => { /***/ }), -/* 721 */ +/* 723 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84934,7 +85030,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__(724); const useNativeRecursiveOption = semver.satisfies(process.version, '>=10.12.0'); @@ -85089,7 +85185,7 @@ module.exports.sync = (input, options) => { /***/ }), -/* 722 */ +/* 724 */ /***/ (function(module, exports) { exports = module.exports = SemVer @@ -86691,7 +86787,7 @@ function coerce (version, options) { /***/ }), -/* 723 */ +/* 725 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86732,7 +86828,7 @@ module.exports = ProgressEmitter; /***/ }), -/* 724 */ +/* 726 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86778,12 +86874,12 @@ exports.default = module.exports; /***/ }), -/* 725 */ +/* 727 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pMap = __webpack_require__(726); +const pMap = __webpack_require__(728); const pFilter = async (iterable, filterer, options) => { const values = await pMap( @@ -86800,7 +86896,7 @@ module.exports.default = pFilter; /***/ }), -/* 726 */ +/* 728 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86879,12 +86975,12 @@ module.exports.default = pMap; /***/ }), -/* 727 */ +/* 729 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(719); +const NestedError = __webpack_require__(721); class CpyError extends NestedError { constructor(message, nested) { 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 96de75618fb8..5b25c1b5c1ce 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 af9cc0d98918..58ac45cb9b87 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 00dcc9c2c9ab..6f4075388094 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 e78cbbb3aed0..f13703811f58 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 8372d4bd41c4..b4b5066eb285 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/lib/failure_metadata.test.ts b/packages/kbn-test/src/functional_test_runner/lib/failure_metadata.test.ts index e3a66ece404d..e246eb595ef2 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 2d1cafae5162..e0afa0af856c 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/snapshots/decorate_snapshot_ui.test.ts b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.test.ts index a8b0252e6f51..5e16e038f14d 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 4a52299ecf6d..e64132c989fb 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 8545a6df46c3..c0945ed92f7d 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 3d19e45b81be..4ba242a41d80 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/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index c732fc7823b6..885e3e238243 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 0097127924a5..37ebbcaa752a 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/dev/precommit_hook/casing_check_config.js b/src/dev/precommit_hook/casing_check_config.js index d18e2e49d6b8..616d3cb4f3ed 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/data/common/search/expressions/esaggs/create_filter.test.ts b/src/plugins/data/common/search/expressions/esaggs/create_filter.test.ts index 076eeaba32c5..e4d4d0e5e7a1 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 e02d50485ad9..f6a372cacd50 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 1e48c5b78612..f12ab6ba068f 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 3932348928ac..72ac022e4fd3 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 70b1221a7473..fc9d47a5a2f7 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/tabify/index.ts b/src/plugins/data/common/search/tabify/index.ts index 9c650061fb01..f93b3fcde345 100644 --- a/src/plugins/data/common/search/tabify/index.ts +++ b/src/plugins/data/common/search/tabify/index.ts @@ -30,5 +30,3 @@ 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 9a37a2de9b5b..9d9060f75126 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 c03b477ca1cd..2026b51b8efb 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 02d734129d8e..f2c980196ab2 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/types.ts b/src/plugins/data/common/search/tabify/types.ts index 2dace451059b..779eec1b71ba 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/public/index.ts b/src/plugins/data/public/index.ts index fc8c44e8d187..15288d24726f 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 d0b711284df8..a9dbd1973ef9 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 78947feb88c2..408573e12eba 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -2476,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) // @@ -2622,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 18752b82bc18..01ba19a9d8df 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 0753e0e15a0a..6f7411cf0747 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/server/index.ts b/src/plugins/data/server/index.ts index 370ff180fa56..8853f1fc34f9 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 { diff --git a/src/plugins/data/server/search/expressions/esaggs.test.ts b/src/plugins/data/server/search/expressions/esaggs.test.ts index d71abbcd460c..229665d33887 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 8f73f970c7ef..13168879b8ba 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/server.api.md b/src/plugins/data/server/server.api.md index 635428f298ab..8f8761534d03 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -1324,27 +1324,6 @@ export function shimHitsTotal(response: SearchResponse, { legacyHitsTot // @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) @@ -1435,20 +1414,20 @@ 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:244:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:244:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:244:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:244:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:246:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:247:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:256:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:257:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:258:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:262:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:263:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:267:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:270:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts -// src/plugins/data/server/index.ts:271:1 - (ae-forgotten-export) The symbol "calcAutoIntervalLessThan" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:240:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:240:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:240:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:240:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:242:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:243:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:252:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:253:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:254:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:258:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:259:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:263:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:266:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/server/index.ts:267: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 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 cbd93feb835a..d8f826adbbb1 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 29ea9d5db9a3..6c2a3cbfc951 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/doc/doc.test.tsx b/src/plugins/discover/public/application/components/doc/doc.test.tsx index 25f5c1669b92..f96ca6ad9aff 100644 --- a/src/plugins/discover/public/application/components/doc/doc.test.tsx +++ b/src/plugins/discover/public/application/components/doc/doc.test.tsx @@ -29,6 +29,13 @@ jest.mock('../../../kibana_services', () => { 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 aad5b5e95ba3..9f78ae0e29ce 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 }} />{' '} - + { 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 444559a0b458..919429ca049e 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,9 @@ */ 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 { indexPatterns as indexPatternsUtils } from '../../../data/public'; export interface Location { min: number; @@ -32,7 +32,6 @@ export interface FunctionArg { export function getArgValueSuggestions() { const indexPatterns = getIndexPatterns(); - const savedObjectsClient = getSavedObjectsClient(); async function getIndexPattern(functionArgs: FunctionArg[]) { const indexPatternArg = functionArgs.find(({ name }) => name === 'index'); @@ -42,22 +41,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 +59,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(':')) { 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 0a85b1c1e5fe..5c23652c3207 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 e69b42d6c526..1d200be0d276 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 fca557efc01e..6a108fc76589 100644 --- a/src/plugins/vis_type_timelion/server/plugin.ts +++ b/src/plugins/vis_type_timelion/server/plugin.ts @@ -46,7 +46,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()) diff --git a/src/plugins/vis_type_timelion/server/routes/run.ts b/src/plugins/vis_type_timelion/server/routes/run.ts index ae26013cc39f..2f6c0d709fdc 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/series_functions/es/es.test.js b/src/plugins/vis_type_timelion/server/series_functions/es/es.test.js index 671042ae6f24..8828fd6917fe 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 bce048503956..7aacc1c1632f 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/visualize/kibana.json b/src/plugins/visualize/kibana.json index 2256a7a7f550..144d33debe3c 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 f83e1f193032..000000000000 --- 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 8d02e0854966..e240e391d605 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 48c9965b4210..ced651047814 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 25db806109aa..000000000000 --- 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 57fa9b2ae480..000000000000 --- 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/common/services/es_archiver.ts b/test/common/services/es_archiver.ts index e1b85ddf8bc9..e6d4a8a56af2 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/functional/apps/discover/_discover.ts b/test/functional/apps/discover/_discover.ts index bf0a02755383..552022241a72 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 f8f45d2bc710..d4818d99565e 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 e11ef249d8c7..000000000000 --- 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/index.ts b/test/functional/apps/discover/index.ts index 4f42b41a1e02..b4fc4ead2d52 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')); diff --git a/test/functional/apps/management/_scripted_fields.js b/test/functional/apps/management/_scripted_fields.js index 62edbc50879a..d1a4c93cec04 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/index.ts b/test/functional/apps/management/index.ts index ca8985387502..3de11fbf4c99 100644 --- a/test/functional/apps/management/index.ts +++ b/test/functional/apps/management/index.ts @@ -31,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/page_objects/discover_page.ts b/test/functional/page_objects/discover_page.ts index 88a138ee09bf..33cee4f40d08 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/visual_regression/services/visual_testing/visual_testing.ts b/test/visual_regression/services/visual_testing/visual_testing.ts index 777306017083..064f43040c47 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/vars/githubPr.groovy b/vars/githubPr.groovy index 5224aa7463d7..eead00c082ba 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 3032d88c26d9..17349f6b566d 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 2cc22e73857b..d082672c065a 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 6c4f89769113..7c40966ff5e0 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 9f35907ca335..661e01038c5d 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/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 0e5a7d56e906..6f0d8571ab28 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 aa8b406cf777..f4fabc29a6b5 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 fd21507a427d..ba0079223797 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/relevance_tuning/index.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/components/relevance_tuning/index.ts index 40f3ddbf2899..55070255ac81 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 000000000000..5934aca6be5f --- /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 000000000000..cca352904930 --- /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/routes.ts b/x-pack/plugins/enterprise_search/public/applications/app_search/routes.ts index 7f12f7d29671..080f6efcc71f 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/server/__mocks__/router.mock.ts b/x-pack/plugins/enterprise_search/server/__mocks__/router.mock.ts index c02d5cf0ff13..819cabec44f0 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 a20e7854db17..c384826f469f 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 000000000000..56a6e6297d1f --- /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 000000000000..eb50d736ac97 --- /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/fleet/server/services/agents/crud_so.ts b/x-pack/plugins/fleet/server/services/agents/crud_so.ts index eb8f389741a6..7111a2413914 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 ba8f8fc36385..726d188f723d 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 cdb23da5b6b1..88d60116672d 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/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 d9256ec916ec..5d5d8a85163c 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 05793a4bed58..9cff3953c2e1 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 779dbe47914a..cddcd92a0f72 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 ed7ca6041767..000000000000 --- 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 000000000000..4e4adc6530f3 --- /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 960b632d70bd..c384ef7531bb 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 976f584ef4d3..405cdd5bcde7 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 5c43bb413eb5..a3196ddcf023 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 02de47f8c56e..70740ddb81f8 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 47417b637630..d8c6fec557dc 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 6de18f1c1d3c..829c75bdced6 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 000000000000..f156ddcaee96 --- /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 8af5314c16b1..3dc0d6d45a9b 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 9f60337166f4..371cb95f8091 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 8d6807c90dae..cd6e9f83eb13 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 570033812c24..79b4b49cbb65 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 8a84b7fa0e76..9937ae2a0b5b 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 6d8e019ff8a0..189fdc2fdf6d 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 5fa192158fb3..0050fe1d87e9 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 da200e9e68d1..33f6fc83b184 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 05f12f6ba61c..ece362e5ae01 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 b1cf41773de3..420abdf02013 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 000000000000..332a8c2ba369 --- /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 2b3411e394a9..cad029478c49 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 15d8d4ed272e..06cfa5daf599 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 000000000000..e4c01e35476f --- /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 66fe498cbac8..e8a63295b4b0 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 fdb25dec6f1f..daaf1fa6ffd6 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/infra/common/graphql/root/schema.gql.ts b/x-pack/plugins/infra/common/graphql/root/schema.gql.ts deleted file mode 100644 index 1665334827e8..000000000000 --- 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 c324813b65ef..000000000000 --- 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 071313817eff..000000000000 --- 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 ee536feb1ce6..000000000000 --- a/x-pack/plugins/infra/common/graphql/types.ts +++ /dev/null @@ -1,780 +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 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; -} - -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 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 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; - -// ==================================================== -// END: Typescript template -// ==================================================== - -// ==================================================== -// Documents -// ==================================================== - -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 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; - }; -} 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 0ef5ae82baeb..6de21da53c36 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 be50989358c7..52a8d43da53b 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 bf3f9ceb0b08..837249b65b2e 100644 --- a/x-pack/plugins/infra/common/log_entry/log_entry.ts +++ b/x-pack/plugins/infra/common/log_entry/log_entry.ts @@ -14,7 +14,6 @@ export type LogEntryTime = TimeKey; /** * message parts */ - export const logMessageConstantPartRT = rt.type({ constant: rt.string, }); diff --git a/x-pack/plugins/infra/docs/arch.md b/x-pack/plugins/infra/docs/arch.md index f3d7312a3491..89b00cd19d1d 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 cdc474635721..b40c9aaf1ff5 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 5584a5ce7c0d..000000000000 --- 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 000000000000..1571b981632d --- /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 8fae6c6a5134..ea0c5a6e54b5 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 cdab53c92d32..62c373a7a341 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 ebfa412410dd..ad1c1e1129de 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 381c75c4b9a2..8a6a2e273f2c 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 ( { +export const useSourceConfigurationFormState = (configuration?: InfraSourceConfiguration) => { const indicesConfigurationFormState = useIndicesConfigurationFormState({ initialFormState: useMemo( () => 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 6727dea712f3..000000000000 --- 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 21b5192e5725..000000000000 --- 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 96bbd858c3a4..c84269d6b498 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 61312a0f2890..000000000000 --- 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 ed52780049f6..000000000000 --- 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 94e2537a67a2..33f74173bee3 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 e8c609ae0bad..1681bf85f7f1 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 efdca72c1383..000000000000 --- a/x-pack/plugins/infra/public/graphql/introspection.json +++ /dev/null @@ -1,2713 +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": "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": "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": "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 eb025ee4efd7..000000000000 --- a/x-pack/plugins/infra/public/graphql/types.ts +++ /dev/null @@ -1,786 +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 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; -} - -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 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 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; - -// ==================================================== -// END: Typescript template -// ==================================================== - -// ==================================================== -// Documents -// ==================================================== - -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 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; - }; -} diff --git a/x-pack/plugins/infra/public/lib/lib.ts b/x-pack/plugins/infra/public/lib/lib.ts index 782f6ce5e0eb..60e1cf0b8bc2 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/metrics/index.tsx b/x-pack/plugins/infra/public/pages/metrics/index.tsx index 24f9598484d7..1b2ea9d551a8 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 6055b60719a6..82a24fe8163e 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 @@ -67,12 +67,13 @@ export const BottomDrawer: React.FC<{ const BottomActionContainer = euiStyled.div<{ isOpen: boolean }>` padding: ${(props) => props.theme.eui.paddingSizes.m} 0; - position: fixed; + position: absolute; + height: ${(props) => (props.isOpen ? '244px' : '48px')}; + overflow: ${(props) => (props.isOpen ? 'visible' : 'hidden')}; left: 0; bottom: 0; right: 0; - transition: transform ${TRANSITION_MS}ms; - transform: translateY(${(props) => (props.isOpen ? 0 : '224px')}) + transition: height ${TRANSITION_MS}ms; `; const BottomActionTopBar = euiStyled(EuiFlexGroup).attrs({ 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 c2cde7eb15e9..3bcf24793b6c 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 bda2a5941e02..9eb7df26565e 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 0d7716ad3cc6..91b1bb9b0c1c 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 c228f09cbb64..256fabdb792f 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 5d38fd436fcc..4fb13f72217b 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 270ccac00063..b9c00101d7b3 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 15ed28c09519..90338d714488 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 169bc9bcbcdb..7a235b9da64c 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 f566e5253c61..689a8146afa0 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 924f59eb0d1d..1315b5917c97 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 20efca79650a..8ca921f4f266 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/utils/apollo_client.ts b/x-pack/plugins/infra/public/utils/apollo_client.ts deleted file mode 100644 index 41831a03cabb..000000000000 --- 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 a33a1964c4d8..000000000000 --- 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/source_configuration.ts b/x-pack/plugins/infra/public/utils/source_configuration.ts index 5104ed46000f..cbffe7826f81 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/scripts/combined_schema.ts b/x-pack/plugins/infra/scripts/combined_schema.ts deleted file mode 100644 index 4813a3b674b3..000000000000 --- 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 aec5ff6da99c..000000000000 --- 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 f5150972a3a6..000000000000 --- 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 bd92dd0f7da8..000000000000 --- 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 e0482382c6d6..000000000000 --- 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 15c4a6677946..000000000000 --- 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 dbd0696fe3e0..000000000000 --- 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 712438ce2bfe..000000000000 --- a/x-pack/plugins/infra/server/graphql/types.ts +++ /dev/null @@ -1,1111 +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 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; -} - -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 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 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; - -// ==================================================== -// 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 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 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; -} - -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 b510519a4fd0..6ce5d5b8c53e 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 b96b0e5bb0b4..6c83dae32912 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/metrics/lib/errors.ts b/x-pack/plugins/infra/server/lib/adapters/metrics/lib/errors.ts deleted file mode 100644 index 750858f3ce1f..000000000000 --- 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 a8bd09c28f94..3b0a1c61f6af 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/infra_types.ts b/x-pack/plugins/infra/server/lib/infra_types.ts index 084ece52302b..eb24efb84645 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/routes/log_sources/status.ts b/x-pack/plugins/infra/server/routes/log_sources/status.ts index e9466f7fa887..71a91e44669d 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 9ff3902f1eae..31d309c6295f 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/utils/serialized_query.ts b/x-pack/plugins/infra/server/utils/serialized_query.ts index 8dfe00fcd380..3a38a0de959d 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 d5f2d00abd50..000000000000 --- 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 5e5320a31b3b..000000000000 --- 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 3df9e8a5145b..93319aeb8d81 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 0a9e9799bbc3..5c61f4ed39b0 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 539e20360620..580dadc88700 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 0c0aa34bb40b..bc5bbcf56b33 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 c95467ab04e1..0d378f87585a 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 c4a3fd094abe..4ce4aa8aed13 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 5ee33f9b4b3d..7344a9d681b2 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 d8c475734e67..99058a5dce87 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 f1eb4ccdd9cf..58f2a286c4ae 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 792f4d7a0b97..603f61c4b8ef 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 2d9a345b978e..e3701363fa15 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 3c312abf1fd9..256d9efe35ea 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 931e62ea1d13..ef25e5d6acdb 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 210101dc25c7..d83b3d59733f 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 bd479062e2a0..17682256a8a1 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 fda7c93af03a..02f28b8801b9 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 88e95f2ca327..759674f8b013 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 cab1a0185333..df0b071f1893 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 e05871fd35a5..30df138cde9d 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 a308a0c29302..c181df9394cd 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 9fa6ad8ee30a..a180473ed31a 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 8ac014c820ac..73d3bcb1eea0 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 24b7e0dec519..8e82a60a802c 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 3f8b9d3e28e1..61b3ee348b46 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 b70a433f2c72..8c5c63931db2 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/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 0945dc57fa51..000000000000 --- 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 0fc99ffd811f..000000000000 --- 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 9fbaba21e71d..000000000000 --- 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 c959d03c6ef8..000000000000 --- 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 ee4584904271..fb2b1b675dde 100644 --- a/x-pack/plugins/maps/server/mvt/get_tile.ts +++ b/x-pack/plugins/maps/server/mvt/get_tile.ts @@ -8,7 +8,8 @@ import geojsonvt from 'geojson-vt'; // @ts-expect-error import vtpbf from 'vt-pbf'; -import { Logger } from 'src/core/server'; +import { Logger, RequestHandlerContext } from 'src/core/server'; +import type { DataApiRequestHandlerContext } 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: RequestHandlerContext & { search: DataApiRequestHandlerContext }; 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: RequestHandlerContext & { search: DataApiRequestHandlerContext }; 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 fc298f73b04a..65692619b333 100644 --- a/x-pack/plugins/maps/server/mvt/mvt_routes.ts +++ b/x-pack/plugins/maps/server/mvt/mvt_routes.ts @@ -13,6 +13,7 @@ import { RequestHandlerContext, } from 'src/core/server'; import { IRouter } from 'src/core/server'; +import type { DataApiRequestHandlerContext } from 'src/plugins/data/server'; import { MVT_GETTILE_API_PATH, API_ROOT_PATH, @@ -24,7 +25,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 +44,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: RequestHandlerContext & { search: DataApiRequestHandlerContext }, request: KibanaRequest, unknown>, response: KibanaResponseFactory ) => { @@ -50,7 +58,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 +66,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 +86,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: RequestHandlerContext & { search: DataApiRequestHandlerContext }, request: KibanaRequest, unknown>, response: KibanaResponseFactory ) => { @@ -90,7 +100,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 +109,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 +136,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/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 98a8e4c9cbf2..f8771e9e19d8 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/server/models/job_service/error_utils.ts b/x-pack/plugins/ml/server/models/job_service/error_utils.ts index dc871a9dce80..6afce76cdc6c 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 81b0494cbdf2..ca7bfe997451 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/new_job/line_chart.ts b/x-pack/plugins/ml/server/models/job_service/new_job/line_chart.ts index 93f7c81c1002..76404b0aa5b6 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 e614f887e29b..94b25ac3f0d8 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 73d35efd66c8..98c6ff3d5606 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/saved_objects/checks.ts b/x-pack/plugins/ml/server/saved_objects/checks.ts index 9258b143c974..90771f0938d2 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/tsconfig.json b/x-pack/plugins/ml/tsconfig.json new file mode 100644 index 000000000000..113bcbe71047 --- /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/security_solution/common/search_strategy/timeline/events/index.ts b/x-pack/plugins/security_solution/common/search_strategy/timeline/events/index.ts index 6bb946199597..9dca1558d5a6 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 10750503fc80..d6c1be0594c0 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 d3ec2763f939..f6b937b516fd 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 f0735bd9717e..d7813f97d400 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 6aa60f25c12b..01a77f7b59b7 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 c64cb2087252..737143c7a3ef 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 8d5201e68371..58858fd71481 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/detections/containers/detection_engine/alerts/mock.ts b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/alerts/mock.ts index dfe71568a1c3..a7eaa1bde5b4 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 a26ac23c7f5b..258c0a3d2599 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 6fe8b279498a..b6de0ef3b451 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/graphql/introspection.json b/x-pack/plugins/security_solution/public/graphql/introspection.json index 9e0cf10a54aa..a2f1f222173d 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 435576a02b30..a5e027c69546 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 000000000000..3a2f96e42025 --- /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 0e948afd5d7c..6e77971b8553 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 000000000000..b8dc10a878f8 --- /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 6492731cdeba..8c4a0aa12ef8 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/open_timeline/note_previews/index.tsx b/x-pack/plugins/security_solution/public/timelines/components/open_timeline/note_previews/index.tsx index d6ea611660ed..2c7b917d373c 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 f99b94003258..ce14ae98afe0 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/server/graphql/timeline/resolvers.ts b/x-pack/plugins/security_solution/server/graphql/timeline/resolvers.ts index fc14663b138b..3d049374d6c5 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 ca6c57f025fa..933c3e912389 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 3ea964c0ee01..783e61106387 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/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 000000000000..34165ab7bc59 --- /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/infra/server/graphql/sources/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/index.ts similarity index 62% rename from x-pack/plugins/infra/server/graphql/sources/index.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/index.ts index ee187d8c31be..ff3c017590f1 100644 --- a/x-pack/plugins/infra/server/graphql/sources/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/hunter/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 * 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 000000000000..f8d5e1302443 --- /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 000000000000..bc6ae6688d44 --- /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/infra/common/graphql/shared/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/index.ts similarity index 62% rename from x-pack/plugins/infra/common/graphql/shared/index.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/index.ts index 56c8675e76ca..7344f8eb9d5e 100644 --- a/x-pack/plugins/infra/common/graphql/shared/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/reader/index.ts @@ -4,5 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export { sharedFragments } from './fragments.gql_query'; -export { sharedSchema } from './schema.gql'; +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 000000000000..748c3c8536f6 --- /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 000000000000..19a6dbaaea98 --- /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/infra/server/graphql/source_status/index.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/index.ts similarity index 61% rename from x-pack/plugins/infra/server/graphql/source_status/index.ts rename to x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/index.ts index abc91fa3815c..3ea5cb0f4235 100644 --- a/x-pack/plugins/infra/server/graphql/source_status/index.ts +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/scripts/roles_users/t1_analyst/index.ts @@ -4,4 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export { createSourceStatusResolvers } from './resolvers'; +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 000000000000..99e503039971 --- /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/utils.test.ts b/x-pack/plugins/security_solution/server/lib/detection_engine/signals/utils.test.ts index 2574abd73b6c..58b21316720c 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 @@ -1434,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 274e4feffcc3..6427dcf8c248 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 @@ -855,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 83566a619061..c2698749f9d8 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/timeline/factory/events/details/index.ts b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/details/index.ts index d9fa4fdda9be..7e657757d247 100644 --- a/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/details/index.ts +++ b/x-pack/plugins/security_solution/server/search_strategy/timeline/factory/events/details/index.ts @@ -32,11 +32,19 @@ export const timelineEventsDetails: SecuritySolutionTimelineFactory = { [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 000000000000..ee84a0faab2c --- /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 000000000000..b0333411bdee --- /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/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index fdf1c74f2051..cab6973072f2 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}で問題を報告してください。", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index b3fefd55ce55..dbd4c426c345 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -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} 中创建问题。", 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 b01104a8d5cf..413d17dcf76c 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 c6da09ea716c..00971215b470 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('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 9dd0698b6c6e..e39f8a72ae6a 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 d2b155984378..000000000000 --- 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 7e92caf0e37d..16816f2ce90a 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 819a2d35b92a..47e688f09c78 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/metadata.ts b/x-pack/test/api_integration/apis/metrics_ui/metadata.ts index e319e59045d2..e8bd547b3143 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 b9cbc58bbd6f..e7cf2962cb31 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 7339c142fb02..26048f25d55f 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 5908523af249..d18ce882cab7 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/security_solution/saved_objects/timeline.ts b/x-pack/test/api_integration/apis/security_solution/saved_objects/timeline.ts index 07e7cad89c24..b576d6386d9d 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 aec9a896afc7..da104e5124b3 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/services/index.ts b/x-pack/test/api_integration/services/index.ts index 7113e117582d..2ac6f30b1e18 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 81f0ac8fad91..000000000000 --- 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 728a58829d1c..7c6f2c5aa731 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/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 5098ff157b11..ec3d1dd1b0c5 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/create_index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_index.ts new file mode 100644 index 000000000000..232d881a1092 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/create_index.ts @@ -0,0 +1,425 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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'); + + describe('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/index.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/index.ts index 44e033e96c89..53008678a78e 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 000000000000..58df4b4a5c58 --- /dev/null +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/read_privileges.ts @@ -0,0 +1,582 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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'); + + describe('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/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 4e234dd8e550..6b4b9c61151b 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/remote_clusters/feature_controls/remote_clusters_security.ts b/x-pack/test/functional/apps/remote_clusters/feature_controls/remote_clusters_security.ts index e6b5a7ac77d7..b1edc7460716 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/upgrade_assistant/upgrade_assistant.ts b/x-pack/test/functional/apps/upgrade_assistant/upgrade_assistant.ts index 57b8fb23613b..9e742ca5463a 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/page_objects/upgrade_assistant_page.ts b/x-pack/test/functional/page_objects/upgrade_assistant_page.ts index 3fe60747505a..739b796c5a62 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/saved_object_tagging/functional/tests/maps_integration.ts b/x-pack/test/saved_object_tagging/functional/tests/maps_integration.ts index 32b9cc378db4..8b7a6affb02e 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/tsconfig.json b/x-pack/test/tsconfig.json index 6039cd330c14..10e293e65926 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -52,6 +52,7 @@ { "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/triggers_actions_ui/tsconfig.json" }, diff --git a/x-pack/tsconfig.json b/x-pack/tsconfig.json index 56420b503dd5..33dc28ca3da4 100644 --- a/x-pack/tsconfig.json +++ b/x-pack/tsconfig.json @@ -26,6 +26,7 @@ "plugins/maps/**/*", "plugins/maps_file_upload/**/*", "plugins/maps_legacy_licensing/**/*", + "plugins/ml/**/*", "plugins/observability/**/*", "plugins/reporting/**/*", "plugins/searchprofiler/**/*", @@ -118,6 +119,7 @@ { "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" }, diff --git a/x-pack/tsconfig.refs.json b/x-pack/tsconfig.refs.json index 82b52c959b6d..edb776f00f77 100644 --- a/x-pack/tsconfig.refs.json +++ b/x-pack/tsconfig.refs.json @@ -29,6 +29,7 @@ { "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" }, diff --git a/yarn.lock b/yarn.lock index 734173756ae2..c51ff54800c2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7355,6 +7355,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"