diff --git a/.ci/jobs.yml b/.ci/jobs.yml index 1740e1db33f2d..f9302db4b2910 100644 --- a/.ci/jobs.yml +++ b/.ci/jobs.yml @@ -1,7 +1,26 @@ JOB: - - selenium - - intake - - x-pack + - kibana-intake + - x-pack-intake + # make sure all kibana-ciGRoups are listed in tasks/function_test_groups.js + - kibana-ciGroup1 + - kibana-ciGroup2 + - kibana-ciGroup3 + - kibana-ciGroup4 + - kibana-ciGroup5 + - kibana-ciGroup6 + - kibana-ciGroup7 + - kibana-ciGroup8 + - kibana-ciGroup9 + - kibana-ciGroup10 + - kibana-ciGroup11 + - kibana-ciGroup12 + # make sure all x-pack-ciGroups are listed in test/scripts/jenkins_xpack_ci_group.sh + - x-pack-ciGroup1 + - x-pack-ciGroup2 + - x-pack-ciGroup3 + - x-pack-ciGroup4 + - x-pack-ciGroup5 + - x-pack-ciGroup6 # `~` is yaml for `null` exclude: ~ diff --git a/.ci/packer_cache.sh b/.ci/packer_cache.sh index 97168ff0262d1..85cefec469a75 100755 --- a/.ci/packer_cache.sh +++ b/.ci/packer_cache.sh @@ -1,3 +1,16 @@ #!/usr/bin/env bash +# run setup script that gives us node, yarn, and bootstraps the project source "src/dev/ci_setup/setup.sh"; + +# cache es snapshots +node scripts/es snapshot --download-only; + +# archive cacheable directories +mkdir -p "$HOME/.kibana/bootstrap_cache" +tar -cf "$HOME/.kibana/bootstrap_cache/master.tar" \ + node_modules \ + packages/*/node_modules \ + x-pack/node_modules \ + x-pack/plugins/*/node_modules \ + .es; diff --git a/.ci/run.sh b/.ci/run.sh index 32c138bd2f450..1761c5e78cdcf 100755 --- a/.ci/run.sh +++ b/.ci/run.sh @@ -5,16 +5,23 @@ set -e # move to Kibana root cd "$(dirname "$0")/.." +./src/dev/ci_setup/load_bootstrap_cache.sh; + case "$JOB" in -"selenium") - ./test/scripts/jenkins_selenium.sh - ;; -"intake") +kibana-intake) ./test/scripts/jenkins_unit.sh ;; -"x-pack") +kibana-ciGroup*) + export CI_GROUP="${JOB##kibana-ciGroup}" + ./test/scripts/jenkins_ci_group.sh + ;; +x-pack-intake) ./test/scripts/jenkins_xpack.sh ;; +x-pack-ciGroup*) + export CI_GROUP="${JOB##x-pack-ciGroup}" + ./test/scripts/jenkins_xpack_ci_group.sh + ;; *) echo "JOB '$JOB' is not implemented." exit 1 diff --git a/.eslintignore b/.eslintignore index f697ad004caab..a734d3f9df2c7 100644 --- a/.eslintignore +++ b/.eslintignore @@ -8,8 +8,6 @@ bower_components /src/fixtures/vislib/mock_data /src/ui/public/angular-bootstrap /src/ui/public/flot-charts -/src/ui/public/kuery/ast/kuery.js -/src/ui/public/kuery/ast/legacy_kuery.js /test/fixtures/scenarios /src/core_plugins/console/public/webpackShims /src/core_plugins/console/public/tests/webpackShims @@ -19,6 +17,8 @@ bower_components /packages/*/target /packages/eslint-config-kibana /packages/eslint-plugin-kibana-custom +/packages/kbn-es-query/src/kuery/ast/kuery.js +/packages/kbn-es-query/src/kuery/ast/legacy_kuery.js /packages/kbn-pm/dist /packages/kbn-plugin-generator/sao_template/template /packages/kbn-ui-framework/dist @@ -27,7 +27,7 @@ bower_components /x-pack/coverage /x-pack/build /x-pack/plugins/**/__tests__/fixtures/** -/x-pack/plugins/canvas/common/lib/grammar.js +/packages/kbn-interpreter/src/common/lib/grammar.js /x-pack/plugins/canvas/canvas_plugin /x-pack/plugins/canvas/canvas_plugin_src/lib/flot-charts **/*.js.snap diff --git a/.eslintrc.js b/.eslintrc.js index 131b3851e726b..7dcecacceae8b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -102,7 +102,7 @@ module.exports = { * Files that ARE NOT allowed to use devDependencies */ { - files: ['packages/kbn-ui-framework/**/*', 'x-pack/**/*'], + files: ['packages/kbn-ui-framework/**/*', 'x-pack/**/*', 'packages/kbn-interpreter/**/*'], rules: { 'import/no-extraneous-dependencies': [ 'error', @@ -124,6 +124,8 @@ module.exports = { 'packages/kbn-ui-framework/generator-kui/**/*', 'packages/kbn-ui-framework/Gruntfile.js', 'packages/kbn-es/src/**/*', + 'packages/kbn-interpreter/tasks/**/*', + 'packages/kbn-interpreter/src/plugin/**/*', 'x-pack/{dev-tools,tasks,scripts,test,build_chromium}/**/*', 'x-pack/**/{__tests__,__test__,__jest__,__fixtures__,__mocks__}/**/*', 'x-pack/**/*.test.js', diff --git a/.i18nrc.json b/.i18nrc.json index 4f7027e36ee3d..45e992cc7d5c6 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -11,11 +11,17 @@ "tableVis": "src/core_plugins/table_vis", "regionMap": "src/core_plugins/region_map", "statusPage": "src/core_plugins/status_page", + "tagCloud": "src/core_plugins/tagcloud", + "tagCloud": "src/core_plugins/tagcloud", "tileMap": "src/core_plugins/tile_map", "timelion": "src/core_plugins/timelion", + "tsvb": "src/core_plugins/metrics", "tagCloud": "src/core_plugins/tagcloud", + "tsvb": "src/core_plugins/metrics", + "xpack.graph": "x-pack/plugins/graph", "xpack.grokDebugger": "x-pack/plugins/grokdebugger", "xpack.idxMgmt": "x-pack/plugins/index_management", + "xpack.infra": "x-pack/plugins/infra", "xpack.licenseMgmt": "x-pack/plugins/license_management", "xpack.monitoring": "x-pack/plugins/monitoring", "xpack.rollupJobs": "x-pack/plugins/rollup", @@ -25,6 +31,8 @@ }, "exclude": [ "src/ui/ui_render/bootstrap/app_bootstrap.js", - "src/ui/ui_render/ui_render_mixin.js" + "src/ui/ui_render/ui_render_mixin.js", + "x-pack/plugins/infra/public/utils/loading_state/loading_result.ts", + "x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts" ] } diff --git a/README.md b/README.md index d48765f1f60da..e075818dfd972 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,6 @@ Kibana is your window into the [Elastic Stack](https://www.elastic.co/products). - [Getting Started](#getting-started) - [Using a Kibana Release](#using-a-kibana-release) - [Building and Running Kibana, and/or Contributing Code](#building-and-running-kibana-andor-contributing-code) - - [Snapshot Builds](#snapshot-builds) - [Documentation](#documentation) - [Version Compatibility with Elasticsearch](#version-compatibility-with-elasticsearch) - [Questions? Problems? Suggestions?](#questions-problems-suggestions) diff --git a/docs/development/visualize/development-create-visualization.asciidoc b/docs/development/visualize/development-create-visualization.asciidoc index 56ed620e148c5..3e8ff5dcc636e 100644 --- a/docs/development/visualize/development-create-visualization.asciidoc +++ b/docs/development/visualize/development-create-visualization.asciidoc @@ -31,7 +31,6 @@ You should also register the visualization with `VisTypesRegistryProvider`. ["source","js"] ----------- -import { CATEGORY } from 'ui/vis/vis_category'; import { VisFactoryProvider } from 'ui/vis/vis_factory'; import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; @@ -43,7 +42,6 @@ const MyNewVisType = (Private) => { title: 'My New Vis', icon: 'my_icon', description: 'Cool new chart', - category: CATEGORY.OTHER ... }); } @@ -59,7 +57,7 @@ The list of common parameters: - *image*: instead of an icon you can provide a SVG image (imported) - *legacyIcon*: (DEPRECATED) provide a class name (e.g. for a font awesome icon) - *description*: description of your visualization as shown in kibana -- *category*: the category your visualization falls into (one of `ui/vis/vis_category` values) +- *hidden*: if set to true, will hide the type from showing up in the visualization wizard - *visConfig*: object holding visualization parameters - *visConfig.defaults*: object holding default visualization configuration - *visualization*: A constructor function for a Visualization. @@ -71,8 +69,8 @@ The list of common parameters: - *options.showQueryBar*: show or hide query bar (defaults to true) - *options.showFilterBar*: show or hide filter bar (defaults to true) - *options.showIndexSelection*: show or hide index selection (defaults to true) -- *stage*: Set this to "experimental" or "labs" to mark your visualization as experimental. -Labs visualizations can also be disabled from the advanced settings. (defaults to "production") +- *stage*: Set this to "experimental" to mark your visualization as experimental. +Experimental visualizations can also be disabled from the advanced settings. (defaults to "production") - *feedbackMessage*: You can provide a message (which can contain HTML), that will be appended to the experimental notification in visualize, if your visualization is experimental or in lab mode. diff --git a/docs/development/visualize/development-embedding-visualizations.asciidoc b/docs/development/visualize/development-embedding-visualizations.asciidoc index ecfa93ba9e5b2..4748f3b546b13 100644 --- a/docs/development/visualize/development-embedding-visualizations.asciidoc +++ b/docs/development/visualize/development-embedding-visualizations.asciidoc @@ -55,4 +55,4 @@ The returned `EmbeddedVisualizeHandler` itself has the following methods and pro - `removeRenderCompleteListener(listener)`: removes an event listener from the handler again You can find the detailed `EmbeddedVisualizeHandler` documentation in its -{repo}blob/{branch}/src/ui/public/visualize/loader/embedded_visualize_handler.js[source code]. \ No newline at end of file +{repo}blob/{branch}/src/ui/public/visualize/loader/embedded_visualize_handler.ts[source code]. \ No newline at end of file diff --git a/docs/monitoring/elasticsearch-details.asciidoc b/docs/monitoring/elasticsearch-details.asciidoc index 025fc8f2887cb..c75b2880f85ae 100644 --- a/docs/monitoring/elasticsearch-details.asciidoc +++ b/docs/monitoring/elasticsearch-details.asciidoc @@ -98,3 +98,35 @@ image::monitoring/images/monitoring-index-advanced.png["Elasticsearch Index Adva The Advanced index view can be used to diagnose issues that generally involve more advanced knowledge of {es}, such as wasteful index memory usage. + +[float] +[[jobs-page]] +==== Jobs + +To view {ml} job metrics, click **Jobs**. For each job in your cluster, it shows +information such as its status, the number of records processed, the size of the +model, the number of forecasts, and the node that runs the job. + +image::monitoring/images/monitoring-jobs.png["Machine learning jobs",link="images/monitoring-jobs.png"] + +[float] +[[ccr-overview-page]] +==== CCR + +beta[] + +To view {ccr} metrics, click **CCR**. For each follower index on the cluster, it +shows information such as the leader index, an indication of how much the +follower index is lagging behind the leader index, the last fetch time, the +number of operations synced, and error messages. If you select a follower index, +you can view the same information for each shard. For example: + +image::monitoring/images/monitoring-ccr.png["Cross-cluster replication",link="images/monitoring-ccr.png"] + +If you select a shard, you can see graphs for the fetch and operation delays. +You can also see advanced information, which contains the results from the +{ref}/ccr-get-follow-stats.html[get follower stats API]. For example: + +image::monitoring/images/monitoring-ccr-shard.png["Cross-cluster replication shard details",link="images/monitoring-ccr-shard.png"] + +For more information, see {stack-ov}/xpack-ccr.html[Cross-cluster replication]. diff --git a/docs/monitoring/images/monitoring-ccr-shard.png b/docs/monitoring/images/monitoring-ccr-shard.png new file mode 100644 index 0000000000000..1fb7f4dfebd26 Binary files /dev/null and b/docs/monitoring/images/monitoring-ccr-shard.png differ diff --git a/docs/monitoring/images/monitoring-ccr.png b/docs/monitoring/images/monitoring-ccr.png new file mode 100644 index 0000000000000..eb8f417ae4761 Binary files /dev/null and b/docs/monitoring/images/monitoring-ccr.png differ diff --git a/docs/monitoring/images/monitoring-jobs.png b/docs/monitoring/images/monitoring-jobs.png new file mode 100644 index 0000000000000..bf454f1edb01d Binary files /dev/null and b/docs/monitoring/images/monitoring-jobs.png differ diff --git a/docs/settings/monitoring-settings.asciidoc b/docs/settings/monitoring-settings.asciidoc index 52d07d03db2e6..1a84e40069c10 100644 --- a/docs/settings/monitoring-settings.asciidoc +++ b/docs/settings/monitoring-settings.asciidoc @@ -6,8 +6,11 @@ ++++ By default, the Monitoring application is enabled, but data collection -is disabled. When you first start {kib} monitoring, you will be prompted to -enable data collection. +is disabled. When you first start {kib} monitoring, you are prompted to +enable data collection. If you are using {security}, you must be +signed in as a user with the `cluster:manage` privilege to enable +data collection. The built-in `superuser` role has this privilege and the +built-in `elastic` user has this role. You can adjust how monitoring data is collected from {kib} and displayed in {kib} by configuring settings in the diff --git a/package.json b/package.json index bb04bc804fcc8..a982137a2d9d8 100644 --- a/package.json +++ b/package.json @@ -88,10 +88,12 @@ "@kbn/babel-preset": "1.0.0", "@kbn/config-schema": "1.0.0", "@kbn/datemath": "5.0.0", + "@kbn/es-query": "1.0.0", "@kbn/i18n": "1.0.0", "@kbn/pm": "1.0.0", "@kbn/test-subj-selector": "0.2.1", "@kbn/ui-framework": "1.0.0", + "@kbn/interpreter": "1.0.0", "JSONStream": "1.1.1", "abortcontroller-polyfill": "^1.1.9", "angular": "1.6.9", @@ -145,7 +147,6 @@ "js-yaml": "3.4.1", "json-stringify-pretty-compact": "1.0.4", "json-stringify-safe": "5.0.1", - "jstimezonedetect": "1.0.5", "leaflet": "1.0.3", "leaflet-draw": "0.4.10", "leaflet-responsive-popup": "0.2.0", @@ -198,6 +199,8 @@ "rxjs": "^6.2.1", "script-loader": "0.7.2", "semver": "^5.5.0", + "socket.io": "^2.1.1", + "stream-stream": "^1.2.6", "style-loader": "0.19.0", "tar": "2.2.0", "tinygradient": "0.3.0", @@ -244,7 +247,7 @@ "@types/boom": "^7.2.0", "@types/chance": "^1.0.0", "@types/classnames": "^2.2.3", - "@types/d3": "^5.0.0", + "@types/d3": "^3.5.41", "@types/dedent": "^0.7.0", "@types/del": "^3.0.1", "@types/elasticsearch": "^5.0.26", @@ -258,6 +261,7 @@ "@types/hapi": "^17.0.18", "@types/has-ansi": "^3.0.0", "@types/hoek": "^4.1.3", + "@types/humps": "^1.1.2", "@types/jest": "^23.3.1", "@types/joi": "^13.4.2", "@types/jquery": "^3.3.6", @@ -384,4 +388,4 @@ "node": "8.11.4", "yarn": "^1.10.1" } -} \ No newline at end of file +} diff --git a/packages/kbn-datemath/package.json b/packages/kbn-datemath/package.json index 40eb6de14b04e..00d21163006f8 100644 --- a/packages/kbn-datemath/package.json +++ b/packages/kbn-datemath/package.json @@ -8,7 +8,7 @@ "typings": "target/index.d.ts", "scripts": { "build": "babel src --out-dir target --copy-files", - "kbn:bootstrap": "yarn build", + "kbn:bootstrap": "yarn build --quiet", "kbn:watch": "yarn build --watch" }, "devDependencies": { diff --git a/packages/kbn-dev-utils/index.d.ts b/packages/kbn-dev-utils/index.d.ts index 9d8fd8889b7e5..dceadb9496f3b 100644 --- a/packages/kbn-dev-utils/index.d.ts +++ b/packages/kbn-dev-utils/index.d.ts @@ -18,3 +18,4 @@ */ export * from './src/tooling_log'; +export * from './src/serializers'; diff --git a/packages/kbn-dev-utils/package.json b/packages/kbn-dev-utils/package.json index 02f4b90512689..deb3c120df373 100644 --- a/packages/kbn-dev-utils/package.json +++ b/packages/kbn-dev-utils/package.json @@ -7,7 +7,8 @@ "private": true, "scripts": { "build": "babel src --out-dir target", - "kbn:bootstrap": "yarn build" + "kbn:bootstrap": "yarn build --quiet", + "kbn:watch": "yarn build --watch" }, "dependencies": { "chalk": "^2.4.1", diff --git a/packages/kbn-dev-utils/src/index.js b/packages/kbn-dev-utils/src/index.js index 4e2d1b62a92f7..82492e568f4b5 100644 --- a/packages/kbn-dev-utils/src/index.js +++ b/packages/kbn-dev-utils/src/index.js @@ -19,3 +19,4 @@ export { withProcRunner } from './proc_runner'; export { ToolingLog, ToolingLogTextWriter, pickLevelFromFlags } from './tooling_log'; +export { createAbsolutePathSerializer } from './serializers'; diff --git a/packages/kbn-dev-utils/src/serializers/absolute_path_serializer.d.ts b/packages/kbn-dev-utils/src/serializers/absolute_path_serializer.d.ts new file mode 100644 index 0000000000000..66e94571a2300 --- /dev/null +++ b/packages/kbn-dev-utils/src/serializers/absolute_path_serializer.d.ts @@ -0,0 +1,22 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export function createAbsolutePathSerializer( + rootPath: string +): { print(...args: any[]): string; test(value: any): boolean }; diff --git a/src/ui/public/filter_manager/lib/index.js b/packages/kbn-dev-utils/src/serializers/absolute_path_serializer.js similarity index 77% rename from src/ui/public/filter_manager/lib/index.js rename to packages/kbn-dev-utils/src/serializers/absolute_path_serializer.js index 61c07de0de2f9..d944772048eb9 100644 --- a/src/ui/public/filter_manager/lib/index.js +++ b/packages/kbn-dev-utils/src/serializers/absolute_path_serializer.js @@ -17,8 +17,9 @@ * under the License. */ -export { buildExistsFilter } from './exists'; -export { buildPhraseFilter } from './phrase'; -export { buildPhrasesFilter } from './phrases'; -export { buildQueryFilter } from './query'; -export { buildRangeFilter } from './range'; +export function createAbsolutePathSerializer(rootPath) { + return { + print: value => value.replace(rootPath, '').replace(/\\/g, '/'), + test: value => typeof value === 'string' && value.startsWith(rootPath), + }; +} diff --git a/packages/kbn-dev-utils/src/serializers/index.d.ts b/packages/kbn-dev-utils/src/serializers/index.d.ts new file mode 100644 index 0000000000000..3b49e243058df --- /dev/null +++ b/packages/kbn-dev-utils/src/serializers/index.d.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { createAbsolutePathSerializer } from './absolute_path_serializer'; diff --git a/packages/kbn-dev-utils/src/serializers/index.js b/packages/kbn-dev-utils/src/serializers/index.js new file mode 100644 index 0000000000000..3b49e243058df --- /dev/null +++ b/packages/kbn-dev-utils/src/serializers/index.js @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { createAbsolutePathSerializer } from './absolute_path_serializer'; diff --git a/packages/kbn-es-query/.babelrc b/packages/kbn-es-query/.babelrc new file mode 100644 index 0000000000000..dc6a77bbe0bcd --- /dev/null +++ b/packages/kbn-es-query/.babelrc @@ -0,0 +1,3 @@ +{ + "presets": ["@kbn/babel-preset/webpack_preset"] +} diff --git a/src/ui/public/kuery/index.d.ts b/packages/kbn-es-query/index.d.ts similarity index 96% rename from src/ui/public/kuery/index.d.ts rename to packages/kbn-es-query/index.d.ts index 982b64e7c9c51..9bbd0a193dfed 100644 --- a/src/ui/public/kuery/index.d.ts +++ b/packages/kbn-es-query/index.d.ts @@ -17,4 +17,4 @@ * under the License. */ -export * from 'ui/kuery/ast'; +export * from './src'; diff --git a/packages/kbn-es-query/package.json b/packages/kbn-es-query/package.json new file mode 100644 index 0000000000000..ce3e96cc8219c --- /dev/null +++ b/packages/kbn-es-query/package.json @@ -0,0 +1,20 @@ +{ + "name": "@kbn/es-query", + "main": "target/index.js", + "version": "1.0.0", + "license": "Apache-2.0", + "private": true, + "scripts": { + "build": "babel src --out-dir target", + "kbn:bootstrap": "yarn build --quiet", + "kbn:watch": "yarn build --watch" + }, + "dependencies": { + "lodash": "npm:@elastic/lodash@3.10.1-kibana1" + }, + "devDependencies": { + "@kbn/babel-preset": "1.0.0", + "babel-cli": "^6.26.0", + "expect.js": "0.3.1" + } +} diff --git a/packages/kbn-es-query/src/__fixtures__/filter_skeleton.json b/packages/kbn-es-query/src/__fixtures__/filter_skeleton.json new file mode 100644 index 0000000000000..1799d04a0fbd8 --- /dev/null +++ b/packages/kbn-es-query/src/__fixtures__/filter_skeleton.json @@ -0,0 +1,5 @@ +{ + "meta": { + "index": "logstash-*" + } +} diff --git a/src/ui/public/kuery/__tests__/index_pattern_response.json b/packages/kbn-es-query/src/__fixtures__/index_pattern_response.json similarity index 99% rename from src/ui/public/kuery/__tests__/index_pattern_response.json rename to packages/kbn-es-query/src/__fixtures__/index_pattern_response.json index a332ad44986a7..555fc4d75c430 100644 --- a/src/ui/public/kuery/__tests__/index_pattern_response.json +++ b/packages/kbn-es-query/src/__fixtures__/index_pattern_response.json @@ -1,4 +1,5 @@ { + "id": "logstash-*", "title": "logstash-*", "fields": [ { diff --git a/src/ui/public/courier/search_source/__tests__/_migrate_filter.js b/packages/kbn-es-query/src/es_query/__tests__/_migrate_filter.js similarity index 100% rename from src/ui/public/courier/search_source/__tests__/_migrate_filter.js rename to packages/kbn-es-query/src/es_query/__tests__/_migrate_filter.js diff --git a/src/ui/public/courier/search_source/build_query/__tests__/build_es_query.js b/packages/kbn-es-query/src/es_query/__tests__/build_es_query.js similarity index 76% rename from src/ui/public/courier/search_source/build_query/__tests__/build_es_query.js rename to packages/kbn-es-query/src/es_query/__tests__/build_es_query.js index 10eaad54d2f7c..3510ef276fd80 100644 --- a/src/ui/public/courier/search_source/build_query/__tests__/build_es_query.js +++ b/packages/kbn-es-query/src/es_query/__tests__/build_es_query.js @@ -17,26 +17,23 @@ * under the License. */ +import expect from 'expect.js'; import { BuildESQueryProvider } from '../build_es_query'; -import StubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import ngMock from 'ng_mock'; -import { expectDeepEqual } from '../../../../../../test_utils/expect_deep_equal.js'; -import { fromKueryExpression, toElasticsearchQuery } from '../../../../kuery'; +import indexPattern from '../../__fixtures__/index_pattern_response.json'; +import { fromKueryExpression, toElasticsearchQuery } from '../../kuery'; import { luceneStringToDsl } from '../lucene_string_to_dsl'; -import { decorateQuery } from '../../decorate_query'; +import { decorateQuery } from '../decorate_query'; -let indexPattern; let buildEsQuery; -describe('build query', function () { +const configStub = { get: () => ({}) }; +const privateStub = fn => fn(configStub); +describe('build query', function () { describe('buildESQuery', function () { - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - indexPattern = Private(StubbedLogstashIndexPatternProvider); - buildEsQuery = Private(BuildESQueryProvider); - })); + beforeEach(() => { + buildEsQuery = privateStub(BuildESQueryProvider); + }); it('should return the parameters of an Elasticsearch bool query', function () { const result = buildEsQuery(); @@ -48,7 +45,7 @@ describe('build query', function () { must_not: [], } }; - expectDeepEqual(result, expected); + expect(result).to.eql(expected); }); it('should combine queries and filters from multiple query languages into a single ES bool query', function () { @@ -66,7 +63,7 @@ describe('build query', function () { const expectedResult = { bool: { must: [ - decorateQuery(luceneStringToDsl('bar:baz')), + decorateQuery(luceneStringToDsl('bar:baz'), configStub), { match_all: {} }, ], filter: [ @@ -79,7 +76,7 @@ describe('build query', function () { const result = buildEsQuery(indexPattern, queries, filters); - expectDeepEqual(result, expectedResult); + expect(result).to.eql(expectedResult); }); }); diff --git a/src/ui/public/courier/search_source/__tests__/decorate_query.js b/packages/kbn-es-query/src/es_query/__tests__/decorate_query.js similarity index 88% rename from src/ui/public/courier/search_source/__tests__/decorate_query.js rename to packages/kbn-es-query/src/es_query/__tests__/decorate_query.js index 7a2f55c81af99..ee6223ba98c20 100644 --- a/src/ui/public/courier/search_source/__tests__/decorate_query.js +++ b/packages/kbn-es-query/src/es_query/__tests__/decorate_query.js @@ -18,19 +18,19 @@ */ import expect from 'expect.js'; -import chrome from '../../../chrome'; import { decorateQuery } from '../decorate_query'; -const config = chrome.getUiSettingsClient(); -describe('Query decorator', function () { +const configStub = { + get: () => ({ analyze_wildcard: true }) +}; +describe('Query decorator', function () { it('should be a function', function () { expect(decorateQuery).to.be.a(Function); }); it('should merge in the query string options', function () { - config.set('query:queryString:options', { analyze_wildcard: true }); - const decoratedQuery = decorateQuery({ query_string: { query: '*' } }); + const decoratedQuery = decorateQuery({ query_string: { query: '*' } }, configStub); expect(decoratedQuery).to.eql({ query_string: { query: '*', analyze_wildcard: true } }); }); }); diff --git a/src/ui/public/courier/search_source/build_query/__tests__/from_filters.js b/packages/kbn-es-query/src/es_query/__tests__/from_filters.js similarity index 87% rename from src/ui/public/courier/search_source/build_query/__tests__/from_filters.js rename to packages/kbn-es-query/src/es_query/__tests__/from_filters.js index b8d766b6fa960..8d1b103c8f66c 100644 --- a/src/ui/public/courier/search_source/build_query/__tests__/from_filters.js +++ b/packages/kbn-es-query/src/es_query/__tests__/from_filters.js @@ -17,14 +17,11 @@ * under the License. */ +import expect from 'expect.js'; import { buildQueryFromFilters } from '../from_filters'; -import ngMock from 'ng_mock'; -import { expectDeepEqual } from '../../../../../../test_utils/expect_deep_equal.js'; describe('build query', function () { describe('buildQueryFromFilters', function () { - beforeEach(ngMock.module('kibana')); - it('should return the parameters of an Elasticsearch bool query', function () { const result = buildQueryFromFilters([]); const expected = { @@ -33,7 +30,7 @@ describe('build query', function () { should: [], must_not: [], }; - expectDeepEqual(result, expected); + expect(result).to.eql(expected); }); it('should transform an array of kibana filters into ES queries combined in the bool clauses', function () { @@ -55,7 +52,7 @@ describe('build query', function () { const result = buildQueryFromFilters(filters); - expectDeepEqual(result.must, expectedESQueries); + expect(result.must).to.eql(expectedESQueries); }); it('should place negated filters in the must_not clause', function () { @@ -70,7 +67,7 @@ describe('build query', function () { const result = buildQueryFromFilters(filters); - expectDeepEqual(result.must_not, expectedESQueries); + expect(result.must_not).to.eql(expectedESQueries); }); it('should translate old ES filter syntax into ES 5+ query objects', function () { @@ -89,7 +86,7 @@ describe('build query', function () { const result = buildQueryFromFilters(filters); - expectDeepEqual(result.must, expectedESQueries); + expect(result.must).to.eql(expectedESQueries); }); it('should migrate deprecated match syntax', function () { @@ -108,7 +105,7 @@ describe('build query', function () { const result = buildQueryFromFilters(filters); - expectDeepEqual(result.must, expectedESQueries); + expect(result.must).to.eql(expectedESQueries); }); it('should not add query:queryString:options to query_string filters', function () { @@ -122,7 +119,7 @@ describe('build query', function () { const result = buildQueryFromFilters(filters); - expectDeepEqual(result.must, expectedESQueries); + expect(result.must).to.eql(expectedESQueries); }); }); }); diff --git a/src/ui/public/courier/search_source/build_query/__tests__/from_kuery.js b/packages/kbn-es-query/src/es_query/__tests__/from_kuery.js similarity index 81% rename from src/ui/public/courier/search_source/build_query/__tests__/from_kuery.js rename to packages/kbn-es-query/src/es_query/__tests__/from_kuery.js index d574af976ceea..07010442ae734 100644 --- a/src/ui/public/courier/search_source/build_query/__tests__/from_kuery.js +++ b/packages/kbn-es-query/src/es_query/__tests__/from_kuery.js @@ -18,24 +18,14 @@ */ import { buildQueryFromKuery } from '../from_kuery'; -import StubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; -import ngMock from 'ng_mock'; -import { expectDeepEqual } from '../../../../../../test_utils/expect_deep_equal.js'; +import indexPattern from '../../__fixtures__/index_pattern_response.json'; import expect from 'expect.js'; -import { fromKueryExpression, toElasticsearchQuery } from '../../../../kuery'; - -let indexPattern; +import { fromKueryExpression, toElasticsearchQuery } from '../../kuery'; describe('build query', function () { const configStub = { get: () => true }; describe('buildQueryFromKuery', function () { - - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - indexPattern = Private(StubbedLogstashIndexPatternProvider); - })); - it('should return the parameters of an Elasticsearch bool query', function () { const result = buildQueryFromKuery(null, [], configStub); const expected = { @@ -44,7 +34,7 @@ describe('build query', function () { should: [], must_not: [], }; - expectDeepEqual(result, expected); + expect(result).to.eql(expected); }); it('should transform an array of kuery queries into ES queries combined in the bool\'s filter clause', function () { @@ -61,7 +51,7 @@ describe('build query', function () { const result = buildQueryFromKuery(indexPattern, queries, configStub); - expectDeepEqual(result.filter, expectedESQueries); + expect(result.filter).to.eql(expectedESQueries); }); it('should throw a useful error if it looks like query is using an old, unsupported syntax', function () { diff --git a/src/ui/public/courier/search_source/build_query/__tests__/from_lucene.js b/packages/kbn-es-query/src/es_query/__tests__/from_lucene.js similarity index 78% rename from src/ui/public/courier/search_source/build_query/__tests__/from_lucene.js rename to packages/kbn-es-query/src/es_query/__tests__/from_lucene.js index 111fcb9ba937f..9c82eb026467c 100644 --- a/src/ui/public/courier/search_source/build_query/__tests__/from_lucene.js +++ b/packages/kbn-es-query/src/es_query/__tests__/from_lucene.js @@ -17,11 +17,12 @@ * under the License. */ +import expect from 'expect.js'; import { buildQueryFromLucene } from '../from_lucene'; -import { decorateQuery } from '../../decorate_query'; -import { expectDeepEqual } from '../../../../../../test_utils/expect_deep_equal'; +import { decorateQuery } from '../decorate_query'; import { luceneStringToDsl } from '../lucene_string_to_dsl'; +const configStub = { get: () => ({}) }; describe('build query', function () { @@ -35,7 +36,7 @@ describe('build query', function () { should: [], must_not: [], }; - expectDeepEqual(result, expected); + expect(result).to.eql(expected); }); it('should transform an array of lucene queries into ES queries combined in the bool\'s must clause', function () { @@ -46,13 +47,13 @@ describe('build query', function () { const expectedESQueries = queries.map( (query) => { - return decorateQuery(luceneStringToDsl(query.query)); + return decorateQuery(luceneStringToDsl(query.query), configStub); } ); - const result = buildQueryFromLucene(queries, decorateQuery); + const result = buildQueryFromLucene(queries, configStub); - expectDeepEqual(result.must, expectedESQueries); + expect(result.must).to.eql(expectedESQueries); }); it('should also accept queries in ES query DSL format, simply passing them through', function () { @@ -60,9 +61,9 @@ describe('build query', function () { { query: { match_all: {} }, language: 'lucene' }, ]; - const result = buildQueryFromLucene(queries, decorateQuery); + const result = buildQueryFromLucene(queries, configStub); - expectDeepEqual(result.must, [queries[0].query]); + expect(result.must).to.eql([queries[0].query]); }); }); diff --git a/src/ui/public/courier/search_source/build_query/__tests__/lucene_string_to_dsl.js b/packages/kbn-es-query/src/es_query/__tests__/lucene_string_to_dsl.js similarity index 83% rename from src/ui/public/courier/search_source/build_query/__tests__/lucene_string_to_dsl.js rename to packages/kbn-es-query/src/es_query/__tests__/lucene_string_to_dsl.js index 20467b99b51e8..67908f28b7330 100644 --- a/src/ui/public/courier/search_source/build_query/__tests__/lucene_string_to_dsl.js +++ b/packages/kbn-es-query/src/es_query/__tests__/lucene_string_to_dsl.js @@ -18,7 +18,6 @@ */ import { luceneStringToDsl } from '../lucene_string_to_dsl'; -import { expectDeepEqual } from '../../../../../../test_utils/expect_deep_equal.js'; import expect from 'expect.js'; describe('build query', function () { @@ -30,7 +29,7 @@ describe('build query', function () { const expectedResult = { query_string: { query: 'foo:bar' } }; - expectDeepEqual(result, expectedResult); + expect(result).to.eql(expectedResult); }); it('should return a match_all query for empty strings and whitespace', function () { @@ -38,15 +37,15 @@ describe('build query', function () { match_all: {} }; - expectDeepEqual(luceneStringToDsl(''), expectedResult); - expectDeepEqual(luceneStringToDsl(' '), expectedResult); + expect(luceneStringToDsl('')).to.eql(expectedResult); + expect(luceneStringToDsl(' ')).to.eql(expectedResult); }); it('should return non-string arguments without modification', function () { const expectedResult = {}; const result = luceneStringToDsl(expectedResult); expect(result).to.be(expectedResult); - expectDeepEqual(result, expectedResult); + expect(result).to.eql(expectedResult); }); }); diff --git a/src/ui/public/courier/search_source/build_query/build_es_query.js b/packages/kbn-es-query/src/es_query/build_es_query.js similarity index 96% rename from src/ui/public/courier/search_source/build_query/build_es_query.js rename to packages/kbn-es-query/src/es_query/build_es_query.js index cda28e8e45b5d..309881e02c11e 100644 --- a/src/ui/public/courier/search_source/build_query/build_es_query.js +++ b/packages/kbn-es-query/src/es_query/build_es_query.js @@ -18,7 +18,6 @@ */ import { groupBy, has } from 'lodash'; -import { decorateQuery } from '../decorate_query'; import { buildQueryFromKuery } from './from_kuery'; import { buildQueryFromFilters } from './from_filters'; import { buildQueryFromLucene } from './from_lucene'; @@ -35,7 +34,7 @@ export function BuildESQueryProvider(config) { const queriesByLanguage = groupBy(validQueries, 'language'); const kueryQuery = buildQueryFromKuery(indexPattern, queriesByLanguage.kuery, config); - const luceneQuery = buildQueryFromLucene(queriesByLanguage.lucene, decorateQuery); + const luceneQuery = buildQueryFromLucene(queriesByLanguage.lucene, config); const filterQuery = buildQueryFromFilters(filters, indexPattern); return { diff --git a/src/ui/public/courier/search_source/decorate_query.js b/packages/kbn-es-query/src/es_query/decorate_query.js similarity index 90% rename from src/ui/public/courier/search_source/decorate_query.js rename to packages/kbn-es-query/src/es_query/decorate_query.js index be80394aa646d..7116f4d89a247 100644 --- a/src/ui/public/courier/search_source/decorate_query.js +++ b/packages/kbn-es-query/src/es_query/decorate_query.js @@ -18,16 +18,13 @@ */ import _ from 'lodash'; -import chrome from '../../chrome'; - -const config = chrome.getUiSettingsClient(); /** * Decorate queries with default parameters * @param {query} query object * @returns {object} */ -export function decorateQuery(query) { +export function decorateQuery(query, config) { const queryOptions = config.get('query:queryString:options'); if (_.has(query, 'query_string.query')) { diff --git a/src/ui/public/courier/search_source/build_query/from_filters.js b/packages/kbn-es-query/src/es_query/from_filters.js similarity index 97% rename from src/ui/public/courier/search_source/build_query/from_filters.js rename to packages/kbn-es-query/src/es_query/from_filters.js index 320a4ae480d6d..738c5008c2f98 100644 --- a/src/ui/public/courier/search_source/build_query/from_filters.js +++ b/packages/kbn-es-query/src/es_query/from_filters.js @@ -18,7 +18,7 @@ */ import _ from 'lodash'; -import { migrateFilter } from '../migrate_filter'; +import { migrateFilter } from './migrate_filter'; /** * Create a filter that can be reversed for filters with negate set diff --git a/src/ui/public/courier/search_source/build_query/from_kuery.js b/packages/kbn-es-query/src/es_query/from_kuery.js similarity index 72% rename from src/ui/public/courier/search_source/build_query/from_kuery.js rename to packages/kbn-es-query/src/es_query/from_kuery.js index d79be5f1f5a88..8e5c5e564ac4f 100644 --- a/src/ui/public/courier/search_source/build_query/from_kuery.js +++ b/packages/kbn-es-query/src/es_query/from_kuery.js @@ -17,33 +17,20 @@ * under the License. */ -import { fromLegacyKueryExpression, fromKueryExpression, toElasticsearchQuery, nodeTypes } from '../../../kuery'; -import { documentationLinks } from '../../../documentation_links'; -import { NoLeadingWildcardsError } from '../../../kuery/errors'; - -const queryDocs = documentationLinks.query; +import { fromLegacyKueryExpression, fromKueryExpression, toElasticsearchQuery, nodeTypes } from '../kuery'; export function buildQueryFromKuery(indexPattern, queries = [], config) { const allowLeadingWildcards = config.get('query:allowLeadingWildcards'); - - const queryASTs = queries.map((query) => { + const queryASTs = queries.map(query => { try { return fromKueryExpression(query.query, { allowLeadingWildcards }); - } - catch (parseError) { - if (parseError instanceof NoLeadingWildcardsError) { - throw parseError; - } - + } catch (parseError) { try { fromLegacyKueryExpression(query.query); - } - catch (legacyParseError) { + } catch (legacyParseError) { throw parseError; } - throw new Error( - `It looks like you're using an outdated Kuery syntax. See what changed in the [docs](${queryDocs.kueryQuerySyntax})!` - ); + throw Error('OutdatedKuerySyntaxError'); } }); return buildQuery(indexPattern, queryASTs); diff --git a/src/ui/public/courier/search_source/build_query/from_lucene.js b/packages/kbn-es-query/src/es_query/from_lucene.js similarity index 88% rename from src/ui/public/courier/search_source/build_query/from_lucene.js rename to packages/kbn-es-query/src/es_query/from_lucene.js index 09bef032ef282..618abde99f47e 100644 --- a/src/ui/public/courier/search_source/build_query/from_lucene.js +++ b/packages/kbn-es-query/src/es_query/from_lucene.js @@ -18,12 +18,13 @@ */ import _ from 'lodash'; +import { decorateQuery } from './decorate_query'; import { luceneStringToDsl } from './lucene_string_to_dsl'; -export function buildQueryFromLucene(queries, decorateQuery) { +export function buildQueryFromLucene(queries, config) { const combinedQueries = _.map(queries, (query) => { const queryDsl = luceneStringToDsl(query.query); - return decorateQuery(queryDsl); + return decorateQuery(queryDsl, config); }); return { diff --git a/src/ui/public/courier/search_source/build_query/index.js b/packages/kbn-es-query/src/es_query/index.js similarity index 90% rename from src/ui/public/courier/search_source/build_query/index.js rename to packages/kbn-es-query/src/es_query/index.js index 3e71a33f733a9..af6456a32d820 100644 --- a/src/ui/public/courier/search_source/build_query/index.js +++ b/packages/kbn-es-query/src/es_query/index.js @@ -20,3 +20,5 @@ export { BuildESQueryProvider } from './build_es_query'; export { buildQueryFromFilters } from './from_filters'; export { luceneStringToDsl } from './lucene_string_to_dsl'; +export { migrateFilter } from './migrate_filter'; +export { decorateQuery } from './decorate_query'; diff --git a/src/ui/public/courier/search_source/build_query/lucene_string_to_dsl.js b/packages/kbn-es-query/src/es_query/lucene_string_to_dsl.js similarity index 100% rename from src/ui/public/courier/search_source/build_query/lucene_string_to_dsl.js rename to packages/kbn-es-query/src/es_query/lucene_string_to_dsl.js diff --git a/src/ui/public/courier/search_source/migrate_filter.js b/packages/kbn-es-query/src/es_query/migrate_filter.js similarity index 95% rename from src/ui/public/courier/search_source/migrate_filter.js rename to packages/kbn-es-query/src/es_query/migrate_filter.js index 63ad3270a8b2a..d5f52648b027e 100644 --- a/src/ui/public/courier/search_source/migrate_filter.js +++ b/packages/kbn-es-query/src/es_query/migrate_filter.js @@ -18,7 +18,7 @@ */ import _ from 'lodash'; -import { getConvertedValueForField } from '../../filter_manager/lib/phrase'; +import { getConvertedValueForField } from '../filters'; export function migrateFilter(filter, indexPattern) { if (filter.match) { diff --git a/src/ui/public/filter_manager/lib/__tests__/phrase.js b/packages/kbn-es-query/src/filters/__tests__/phrase.js similarity index 77% rename from src/ui/public/filter_manager/lib/__tests__/phrase.js rename to packages/kbn-es-query/src/filters/__tests__/phrase.js index e6ba3f7342043..64d0bcc3ce275 100644 --- a/src/ui/public/filter_manager/lib/__tests__/phrase.js +++ b/packages/kbn-es-query/src/filters/__tests__/phrase.js @@ -21,25 +21,23 @@ import { buildInlineScriptForPhraseFilter, buildPhraseFilter } from '../phrase'; import expect from 'expect.js'; import _ from 'lodash'; -import ngMock from 'ng_mock'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; +import indexPattern from '../../__fixtures__/index_pattern_response.json'; +import filterSkeleton from '../../__fixtures__/filter_skeleton'; -let indexPattern; let expected; describe('Filter Manager', function () { describe('Phrase filter builder', function () { - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - expected = _.cloneDeep(require('fixtures/filter_skeleton')); - })); + beforeEach(() => { + expected = _.cloneDeep(filterSkeleton); + }); it('should be a function', function () { expect(buildPhraseFilter).to.be.a(Function); }); it('should return a match query filter when passed a standard field', function () { + const field = getField(indexPattern, 'bytes'); expected.query = { match: { bytes: { @@ -48,19 +46,20 @@ describe('Filter Manager', function () { } } }; - expect(buildPhraseFilter(indexPattern.fields.byName.bytes, 5, indexPattern)).to.eql(expected); + expect(buildPhraseFilter(field, 5, indexPattern)).to.eql(expected); }); it('should return a script filter when passed a scripted field', function () { + const field = getField(indexPattern, 'script number'); expected.meta.field = 'script number'; _.set(expected, 'script.script', { - inline: '(' + indexPattern.fields.byName['script number'].script + ') == value', + inline: '(' + field.script + ') == value', lang: 'expression', params: { value: 5, } }); - expect(buildPhraseFilter(indexPattern.fields.byName['script number'], 5, indexPattern)).to.eql(expected); + expect(buildPhraseFilter(field, 5, indexPattern)).to.eql(expected); }); }); @@ -90,3 +89,7 @@ describe('Filter Manager', function () { }); }); }); + +function getField(indexPattern, name) { + return indexPattern.fields.find(field => field.name === name); +} diff --git a/src/ui/public/filter_manager/lib/__tests__/query.js b/packages/kbn-es-query/src/filters/__tests__/query.js similarity index 76% rename from src/ui/public/filter_manager/lib/__tests__/query.js rename to packages/kbn-es-query/src/filters/__tests__/query.js index 8210dbe96892c..62f7a898140c1 100644 --- a/src/ui/public/filter_manager/lib/__tests__/query.js +++ b/packages/kbn-es-query/src/filters/__tests__/query.js @@ -18,21 +18,18 @@ */ import { buildQueryFilter } from '../query'; +import { cloneDeep } from 'lodash'; import expect from 'expect.js'; -import _ from 'lodash'; -import ngMock from 'ng_mock'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; +import indexPattern from '../../__fixtures__/index_pattern_response.json'; +import filterSkeleton from '../../__fixtures__/filter_skeleton'; -let indexPattern; let expected; describe('Filter Manager', function () { describe('Phrase filter builder', function () { - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - expected = _.cloneDeep(require('fixtures/filter_skeleton')); - })); + beforeEach(() => { + expected = cloneDeep(filterSkeleton); + }); it('should be a function', function () { expect(buildQueryFilter).to.be.a(Function); diff --git a/src/ui/public/filter_manager/lib/__tests__/range.js b/packages/kbn-es-query/src/filters/__tests__/range.js similarity index 68% rename from src/ui/public/filter_manager/lib/__tests__/range.js rename to packages/kbn-es-query/src/filters/__tests__/range.js index 10f894f02223c..9b022bfe4617a 100644 --- a/src/ui/public/filter_manager/lib/__tests__/range.js +++ b/packages/kbn-es-query/src/filters/__tests__/range.js @@ -20,78 +20,77 @@ import { buildRangeFilter } from '../range'; import expect from 'expect.js'; import _ from 'lodash'; -import ngMock from 'ng_mock'; -import FixturesStubbedLogstashIndexPatternProvider from 'fixtures/stubbed_logstash_index_pattern'; +import indexPattern from '../../__fixtures__/index_pattern_response.json'; +import filterSkeleton from '../../__fixtures__/filter_skeleton'; -let indexPattern; let expected; describe('Filter Manager', function () { describe('Range filter builder', function () { - beforeEach(ngMock.module('kibana')); - beforeEach(ngMock.inject(function (Private) { - indexPattern = Private(FixturesStubbedLogstashIndexPatternProvider); - expected = _.cloneDeep(require('fixtures/filter_skeleton')); - })); + beforeEach(() => { + expected = _.cloneDeep(filterSkeleton); + }); it('should be a function', function () { expect(buildRangeFilter).to.be.a(Function); }); it('should return a range filter when passed a standard field', function () { + const field = getField(indexPattern, 'bytes'); expected.range = { bytes: { gte: 1, lte: 3 } }; - expect(buildRangeFilter(indexPattern.fields.byName.bytes, { gte: 1, lte: 3 }, indexPattern)).to.eql(expected); + expect(buildRangeFilter(field, { gte: 1, lte: 3 }, indexPattern)).to.eql(expected); }); it('should return a script filter when passed a scripted field', function () { + const field = getField(indexPattern, 'script number'); expected.meta.field = 'script number'; _.set(expected, 'script.script', { lang: 'expression', - inline: '(' + indexPattern.fields.byName['script number'].script + ')>=gte && (' + - indexPattern.fields.byName['script number'].script + ')<=lte', + inline: '(' + field.script + ')>=gte && (' + field.script + ')<=lte', params: { value: '>=1 <=3', gte: 1, lte: 3 } }); - expect(buildRangeFilter( - indexPattern.fields.byName['script number'], { gte: 1, lte: 3 }, indexPattern)).to.eql(expected); + expect(buildRangeFilter(field, { gte: 1, lte: 3 }, indexPattern)).to.eql(expected); }); it('should wrap painless scripts in comparator lambdas', function () { + const field = getField(indexPattern, 'script date'); const expected = `boolean gte(Supplier s, def v) {return s.get() >= v} ` + `boolean lte(Supplier s, def v) {return s.get() <= v}` + - `gte(() -> { ${indexPattern.fields.byName['script date'].script} }, params.gte) && ` + - `lte(() -> { ${indexPattern.fields.byName['script date'].script} }, params.lte)`; + `gte(() -> { ${field.script} }, params.gte) && ` + + `lte(() -> { ${field.script} }, params.lte)`; - const inlineScript = buildRangeFilter( - indexPattern.fields.byName['script date'], { gte: 1, lte: 3 }, indexPattern).script.script.inline; + const inlineScript = buildRangeFilter(field, { gte: 1, lte: 3 }, indexPattern).script.script.inline; expect(inlineScript).to.be(expected); }); it('should throw an error when gte and gt, or lte and lt are both passed', function () { + const field = getField(indexPattern, 'script number'); expect(function () { - buildRangeFilter(indexPattern.fields.byName['script number'], { gte: 1, gt: 3 }, indexPattern); + buildRangeFilter(field, { gte: 1, gt: 3 }, indexPattern); }).to.throwError(); expect(function () { - buildRangeFilter(indexPattern.fields.byName['script number'], { lte: 1, lt: 3 }, indexPattern); + buildRangeFilter(field, { lte: 1, lt: 3 }, indexPattern); }).to.throwError(); }); it('to use the right operator for each of gte, gt, lt and lte', function () { + const field = getField(indexPattern, 'script number'); _.each({ gte: '>=', gt: '>', lte: '<=', lt: '<' }, function (operator, key) { const params = {}; params[key] = 5; - const filter = buildRangeFilter(indexPattern.fields.byName['script number'], params, indexPattern); + const filter = buildRangeFilter(field, params, indexPattern); expect(filter.script.script.inline).to.be( - '(' + indexPattern.fields.byName['script number'].script + ')' + operator + key); + '(' + field.script + ')' + operator + key); expect(filter.script.script.params[key]).to.be(5); expect(filter.script.script.params.value).to.be(operator + 5); @@ -99,9 +98,10 @@ describe('Filter Manager', function () { }); describe('when given params where one side is infinite', function () { + const field = getField(indexPattern, 'script number'); let filter; beforeEach(function () { - filter = buildRangeFilter(indexPattern.fields.byName['script number'], { gte: 0, lt: Infinity }, indexPattern); + filter = buildRangeFilter(field, { gte: 0, lt: Infinity }, indexPattern); }); describe('returned filter', function () { @@ -118,17 +118,19 @@ describe('Filter Manager', function () { }); it('does not contain a script condition for the infinite side', function () { - const script = indexPattern.fields.byName['script number'].script; + const field = getField(indexPattern, 'script number'); + const script = field.script; expect(filter.script.script.inline).to.equal(`(${script})>=gte`); }); }); }); describe('when given params where both sides are infinite', function () { + const field = getField(indexPattern, 'script number'); let filter; beforeEach(function () { filter = buildRangeFilter( - indexPattern.fields.byName['script number'], { gte: -Infinity, lt: Infinity }, indexPattern); + field, { gte: -Infinity, lt: Infinity }, indexPattern); }); describe('returned filter', function () { @@ -148,3 +150,7 @@ describe('Filter Manager', function () { }); }); }); + +function getField(indexPattern, name) { + return indexPattern.fields.find(field => field.name === name); +} diff --git a/src/ui/public/filter_manager/lib/exists.js b/packages/kbn-es-query/src/filters/exists.js similarity index 100% rename from src/ui/public/filter_manager/lib/exists.js rename to packages/kbn-es-query/src/filters/exists.js diff --git a/packages/kbn-es-query/src/filters/index.js b/packages/kbn-es-query/src/filters/index.js new file mode 100644 index 0000000000000..dfd8924736862 --- /dev/null +++ b/packages/kbn-es-query/src/filters/index.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './exists'; +export * from './phrase'; +export * from './phrases'; +export * from './query'; +export * from './range'; diff --git a/src/ui/public/filter_manager/lib/phrase.js b/packages/kbn-es-query/src/filters/phrase.js similarity index 100% rename from src/ui/public/filter_manager/lib/phrase.js rename to packages/kbn-es-query/src/filters/phrase.js diff --git a/src/ui/public/filter_manager/lib/phrases.js b/packages/kbn-es-query/src/filters/phrases.js similarity index 89% rename from src/ui/public/filter_manager/lib/phrases.js rename to packages/kbn-es-query/src/filters/phrases.js index 9ddf5b0e8a8fa..0d85669e65fcd 100644 --- a/src/ui/public/filter_manager/lib/phrases.js +++ b/packages/kbn-es-query/src/filters/phrases.js @@ -24,7 +24,7 @@ export function buildPhrasesFilter(field, params, indexPattern) { const type = 'phrases'; const key = field.name; const value = params - .map(value => field.format.convert(value)) + .map(value => format(field, value)) .join(', '); const filter = { @@ -53,3 +53,9 @@ export function buildPhrasesFilter(field, params, indexPattern) { return filter; } + +function format(field, value) { + return field && field.format && field.format.convert + ? field.format.convert(value) + : value; +} diff --git a/src/ui/public/filter_manager/lib/query.js b/packages/kbn-es-query/src/filters/query.js similarity index 100% rename from src/ui/public/filter_manager/lib/query.js rename to packages/kbn-es-query/src/filters/query.js diff --git a/src/ui/public/filter_manager/lib/range.js b/packages/kbn-es-query/src/filters/range.js similarity index 93% rename from src/ui/public/filter_manager/lib/range.js rename to packages/kbn-es-query/src/filters/range.js index 9c356d5b9061e..ec3fc167f75d4 100644 --- a/src/ui/public/filter_manager/lib/range.js +++ b/packages/kbn-es-query/src/filters/range.js @@ -33,7 +33,7 @@ const comparators = { }; function formatValue(field, params) { - return _.map(params, (val, key) => operators[key] + field.format.convert(val)).join(' '); + return _.map(params, (val, key) => operators[key] + format(field, val)).join(' '); } export function buildRangeFilter(field, params, indexPattern, formattedValue) { @@ -102,3 +102,10 @@ export function getRangeScript(field, params) { } }; } + +function format(field, value) { + return field && field.format && field.format.convert + ? field.format.convert(value) + : value; +} + diff --git a/src/ui/public/kuery/ast/index.d.ts b/packages/kbn-es-query/src/index.d.ts similarity index 95% rename from src/ui/public/kuery/ast/index.d.ts rename to packages/kbn-es-query/src/index.d.ts index 369ad1239d258..79e6903b18644 100644 --- a/src/ui/public/kuery/ast/index.d.ts +++ b/packages/kbn-es-query/src/index.d.ts @@ -17,4 +17,4 @@ * under the License. */ -export * from 'ui/kuery/ast/ast'; +export * from './kuery'; diff --git a/packages/kbn-es-query/src/index.js b/packages/kbn-es-query/src/index.js new file mode 100644 index 0000000000000..086b2f6db8d0d --- /dev/null +++ b/packages/kbn-es-query/src/index.js @@ -0,0 +1,22 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './kuery'; +export * from './filters'; +export * from './es_query'; diff --git a/src/ui/public/kuery/ast/__tests__/ast.js b/packages/kbn-es-query/src/kuery/ast/__tests__/ast.js similarity index 87% rename from src/ui/public/kuery/ast/__tests__/ast.js rename to packages/kbn-es-query/src/kuery/ast/__tests__/ast.js index d90723d7bf37e..481d0405373a6 100644 --- a/src/ui/public/kuery/ast/__tests__/ast.js +++ b/packages/kbn-es-query/src/kuery/ast/__tests__/ast.js @@ -20,10 +20,7 @@ import * as ast from '../ast'; import expect from 'expect.js'; import { nodeTypes } from '../../node_types/index'; -import indexPatternResponse from '../../__tests__/index_pattern_response.json'; - -import { expectDeepEqual } from '../../../../../test_utils/expect_deep_equal.js'; - +import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json'; // Helpful utility allowing us to test the PEG parser by simply checking for deep equality between // the nodes the parser generates and the nodes our constructor functions generate. @@ -62,19 +59,19 @@ describe('kuery AST API', function () { it('should return a match all "is" function for whitespace', function () { const expected = nodeTypes.function.buildNode('is', '*', '*'); const actual = fromLegacyKueryExpressionNoMeta(' '); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should return an "and" function for single literals', function () { const expected = nodeTypes.function.buildNode('and', [nodeTypes.literal.buildNode('foo')]); const actual = fromLegacyKueryExpressionNoMeta('foo'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should ignore extraneous whitespace at the beginning and end of the query', function () { const expected = nodeTypes.function.buildNode('and', [nodeTypes.literal.buildNode('foo')]); const actual = fromLegacyKueryExpressionNoMeta(' foo '); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('literals and queries separated by whitespace should be joined by an implicit "and"', function () { @@ -83,7 +80,7 @@ describe('kuery AST API', function () { nodeTypes.literal.buildNode('bar'), ]); const actual = fromLegacyKueryExpressionNoMeta('foo bar'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should also support explicit "and"s as a binary operator', function () { @@ -92,7 +89,7 @@ describe('kuery AST API', function () { nodeTypes.literal.buildNode('bar'), ]); const actual = fromLegacyKueryExpressionNoMeta('foo and bar'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should also support "and" as a function', function () { @@ -101,7 +98,7 @@ describe('kuery AST API', function () { nodeTypes.literal.buildNode('bar'), ], 'function'); const actual = fromLegacyKueryExpressionNoMeta('and(foo, bar)'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should support "or" as a binary operator', function () { @@ -110,7 +107,7 @@ describe('kuery AST API', function () { nodeTypes.literal.buildNode('bar'), ]); const actual = fromLegacyKueryExpressionNoMeta('foo or bar'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should support "or" as a function', function () { @@ -119,7 +116,7 @@ describe('kuery AST API', function () { nodeTypes.literal.buildNode('bar'), ]); const actual = fromLegacyKueryExpressionNoMeta('or(foo, bar)'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should support negation of queries with a "!" prefix', function () { @@ -129,7 +126,7 @@ describe('kuery AST API', function () { nodeTypes.literal.buildNode('bar'), ])); const actual = fromLegacyKueryExpressionNoMeta('!or(foo, bar)'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('"and" should have a higher precedence than "or"', function () { @@ -144,7 +141,7 @@ describe('kuery AST API', function () { ]) ]); const actual = fromLegacyKueryExpressionNoMeta('foo or bar and baz or qux'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should support grouping to override default precedence', function () { @@ -156,13 +153,13 @@ describe('kuery AST API', function () { nodeTypes.literal.buildNode('baz'), ]); const actual = fromLegacyKueryExpressionNoMeta('(foo or bar) and baz'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should support a shorthand operator syntax for "is" functions', function () { const expected = nodeTypes.function.buildNode('is', 'foo', 'bar', true); const actual = fromLegacyKueryExpressionNoMeta('foo:bar'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should support a shorthand operator syntax for inclusive "range" functions', function () { @@ -173,13 +170,13 @@ describe('kuery AST API', function () { ]; const expected = nodeTypes.function.buildNodeWithArgumentNodes('range', argumentNodes); const actual = fromLegacyKueryExpressionNoMeta('bytes:[1000 to 8000]'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should support functions with named arguments', function () { const expected = nodeTypes.function.buildNode('range', 'bytes', { gt: 1000, lt: 8000 }); const actual = fromLegacyKueryExpressionNoMeta('range(bytes, gt=1000, lt=8000)'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should throw an error for unknown functions', function () { @@ -192,25 +189,25 @@ describe('kuery AST API', function () { it('should return a match all "is" function for whitespace', function () { const expected = nodeTypes.function.buildNode('is', '*', '*'); const actual = ast.fromKueryExpression(' '); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should return an "is" function with a null field for single literals', function () { const expected = nodeTypes.function.buildNode('is', null, 'foo'); const actual = ast.fromKueryExpression('foo'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should ignore extraneous whitespace at the beginning and end of the query', function () { const expected = nodeTypes.function.buildNode('is', null, 'foo'); const actual = ast.fromKueryExpression(' foo '); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should not split on whitespace', function () { const expected = nodeTypes.function.buildNode('is', null, 'foo bar'); const actual = ast.fromKueryExpression('foo bar'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should support "and" as a binary operator', function () { @@ -219,7 +216,7 @@ describe('kuery AST API', function () { nodeTypes.function.buildNode('is', null, 'bar'), ]); const actual = ast.fromKueryExpression('foo and bar'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should support "or" as a binary operator', function () { @@ -228,7 +225,7 @@ describe('kuery AST API', function () { nodeTypes.function.buildNode('is', null, 'bar'), ]); const actual = ast.fromKueryExpression('foo or bar'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should support negation of queries with a "not" prefix', function () { @@ -239,7 +236,7 @@ describe('kuery AST API', function () { ]) ); const actual = ast.fromKueryExpression('not (foo or bar)'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('"and" should have a higher precedence than "or"', function () { @@ -254,7 +251,7 @@ describe('kuery AST API', function () { ]) ]); const actual = ast.fromKueryExpression('foo or bar and baz or qux'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should support grouping to override default precedence', function () { @@ -266,25 +263,25 @@ describe('kuery AST API', function () { nodeTypes.function.buildNode('is', null, 'baz'), ]); const actual = ast.fromKueryExpression('(foo or bar) and baz'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should support matching against specific fields', function () { const expected = nodeTypes.function.buildNode('is', 'foo', 'bar'); const actual = ast.fromKueryExpression('foo:bar'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should also not split on whitespace when matching specific fields', function () { const expected = nodeTypes.function.buildNode('is', 'foo', 'bar baz'); const actual = ast.fromKueryExpression('foo:bar baz'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should treat quoted values as phrases', function () { const expected = nodeTypes.function.buildNode('is', 'foo', 'bar baz', true); const actual = ast.fromKueryExpression('foo:"bar baz"'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should support a shorthand for matching multiple values against a single field', function () { @@ -293,7 +290,7 @@ describe('kuery AST API', function () { nodeTypes.function.buildNode('is', 'foo', 'baz'), ]); const actual = ast.fromKueryExpression('foo:(bar or baz)'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should support "and" and "not" operators and grouping in the shorthand as well', function () { @@ -307,7 +304,7 @@ describe('kuery AST API', function () { ), ]); const actual = ast.fromKueryExpression('foo:((bar or baz) and not qux)'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should support exclusive range operators', function () { @@ -320,7 +317,7 @@ describe('kuery AST API', function () { }), ]); const actual = ast.fromKueryExpression('bytes > 1000 and bytes < 8000'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should support inclusive range operators', function () { @@ -333,25 +330,25 @@ describe('kuery AST API', function () { }), ]); const actual = ast.fromKueryExpression('bytes >= 1000 and bytes <= 8000'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should support wildcards in field names', function () { const expected = nodeTypes.function.buildNode('is', 'machine*', 'osx'); const actual = ast.fromKueryExpression('machine*:osx'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should support wildcards in values', function () { const expected = nodeTypes.function.buildNode('is', 'foo', 'ba*'); const actual = ast.fromKueryExpression('foo:ba*'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should create an exists "is" query when a field is given and "*" is the value', function () { const expected = nodeTypes.function.buildNode('is', 'foo', '*'); const actual = ast.fromKueryExpression('foo:*'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); }); @@ -364,35 +361,35 @@ describe('kuery AST API', function () { const booleanTrueLiteral = nodeTypes.literal.buildNode(true); const numberLiteral = nodeTypes.literal.buildNode(42); - expectDeepEqual(ast.fromLiteralExpression('foo'), stringLiteral); - expectDeepEqual(ast.fromLiteralExpression('true'), booleanTrueLiteral); - expectDeepEqual(ast.fromLiteralExpression('false'), booleanFalseLiteral); - expectDeepEqual(ast.fromLiteralExpression('42'), numberLiteral); + expect(ast.fromLiteralExpression('foo')).to.eql(stringLiteral); + expect(ast.fromLiteralExpression('true')).to.eql(booleanTrueLiteral); + expect(ast.fromLiteralExpression('false')).to.eql(booleanFalseLiteral); + expect(ast.fromLiteralExpression('42')).to.eql(numberLiteral); }); it('should allow escaping of special characters with a backslash', function () { const expected = nodeTypes.literal.buildNode('\\():<>"*'); // yo dawg const actual = ast.fromLiteralExpression('\\\\\\(\\)\\:\\<\\>\\"\\*'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should support double quoted strings that do not need escapes except for quotes', function () { const expected = nodeTypes.literal.buildNode('\\():<>"*'); const actual = ast.fromLiteralExpression('"\\():<>\\"*"'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should support escaped backslashes inside quoted strings', function () { const expected = nodeTypes.literal.buildNode('\\'); const actual = ast.fromLiteralExpression('"\\\\"'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should detect wildcards and build wildcard AST nodes', function () { const expected = nodeTypes.wildcard.buildNode('foo*bar'); const actual = ast.fromLiteralExpression('foo*bar'); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); }); @@ -402,21 +399,21 @@ describe('kuery AST API', function () { const node = nodeTypes.function.buildNode('exists', 'response'); const expected = nodeTypes.function.toElasticsearchQuery(node, indexPattern); const result = ast.toElasticsearchQuery(node, indexPattern); - expectDeepEqual(result, expected); + expect(result).to.eql(expected); }); it('should return an empty "and" function for undefined nodes and unknown node types', function () { const expected = nodeTypes.function.toElasticsearchQuery(nodeTypes.function.buildNode('and', [])); - expectDeepEqual(ast.toElasticsearchQuery(), expected); + expect(ast.toElasticsearchQuery()).to.eql(expected); const noTypeNode = nodeTypes.function.buildNode('exists', 'foo'); delete noTypeNode.type; - expectDeepEqual(ast.toElasticsearchQuery(noTypeNode), expected); + expect(ast.toElasticsearchQuery(noTypeNode)).to.eql(expected); const unknownTypeNode = nodeTypes.function.buildNode('exists', 'foo'); unknownTypeNode.type = 'notValid'; - expectDeepEqual(ast.toElasticsearchQuery(unknownTypeNode), expected); + expect(ast.toElasticsearchQuery(unknownTypeNode)).to.eql(expected); }); }); diff --git a/src/ui/public/kuery/ast/ast.d.ts b/packages/kbn-es-query/src/kuery/ast/ast.d.ts similarity index 100% rename from src/ui/public/kuery/ast/ast.d.ts rename to packages/kbn-es-query/src/kuery/ast/ast.d.ts diff --git a/src/ui/public/kuery/ast/ast.js b/packages/kbn-es-query/src/kuery/ast/ast.js similarity index 96% rename from src/ui/public/kuery/ast/ast.js rename to packages/kbn-es-query/src/kuery/ast/ast.js index 551ea50ed4d45..41fa5bc4054bf 100644 --- a/src/ui/public/kuery/ast/ast.js +++ b/packages/kbn-es-query/src/kuery/ast/ast.js @@ -19,7 +19,6 @@ import _ from 'lodash'; import { nodeTypes } from '../node_types/index'; -import * as errors from '../errors'; import { parse as parseKuery } from './kuery'; import { parse as parseLegacyKuery } from './legacy_kuery'; @@ -47,7 +46,7 @@ function fromExpression(expression, parseOptions = {}, parse = parseKuery) { parseOptions = { ...parseOptions, - helpers: { nodeTypes, errors } + helpers: { nodeTypes } }; return parse(expression, parseOptions); diff --git a/src/fixtures/filter_skeleton.js b/packages/kbn-es-query/src/kuery/ast/index.d.ts similarity index 93% rename from src/fixtures/filter_skeleton.js rename to packages/kbn-es-query/src/kuery/ast/index.d.ts index 32bc1c906e7fd..9e68d01d046cc 100644 --- a/src/fixtures/filter_skeleton.js +++ b/packages/kbn-es-query/src/kuery/ast/index.d.ts @@ -17,8 +17,4 @@ * under the License. */ -export default { - meta: { - index: 'logstash-*' - } -}; +export * from '../ast/ast'; diff --git a/src/ui/public/kuery/ast/index.js b/packages/kbn-es-query/src/kuery/ast/index.js similarity index 100% rename from src/ui/public/kuery/ast/index.js rename to packages/kbn-es-query/src/kuery/ast/index.js diff --git a/src/ui/public/kuery/ast/kuery.js b/packages/kbn-es-query/src/kuery/ast/kuery.js similarity index 99% rename from src/ui/public/kuery/ast/kuery.js rename to packages/kbn-es-query/src/kuery/ast/kuery.js index 020c08f243f3f..0d6ec801853ba 100644 --- a/src/ui/public/kuery/ast/kuery.js +++ b/packages/kbn-es-query/src/kuery/ast/kuery.js @@ -154,7 +154,7 @@ module.exports = (function() { if (value.type === 'cursor') return value; if (!allowLeadingWildcards && value.type === 'wildcard' && nodeTypes.wildcard.hasLeadingWildcard(value)) { - throw new errors.NoLeadingWildcardsError(); + error('Leading wildcards are disabled. See query:allowLeadingWildcards in Advanced Settings.'); } const isPhrase = buildLiteralNode(false); @@ -1661,7 +1661,7 @@ module.exports = (function() { } - const { parseCursor, cursorSymbol, allowLeadingWildcards = true, helpers: { nodeTypes, errors } } = options; + const { parseCursor, cursorSymbol, allowLeadingWildcards = true, helpers: { nodeTypes } } = options; const buildFunctionNode = nodeTypes.function.buildNodeWithArgumentNodes; const buildLiteralNode = nodeTypes.literal.buildNode; const buildWildcardNode = nodeTypes.wildcard.buildNode; diff --git a/src/ui/public/kuery/ast/kuery.peg b/packages/kbn-es-query/src/kuery/ast/kuery.peg similarity index 97% rename from src/ui/public/kuery/ast/kuery.peg rename to packages/kbn-es-query/src/kuery/ast/kuery.peg index 4dc48b17e5612..a47411c10e2d8 100644 --- a/src/ui/public/kuery/ast/kuery.peg +++ b/packages/kbn-es-query/src/kuery/ast/kuery.peg @@ -5,7 +5,7 @@ // Initialization block { - const { parseCursor, cursorSymbol, allowLeadingWildcards = true, helpers: { nodeTypes, errors } } = options; + const { parseCursor, cursorSymbol, allowLeadingWildcards = true, helpers: { nodeTypes } } = options; const buildFunctionNode = nodeTypes.function.buildNodeWithArgumentNodes; const buildLiteralNode = nodeTypes.literal.buildNode; const buildWildcardNode = nodeTypes.wildcard.buildNode; @@ -163,7 +163,7 @@ Value if (value.type === 'cursor') return value; if (!allowLeadingWildcards && value.type === 'wildcard' && nodeTypes.wildcard.hasLeadingWildcard(value)) { - throw new errors.NoLeadingWildcardsError(); + error('Leading wildcards are disabled. See query:allowLeadingWildcards in Advanced Settings.'); } const isPhrase = buildLiteralNode(false); diff --git a/src/ui/public/kuery/ast/legacy_kuery.js b/packages/kbn-es-query/src/kuery/ast/legacy_kuery.js similarity index 100% rename from src/ui/public/kuery/ast/legacy_kuery.js rename to packages/kbn-es-query/src/kuery/ast/legacy_kuery.js diff --git a/src/ui/public/kuery/ast/legacy_kuery.peg b/packages/kbn-es-query/src/kuery/ast/legacy_kuery.peg similarity index 100% rename from src/ui/public/kuery/ast/legacy_kuery.peg rename to packages/kbn-es-query/src/kuery/ast/legacy_kuery.peg diff --git a/src/ui/public/kuery/filter_migration/__tests__/exists.js b/packages/kbn-es-query/src/kuery/filter_migration/__tests__/exists.js similarity index 100% rename from src/ui/public/kuery/filter_migration/__tests__/exists.js rename to packages/kbn-es-query/src/kuery/filter_migration/__tests__/exists.js diff --git a/src/ui/public/kuery/filter_migration/__tests__/filter_to_kuery.js b/packages/kbn-es-query/src/kuery/filter_migration/__tests__/filter_to_kuery.js similarity index 94% rename from src/ui/public/kuery/filter_migration/__tests__/filter_to_kuery.js rename to packages/kbn-es-query/src/kuery/filter_migration/__tests__/filter_to_kuery.js index 4f328678b2127..f21675fdd7de4 100644 --- a/src/ui/public/kuery/filter_migration/__tests__/filter_to_kuery.js +++ b/packages/kbn-es-query/src/kuery/filter_migration/__tests__/filter_to_kuery.js @@ -20,7 +20,6 @@ import _ from 'lodash'; import expect from 'expect.js'; import { filterToKueryAST } from '../filter_to_kuery'; -import { expectDeepEqual } from '../../../../../test_utils/expect_deep_equal.js'; describe('filter to kuery migration', function () { @@ -63,7 +62,7 @@ describe('filter to kuery migration', function () { expect(negatedResult).to.have.property('type', 'function'); expect(negatedResult).to.have.property('function', 'not'); - expectDeepEqual(negatedResult.arguments[0], result); + expect(negatedResult.arguments[0]).to.eql(result); }); }); diff --git a/src/ui/public/kuery/filter_migration/__tests__/geo_bounding_box.js b/packages/kbn-es-query/src/kuery/filter_migration/__tests__/geo_bounding_box.js similarity index 100% rename from src/ui/public/kuery/filter_migration/__tests__/geo_bounding_box.js rename to packages/kbn-es-query/src/kuery/filter_migration/__tests__/geo_bounding_box.js diff --git a/src/ui/public/kuery/filter_migration/__tests__/geo_polygon.js b/packages/kbn-es-query/src/kuery/filter_migration/__tests__/geo_polygon.js similarity index 100% rename from src/ui/public/kuery/filter_migration/__tests__/geo_polygon.js rename to packages/kbn-es-query/src/kuery/filter_migration/__tests__/geo_polygon.js diff --git a/src/ui/public/kuery/filter_migration/__tests__/phrase.js b/packages/kbn-es-query/src/kuery/filter_migration/__tests__/phrase.js similarity index 100% rename from src/ui/public/kuery/filter_migration/__tests__/phrase.js rename to packages/kbn-es-query/src/kuery/filter_migration/__tests__/phrase.js diff --git a/src/ui/public/kuery/filter_migration/__tests__/range.js b/packages/kbn-es-query/src/kuery/filter_migration/__tests__/range.js similarity index 100% rename from src/ui/public/kuery/filter_migration/__tests__/range.js rename to packages/kbn-es-query/src/kuery/filter_migration/__tests__/range.js diff --git a/src/ui/public/kuery/filter_migration/exists.js b/packages/kbn-es-query/src/kuery/filter_migration/exists.js similarity index 100% rename from src/ui/public/kuery/filter_migration/exists.js rename to packages/kbn-es-query/src/kuery/filter_migration/exists.js diff --git a/src/ui/public/kuery/filter_migration/filter_to_kuery.js b/packages/kbn-es-query/src/kuery/filter_migration/filter_to_kuery.js similarity index 100% rename from src/ui/public/kuery/filter_migration/filter_to_kuery.js rename to packages/kbn-es-query/src/kuery/filter_migration/filter_to_kuery.js diff --git a/src/ui/public/kuery/filter_migration/geo_bounding_box.js b/packages/kbn-es-query/src/kuery/filter_migration/geo_bounding_box.js similarity index 100% rename from src/ui/public/kuery/filter_migration/geo_bounding_box.js rename to packages/kbn-es-query/src/kuery/filter_migration/geo_bounding_box.js diff --git a/src/ui/public/kuery/filter_migration/geo_polygon.js b/packages/kbn-es-query/src/kuery/filter_migration/geo_polygon.js similarity index 100% rename from src/ui/public/kuery/filter_migration/geo_polygon.js rename to packages/kbn-es-query/src/kuery/filter_migration/geo_polygon.js diff --git a/src/ui/public/kuery/filter_migration/index.js b/packages/kbn-es-query/src/kuery/filter_migration/index.js similarity index 100% rename from src/ui/public/kuery/filter_migration/index.js rename to packages/kbn-es-query/src/kuery/filter_migration/index.js diff --git a/src/ui/public/kuery/filter_migration/phrase.js b/packages/kbn-es-query/src/kuery/filter_migration/phrase.js similarity index 100% rename from src/ui/public/kuery/filter_migration/phrase.js rename to packages/kbn-es-query/src/kuery/filter_migration/phrase.js diff --git a/src/ui/public/kuery/filter_migration/range.js b/packages/kbn-es-query/src/kuery/filter_migration/range.js similarity index 100% rename from src/ui/public/kuery/filter_migration/range.js rename to packages/kbn-es-query/src/kuery/filter_migration/range.js diff --git a/src/ui/public/kuery/functions/__tests__/and.js b/packages/kbn-es-query/src/kuery/functions/__tests__/and.js similarity index 96% rename from src/ui/public/kuery/functions/__tests__/and.js rename to packages/kbn-es-query/src/kuery/functions/__tests__/and.js index 1ce6ac0a63466..2a29e01d2b50b 100644 --- a/src/ui/public/kuery/functions/__tests__/and.js +++ b/packages/kbn-es-query/src/kuery/functions/__tests__/and.js @@ -21,7 +21,7 @@ import expect from 'expect.js'; import * as and from '../and'; import { nodeTypes } from '../../node_types'; import * as ast from '../../ast'; -import indexPatternResponse from '../../__tests__/index_pattern_response.json'; +import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json'; let indexPattern; diff --git a/src/ui/public/kuery/functions/__tests__/exists.js b/packages/kbn-es-query/src/kuery/functions/__tests__/exists.js similarity index 96% rename from src/ui/public/kuery/functions/__tests__/exists.js rename to packages/kbn-es-query/src/kuery/functions/__tests__/exists.js index dc7c56bc9c991..033b103bf2d11 100644 --- a/src/ui/public/kuery/functions/__tests__/exists.js +++ b/packages/kbn-es-query/src/kuery/functions/__tests__/exists.js @@ -21,7 +21,7 @@ import expect from 'expect.js'; import * as exists from '../exists'; import { nodeTypes } from '../../node_types'; import _ from 'lodash'; -import indexPatternResponse from '../../__tests__/index_pattern_response.json'; +import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json'; let indexPattern; diff --git a/src/ui/public/kuery/functions/__tests__/geo_bounding_box.js b/packages/kbn-es-query/src/kuery/functions/__tests__/geo_bounding_box.js similarity index 97% rename from src/ui/public/kuery/functions/__tests__/geo_bounding_box.js rename to packages/kbn-es-query/src/kuery/functions/__tests__/geo_bounding_box.js index 477aa23b628c4..d5fb98525c43d 100644 --- a/src/ui/public/kuery/functions/__tests__/geo_bounding_box.js +++ b/packages/kbn-es-query/src/kuery/functions/__tests__/geo_bounding_box.js @@ -20,7 +20,7 @@ import expect from 'expect.js'; import * as geoBoundingBox from '../geo_bounding_box'; import { nodeTypes } from '../../node_types'; -import indexPatternResponse from '../../__tests__/index_pattern_response.json'; +import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json'; let indexPattern; const params = { diff --git a/src/ui/public/kuery/functions/__tests__/geo_polygon.js b/packages/kbn-es-query/src/kuery/functions/__tests__/geo_polygon.js similarity index 97% rename from src/ui/public/kuery/functions/__tests__/geo_polygon.js rename to packages/kbn-es-query/src/kuery/functions/__tests__/geo_polygon.js index 330431c5bdb56..223d24e2fe116 100644 --- a/src/ui/public/kuery/functions/__tests__/geo_polygon.js +++ b/packages/kbn-es-query/src/kuery/functions/__tests__/geo_polygon.js @@ -20,7 +20,7 @@ import expect from 'expect.js'; import * as geoPolygon from '../geo_polygon'; import { nodeTypes } from '../../node_types'; -import indexPatternResponse from '../../__tests__/index_pattern_response.json'; +import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json'; let indexPattern; diff --git a/src/ui/public/kuery/functions/__tests__/is.js b/packages/kbn-es-query/src/kuery/functions/__tests__/is.js similarity index 93% rename from src/ui/public/kuery/functions/__tests__/is.js rename to packages/kbn-es-query/src/kuery/functions/__tests__/is.js index 6c51dcab7e50d..2dca24b187875 100644 --- a/src/ui/public/kuery/functions/__tests__/is.js +++ b/packages/kbn-es-query/src/kuery/functions/__tests__/is.js @@ -20,9 +20,7 @@ import expect from 'expect.js'; import * as is from '../is'; import { nodeTypes } from '../../node_types'; -import indexPatternResponse from '../../__tests__/index_pattern_response.json'; - -import { expectDeepEqual } from '../../../../../test_utils/expect_deep_equal'; +import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json'; let indexPattern; @@ -79,7 +77,7 @@ describe('kuery functions', function () { const node = nodeTypes.function.buildNode('is', '*', '*'); const result = is.toElasticsearchQuery(node, indexPattern); - expectDeepEqual(result, expected); + expect(result).to.eql(expected); }); it('should return an ES multi_match query using default_field when fieldName is null', function () { @@ -93,7 +91,7 @@ describe('kuery functions', function () { const node = nodeTypes.function.buildNode('is', null, 200); const result = is.toElasticsearchQuery(node, indexPattern); - expectDeepEqual(result, expected); + expect(result).to.eql(expected); }); it('should return an ES query_string query using default_field when fieldName is null and value contains a wildcard', function () { @@ -105,7 +103,7 @@ describe('kuery functions', function () { const node = nodeTypes.function.buildNode('is', null, 'jpg*'); const result = is.toElasticsearchQuery(node, indexPattern); - expectDeepEqual(result, expected); + expect(result).to.eql(expected); }); it('should return an ES bool query with a sub-query for each field when fieldName is "*"', function () { @@ -127,7 +125,7 @@ describe('kuery functions', function () { const node = nodeTypes.function.buildNode('is', 'extension', '*'); const result = is.toElasticsearchQuery(node, indexPattern); - expectDeepEqual(result, expected); + expect(result).to.eql(expected); }); it('should return an ES match query when a concrete fieldName and value are provided', function () { @@ -142,7 +140,7 @@ describe('kuery functions', function () { const node = nodeTypes.function.buildNode('is', 'extension', 'jpg'); const result = is.toElasticsearchQuery(node, indexPattern); - expectDeepEqual(result, expected); + expect(result).to.eql(expected); }); it('should support creation of phrase queries', function () { @@ -157,7 +155,7 @@ describe('kuery functions', function () { const node = nodeTypes.function.buildNode('is', 'extension', 'jpg', true); const result = is.toElasticsearchQuery(node, indexPattern); - expectDeepEqual(result, expected); + expect(result).to.eql(expected); }); it('should create a query_string query for wildcard values', function () { @@ -177,7 +175,7 @@ describe('kuery functions', function () { const node = nodeTypes.function.buildNode('is', 'extension', 'jpg*'); const result = is.toElasticsearchQuery(node, indexPattern); - expectDeepEqual(result, expected); + expect(result).to.eql(expected); }); it('should support scripted fields', function () { diff --git a/src/ui/public/kuery/functions/__tests__/not.js b/packages/kbn-es-query/src/kuery/functions/__tests__/not.js similarity index 95% rename from src/ui/public/kuery/functions/__tests__/not.js rename to packages/kbn-es-query/src/kuery/functions/__tests__/not.js index 41e24bfcd041f..f397382b543e8 100644 --- a/src/ui/public/kuery/functions/__tests__/not.js +++ b/packages/kbn-es-query/src/kuery/functions/__tests__/not.js @@ -21,7 +21,7 @@ import expect from 'expect.js'; import * as not from '../not'; import { nodeTypes } from '../../node_types'; import * as ast from '../../ast'; -import indexPatternResponse from '../../__tests__/index_pattern_response.json'; +import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json'; let indexPattern; diff --git a/src/ui/public/kuery/functions/__tests__/or.js b/packages/kbn-es-query/src/kuery/functions/__tests__/or.js similarity index 96% rename from src/ui/public/kuery/functions/__tests__/or.js rename to packages/kbn-es-query/src/kuery/functions/__tests__/or.js index 4856b2a21c5cd..49a2b21446b02 100644 --- a/src/ui/public/kuery/functions/__tests__/or.js +++ b/packages/kbn-es-query/src/kuery/functions/__tests__/or.js @@ -21,7 +21,7 @@ import expect from 'expect.js'; import * as or from '../or'; import { nodeTypes } from '../../node_types'; import * as ast from '../../ast'; -import indexPatternResponse from '../../__tests__/index_pattern_response.json'; +import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json'; let indexPattern; diff --git a/src/ui/public/kuery/functions/__tests__/range.js b/packages/kbn-es-query/src/kuery/functions/__tests__/range.js similarity index 93% rename from src/ui/public/kuery/functions/__tests__/range.js rename to packages/kbn-es-query/src/kuery/functions/__tests__/range.js index d9a59a88c33b7..25c309790f266 100644 --- a/src/ui/public/kuery/functions/__tests__/range.js +++ b/packages/kbn-es-query/src/kuery/functions/__tests__/range.js @@ -18,10 +18,9 @@ */ import expect from 'expect.js'; -import { expectDeepEqual } from '../../../../../test_utils/expect_deep_equal'; import * as range from '../range'; import { nodeTypes } from '../../node_types'; -import indexPatternResponse from '../../__tests__/index_pattern_response.json'; +import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json'; let indexPattern; @@ -84,7 +83,7 @@ describe('kuery functions', function () { const node = nodeTypes.function.buildNode('range', 'bytes', { gt: 1000, lt: 8000 }); const result = range.toElasticsearchQuery(node, indexPattern); - expectDeepEqual(result, expected); + expect(result).to.eql(expected); }); it('should support wildcard field names', function () { @@ -106,7 +105,7 @@ describe('kuery functions', function () { const node = nodeTypes.function.buildNode('range', 'byt*', { gt: 1000, lt: 8000 }); const result = range.toElasticsearchQuery(node, indexPattern); - expectDeepEqual(result, expected); + expect(result).to.eql(expected); }); it('should support scripted fields', function () { diff --git a/src/ui/public/kuery/functions/__tests__/utils/get_fields.js b/packages/kbn-es-query/src/kuery/functions/__tests__/utils/get_fields.js similarity index 91% rename from src/ui/public/kuery/functions/__tests__/utils/get_fields.js rename to packages/kbn-es-query/src/kuery/functions/__tests__/utils/get_fields.js index 1019e9e44fa0c..501f61a92b5e2 100644 --- a/src/ui/public/kuery/functions/__tests__/utils/get_fields.js +++ b/packages/kbn-es-query/src/kuery/functions/__tests__/utils/get_fields.js @@ -19,10 +19,9 @@ import { getFields } from '../../utils/get_fields'; import expect from 'expect.js'; -import indexPatternResponse from '../../../__tests__/index_pattern_response.json'; +import indexPatternResponse from '../../../../__fixtures__/index_pattern_response.json'; import { nodeTypes } from '../../..'; -import { expectDeepEqual } from '../../../../../../test_utils/expect_deep_equal'; let indexPattern; @@ -39,7 +38,7 @@ describe('getFields', function () { const fieldNameNode = nodeTypes.literal.buildNode('nonExistentField'); const expected = []; const actual = getFields(fieldNameNode, indexPattern); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should return the single matching field in an array', function () { @@ -69,7 +68,7 @@ describe('getFields', function () { // ensure the wildcard is not actually being parsed const expected = []; const actual = getFields(nodeTypes.literal.buildNode('fo*'), indexPatternWithWildField); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); }); @@ -79,7 +78,7 @@ describe('getFields', function () { const fieldNameNode = nodeTypes.wildcard.buildNode('nonExistent*'); const expected = []; const actual = getFields(fieldNameNode, indexPattern); - expectDeepEqual(actual, expected); + expect(actual).to.eql(expected); }); it('should return all fields that match the pattern in an array', function () { diff --git a/src/ui/public/kuery/functions/and.js b/packages/kbn-es-query/src/kuery/functions/and.js similarity index 100% rename from src/ui/public/kuery/functions/and.js rename to packages/kbn-es-query/src/kuery/functions/and.js diff --git a/src/ui/public/kuery/functions/exists.js b/packages/kbn-es-query/src/kuery/functions/exists.js similarity index 100% rename from src/ui/public/kuery/functions/exists.js rename to packages/kbn-es-query/src/kuery/functions/exists.js diff --git a/src/ui/public/kuery/functions/geo_bounding_box.js b/packages/kbn-es-query/src/kuery/functions/geo_bounding_box.js similarity index 100% rename from src/ui/public/kuery/functions/geo_bounding_box.js rename to packages/kbn-es-query/src/kuery/functions/geo_bounding_box.js diff --git a/src/ui/public/kuery/functions/geo_polygon.js b/packages/kbn-es-query/src/kuery/functions/geo_polygon.js similarity index 100% rename from src/ui/public/kuery/functions/geo_polygon.js rename to packages/kbn-es-query/src/kuery/functions/geo_polygon.js diff --git a/src/ui/public/kuery/functions/index.js b/packages/kbn-es-query/src/kuery/functions/index.js similarity index 100% rename from src/ui/public/kuery/functions/index.js rename to packages/kbn-es-query/src/kuery/functions/index.js diff --git a/src/ui/public/kuery/functions/is.js b/packages/kbn-es-query/src/kuery/functions/is.js similarity index 98% rename from src/ui/public/kuery/functions/is.js rename to packages/kbn-es-query/src/kuery/functions/is.js index f48ce1ae4732a..a13d8883e495b 100644 --- a/src/ui/public/kuery/functions/is.js +++ b/packages/kbn-es-query/src/kuery/functions/is.js @@ -21,7 +21,7 @@ import _ from 'lodash'; import * as ast from '../ast'; import * as literal from '../node_types/literal'; import * as wildcard from '../node_types/wildcard'; -import { getPhraseScript } from '../../filter_manager/lib/phrase'; +import { getPhraseScript } from '../../filters'; import { getFields } from './utils/get_fields'; export function buildNodeParams(fieldName, value, isPhrase = false) { diff --git a/src/ui/public/kuery/functions/not.js b/packages/kbn-es-query/src/kuery/functions/not.js similarity index 100% rename from src/ui/public/kuery/functions/not.js rename to packages/kbn-es-query/src/kuery/functions/not.js diff --git a/src/ui/public/kuery/functions/or.js b/packages/kbn-es-query/src/kuery/functions/or.js similarity index 100% rename from src/ui/public/kuery/functions/or.js rename to packages/kbn-es-query/src/kuery/functions/or.js diff --git a/src/ui/public/kuery/functions/range.js b/packages/kbn-es-query/src/kuery/functions/range.js similarity index 97% rename from src/ui/public/kuery/functions/range.js rename to packages/kbn-es-query/src/kuery/functions/range.js index 91bbb68ec411d..0853e53e1f81a 100644 --- a/src/ui/public/kuery/functions/range.js +++ b/packages/kbn-es-query/src/kuery/functions/range.js @@ -20,7 +20,7 @@ import _ from 'lodash'; import { nodeTypes } from '../node_types'; import * as ast from '../ast'; -import { getRangeScript } from '../../filter_manager/lib/range'; +import { getRangeScript } from '../../filters'; import { getFields } from './utils/get_fields'; export function buildNodeParams(fieldName, params) { diff --git a/src/ui/public/kuery/functions/utils/get_fields.js b/packages/kbn-es-query/src/kuery/functions/utils/get_fields.js similarity index 100% rename from src/ui/public/kuery/functions/utils/get_fields.js rename to packages/kbn-es-query/src/kuery/functions/utils/get_fields.js diff --git a/packages/kbn-es-query/src/kuery/index.d.ts b/packages/kbn-es-query/src/kuery/index.d.ts new file mode 100644 index 0000000000000..9d797406420d4 --- /dev/null +++ b/packages/kbn-es-query/src/kuery/index.d.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './ast'; diff --git a/src/ui/public/kuery/index.js b/packages/kbn-es-query/src/kuery/index.js similarity index 100% rename from src/ui/public/kuery/index.js rename to packages/kbn-es-query/src/kuery/index.js diff --git a/src/ui/public/kuery/node_types/__tests__/function.js b/packages/kbn-es-query/src/kuery/node_types/__tests__/function.js similarity index 92% rename from src/ui/public/kuery/node_types/__tests__/function.js rename to packages/kbn-es-query/src/kuery/node_types/__tests__/function.js index 5767e50fa6a21..4e7e1d9254365 100644 --- a/src/ui/public/kuery/node_types/__tests__/function.js +++ b/packages/kbn-es-query/src/kuery/node_types/__tests__/function.js @@ -20,9 +20,8 @@ import * as functionType from '../function'; import _ from 'lodash'; import expect from 'expect.js'; -import { expectDeepEqual } from '../../../../../test_utils/expect_deep_equal.js'; import * as isFunction from '../../functions/is'; -import indexPatternResponse from '../../__tests__/index_pattern_response.json'; +import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json'; import { nodeTypes } from '../../node_types'; @@ -60,7 +59,7 @@ describe('kuery node types', function () { expect(result).to.have.property('function', 'is'); expect(result).to.have.property('arguments'); expect(result.arguments).to.be(argumentNodes); - expectDeepEqual(result.arguments, argumentNodes); + expect(result.arguments).to.eql(argumentNodes); }); }); diff --git a/src/ui/public/kuery/node_types/__tests__/literal.js b/packages/kbn-es-query/src/kuery/node_types/__tests__/literal.js similarity index 100% rename from src/ui/public/kuery/node_types/__tests__/literal.js rename to packages/kbn-es-query/src/kuery/node_types/__tests__/literal.js diff --git a/src/ui/public/kuery/node_types/__tests__/named_arg.js b/packages/kbn-es-query/src/kuery/node_types/__tests__/named_arg.js similarity index 94% rename from src/ui/public/kuery/node_types/__tests__/named_arg.js rename to packages/kbn-es-query/src/kuery/node_types/__tests__/named_arg.js index 48cfad1567ee3..6a7d8cb3abb81 100644 --- a/src/ui/public/kuery/node_types/__tests__/named_arg.js +++ b/packages/kbn-es-query/src/kuery/node_types/__tests__/named_arg.js @@ -18,7 +18,6 @@ */ import expect from 'expect.js'; -import { expectDeepEqual } from '../../../../../test_utils/expect_deep_equal.js'; import * as namedArg from '../named_arg'; import { nodeTypes } from '../../node_types'; @@ -43,7 +42,7 @@ describe('kuery node types', function () { const value = nodeTypes.literal.buildNode('foo'); const result = namedArg.buildNode('fieldName', value); expect(result.value).to.be(value); - expectDeepEqual(result.value, value); + expect(result.value).to.eql(value); }); }); diff --git a/src/ui/public/kuery/node_types/__tests__/wildcard.js b/packages/kbn-es-query/src/kuery/node_types/__tests__/wildcard.js similarity index 100% rename from src/ui/public/kuery/node_types/__tests__/wildcard.js rename to packages/kbn-es-query/src/kuery/node_types/__tests__/wildcard.js diff --git a/src/ui/public/kuery/node_types/function.js b/packages/kbn-es-query/src/kuery/node_types/function.js similarity index 100% rename from src/ui/public/kuery/node_types/function.js rename to packages/kbn-es-query/src/kuery/node_types/function.js diff --git a/src/ui/public/kuery/node_types/index.js b/packages/kbn-es-query/src/kuery/node_types/index.js similarity index 100% rename from src/ui/public/kuery/node_types/index.js rename to packages/kbn-es-query/src/kuery/node_types/index.js diff --git a/src/ui/public/kuery/node_types/literal.js b/packages/kbn-es-query/src/kuery/node_types/literal.js similarity index 100% rename from src/ui/public/kuery/node_types/literal.js rename to packages/kbn-es-query/src/kuery/node_types/literal.js diff --git a/src/ui/public/kuery/node_types/named_arg.js b/packages/kbn-es-query/src/kuery/node_types/named_arg.js similarity index 100% rename from src/ui/public/kuery/node_types/named_arg.js rename to packages/kbn-es-query/src/kuery/node_types/named_arg.js diff --git a/src/ui/public/kuery/node_types/wildcard.js b/packages/kbn-es-query/src/kuery/node_types/wildcard.js similarity index 100% rename from src/ui/public/kuery/node_types/wildcard.js rename to packages/kbn-es-query/src/kuery/node_types/wildcard.js diff --git a/packages/kbn-es-query/tsconfig.json b/packages/kbn-es-query/tsconfig.json new file mode 100644 index 0000000000000..9a22ea4fc44aa --- /dev/null +++ b/packages/kbn-es-query/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "include": [ + "index.d.ts", + "src/**/*.d.ts" + ] +} diff --git a/packages/kbn-es-query/yarn.lock b/packages/kbn-es-query/yarn.lock new file mode 100644 index 0000000000000..57990d9b6c188 --- /dev/null +++ b/packages/kbn-es-query/yarn.lock @@ -0,0 +1,2292 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@kbn/babel-preset@link:../kbn-babel-preset": + version "0.0.0" + uid "" + +abbrev@1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== + +ansi-regex@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= + +ansi-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= + +anymatch@^1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.2.tgz#553dcb8f91e3c889845dfdba34c77721b90b9d7a" + integrity sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA== + dependencies: + micromatch "^2.1.5" + normalize-path "^2.0.0" + +aproba@^1.0.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== + +are-we-there-yet@~1.1.2: + version "1.1.5" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" + integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +arr-diff@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" + integrity sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8= + dependencies: + arr-flatten "^1.0.1" + +arr-diff@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= + +arr-flatten@^1.0.1, arr-flatten@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== + +arr-union@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" + integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= + +array-unique@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" + integrity sha1-odl8yvy8JiXMcPrc6zalDFiwGlM= + +array-unique@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= + +assign-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= + +async-each@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" + integrity sha1-GdOGodntxufByF04iu28xW0zYC0= + +atob@^2.1.1: + version "2.1.2" + resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== + +babel-cli@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-cli/-/babel-cli-6.26.0.tgz#502ab54874d7db88ad00b887a06383ce03d002f1" + integrity sha1-UCq1SHTX24itALiHoGODzgPQAvE= + dependencies: + babel-core "^6.26.0" + babel-polyfill "^6.26.0" + babel-register "^6.26.0" + babel-runtime "^6.26.0" + commander "^2.11.0" + convert-source-map "^1.5.0" + fs-readdir-recursive "^1.0.0" + glob "^7.1.2" + lodash "^4.17.4" + output-file-sync "^1.1.2" + path-is-absolute "^1.0.1" + slash "^1.0.0" + source-map "^0.5.6" + v8flags "^2.1.1" + optionalDependencies: + chokidar "^1.6.1" + +babel-code-frame@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" + integrity sha1-Y/1D99weO7fONZR9uP42mj9Yx0s= + dependencies: + chalk "^1.1.3" + esutils "^2.0.2" + js-tokens "^3.0.2" + +babel-core@^6.26.0: + version "6.26.3" + resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207" + integrity sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA== + dependencies: + babel-code-frame "^6.26.0" + babel-generator "^6.26.0" + babel-helpers "^6.24.1" + babel-messages "^6.23.0" + babel-register "^6.26.0" + babel-runtime "^6.26.0" + babel-template "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + convert-source-map "^1.5.1" + debug "^2.6.9" + json5 "^0.5.1" + lodash "^4.17.4" + minimatch "^3.0.4" + path-is-absolute "^1.0.1" + private "^0.1.8" + slash "^1.0.0" + source-map "^0.5.7" + +babel-generator@^6.26.0: + version "6.26.1" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90" + integrity sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA== + dependencies: + babel-messages "^6.23.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + detect-indent "^4.0.0" + jsesc "^1.3.0" + lodash "^4.17.4" + source-map "^0.5.7" + trim-right "^1.0.1" + +babel-helper-builder-binary-assignment-operator-visitor@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664" + integrity sha1-zORReto1b0IgvK6KAsKzRvmlZmQ= + dependencies: + babel-helper-explode-assignable-expression "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-builder-react-jsx@^6.24.1: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz#39ff8313b75c8b65dceff1f31d383e0ff2a408a0" + integrity sha1-Of+DE7dci2Xc7/HzHTg+D/KkCKA= + dependencies: + babel-runtime "^6.26.0" + babel-types "^6.26.0" + esutils "^2.0.2" + +babel-helper-call-delegate@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d" + integrity sha1-7Oaqzdx25Bw0YfiL/Fdb0Nqi340= + dependencies: + babel-helper-hoist-variables "^6.24.1" + babel-runtime "^6.22.0" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-define-map@^6.24.1: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz#a5f56dab41a25f97ecb498c7ebaca9819f95be5f" + integrity sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8= + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + lodash "^4.17.4" + +babel-helper-explode-assignable-expression@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa" + integrity sha1-8luCz33BBDPFX3BZLVdGQArCLKo= + dependencies: + babel-runtime "^6.22.0" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-function-name@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9" + integrity sha1-00dbjAPtmCQqJbSDUasYOZ01gKk= + dependencies: + babel-helper-get-function-arity "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-get-function-arity@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d" + integrity sha1-j3eCqpNAfEHTqlCQj4mwMbG2hT0= + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-hoist-variables@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76" + integrity sha1-HssnaJydJVE+rbyZFKc/VAi+enY= + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-optimise-call-expression@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257" + integrity sha1-96E0J7qfc/j0+pk8VKl4gtEkQlc= + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-regex@^6.24.1: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz#325c59f902f82f24b74faceed0363954f6495e72" + integrity sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI= + dependencies: + babel-runtime "^6.26.0" + babel-types "^6.26.0" + lodash "^4.17.4" + +babel-helper-remap-async-to-generator@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b" + integrity sha1-XsWBgnrXI/7N04HxySg5BnbkVRs= + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-replace-supers@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a" + integrity sha1-v22/5Dk40XNpohPKiov3S2qQqxo= + dependencies: + babel-helper-optimise-call-expression "^6.24.1" + babel-messages "^6.23.0" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helpers@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" + integrity sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI= + dependencies: + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-messages@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" + integrity sha1-8830cDhYA1sqKVHG7F7fbGLyYw4= + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-add-module-exports@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/babel-plugin-add-module-exports/-/babel-plugin-add-module-exports-0.2.1.tgz#9ae9a1f4a8dc67f0cdec4f4aeda1e43a5ff65e25" + integrity sha1-mumh9KjcZ/DN7E9K7aHkOl/2XiU= + +babel-plugin-check-es2015-constants@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a" + integrity sha1-NRV7EBQm/S/9PaP3XH0ekYNbv4o= + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-syntax-async-functions@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" + integrity sha1-ytnK0RkbWtY0vzCuCHI5HgZHvpU= + +babel-plugin-syntax-async-generators@^6.5.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz#6bc963ebb16eccbae6b92b596eb7f35c342a8b9a" + integrity sha1-a8lj67FuzLrmuStZbrfzXDQqi5o= + +babel-plugin-syntax-class-properties@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz#d7eb23b79a317f8543962c505b827c7d6cac27de" + integrity sha1-1+sjt5oxf4VDlixQW4J8fWysJ94= + +babel-plugin-syntax-exponentiation-operator@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de" + integrity sha1-nufoM3KQ2pUoggGmpX9BcDF4MN4= + +babel-plugin-syntax-flow@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d" + integrity sha1-TDqyCiryaqIM0lmVw5jE63AxDI0= + +babel-plugin-syntax-jsx@^6.3.13, babel-plugin-syntax-jsx@^6.8.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" + integrity sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY= + +babel-plugin-syntax-object-rest-spread@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" + integrity sha1-/WU28rzhODb/o6VFjEkDpZe7O/U= + +babel-plugin-syntax-trailing-function-commas@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3" + integrity sha1-ugNgk3+NBuQBgKQ/4NVhb/9TLPM= + +babel-plugin-transform-async-generator-functions@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz#f058900145fd3e9907a6ddf28da59f215258a5db" + integrity sha1-8FiQAUX9PpkHpt3yjaWfIVJYpds= + dependencies: + babel-helper-remap-async-to-generator "^6.24.1" + babel-plugin-syntax-async-generators "^6.5.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-async-to-generator@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761" + integrity sha1-ZTbjeK/2yx1VF6wOQOs+n8jQh2E= + dependencies: + babel-helper-remap-async-to-generator "^6.24.1" + babel-plugin-syntax-async-functions "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-class-properties@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz#6a79763ea61d33d36f37b611aa9def81a81b46ac" + integrity sha1-anl2PqYdM9NvN7YRqp3vgagbRqw= + dependencies: + babel-helper-function-name "^6.24.1" + babel-plugin-syntax-class-properties "^6.8.0" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-define@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-define/-/babel-plugin-transform-define-1.3.0.tgz#94c5f9459c810c738cc7c50cbd44a31829d6f319" + integrity sha1-lMX5RZyBDHOMx8UMvUSjGCnW8xk= + dependencies: + lodash "4.17.4" + traverse "0.6.6" + +babel-plugin-transform-es2015-arrow-functions@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221" + integrity sha1-RSaSy3EdX3ncf4XkQM5BufJE0iE= + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-block-scoped-functions@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141" + integrity sha1-u8UbSflk1wy42OC5ToICRs46YUE= + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-block-scoping@^6.23.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f" + integrity sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8= + dependencies: + babel-runtime "^6.26.0" + babel-template "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + lodash "^4.17.4" + +babel-plugin-transform-es2015-classes@^6.23.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db" + integrity sha1-WkxYpQyclGHlZLSyo7+ryXolhNs= + dependencies: + babel-helper-define-map "^6.24.1" + babel-helper-function-name "^6.24.1" + babel-helper-optimise-call-expression "^6.24.1" + babel-helper-replace-supers "^6.24.1" + babel-messages "^6.23.0" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-computed-properties@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3" + integrity sha1-b+Ko0WiV1WNPTNmZttNICjCBWbM= + dependencies: + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-destructuring@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d" + integrity sha1-mXux8auWf2gtKwh2/jWNYOdlxW0= + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-duplicate-keys@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e" + integrity sha1-c+s9MQypaePvnskcU3QabxV2Qj4= + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-for-of@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691" + integrity sha1-9HyVsrYT3x0+zC/bdXNiPHUkhpE= + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-function-name@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b" + integrity sha1-g0yJhTvDaxrw86TF26qU/Y6sqos= + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-literals@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e" + integrity sha1-T1SgLWzWbPkVKAAZox0xklN3yi4= + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015-modules-amd@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154" + integrity sha1-Oz5UAXI5hC1tGcMBHEvS8AoA0VQ= + dependencies: + babel-plugin-transform-es2015-modules-commonjs "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1: + version "6.26.2" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3" + integrity sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q== + dependencies: + babel-plugin-transform-strict-mode "^6.24.1" + babel-runtime "^6.26.0" + babel-template "^6.26.0" + babel-types "^6.26.0" + +babel-plugin-transform-es2015-modules-systemjs@^6.23.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23" + integrity sha1-/4mhQrkRmpBhlfXxBuzzBdlAfSM= + dependencies: + babel-helper-hoist-variables "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-modules-umd@^6.23.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468" + integrity sha1-rJl+YoXNGO1hdq22B9YCNErThGg= + dependencies: + babel-plugin-transform-es2015-modules-amd "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-object-super@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d" + integrity sha1-JM72muIcuDp/hgPa0CH1cusnj40= + dependencies: + babel-helper-replace-supers "^6.24.1" + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-parameters@^6.23.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b" + integrity sha1-V6w1GrScrxSpfNE7CfZv3wpiXys= + dependencies: + babel-helper-call-delegate "^6.24.1" + babel-helper-get-function-arity "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-shorthand-properties@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0" + integrity sha1-JPh11nIch2YbvZmkYi5R8U3jiqA= + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-spread@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1" + integrity sha1-1taKmfia7cRTbIGlQujdnxdG+NE= + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-sticky-regex@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc" + integrity sha1-AMHNsaynERLN8M9hJsLta0V8zbw= + dependencies: + babel-helper-regex "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-template-literals@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d" + integrity sha1-qEs0UPfp+PH2g51taH2oS7EjbY0= + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-typeof-symbol@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372" + integrity sha1-3sCfHN3/lLUqxz1QXITfWdzOs3I= + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-unicode-regex@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9" + integrity sha1-04sS9C6nMj9yk4fxinxa4frrNek= + dependencies: + babel-helper-regex "^6.24.1" + babel-runtime "^6.22.0" + regexpu-core "^2.0.0" + +babel-plugin-transform-exponentiation-operator@^6.22.0: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e" + integrity sha1-KrDJx/MJj6SJB3cruBP+QejeOg4= + dependencies: + babel-helper-builder-binary-assignment-operator-visitor "^6.24.1" + babel-plugin-syntax-exponentiation-operator "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-flow-strip-types@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz#84cb672935d43714fdc32bce84568d87441cf7cf" + integrity sha1-hMtnKTXUNxT9wyvOhFaNh0Qc988= + dependencies: + babel-plugin-syntax-flow "^6.18.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-object-rest-spread@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.26.0.tgz#0f36692d50fef6b7e2d4b3ac1478137a963b7b06" + integrity sha1-DzZpLVD+9rfi1LOsFHgTepY7ewY= + dependencies: + babel-plugin-syntax-object-rest-spread "^6.8.0" + babel-runtime "^6.26.0" + +babel-plugin-transform-react-display-name@^6.23.0: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz#67e2bf1f1e9c93ab08db96792e05392bf2cc28d1" + integrity sha1-Z+K/Hx6ck6sI25Z5LgU5K/LMKNE= + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-react-jsx-self@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz#df6d80a9da2612a121e6ddd7558bcbecf06e636e" + integrity sha1-322AqdomEqEh5t3XVYvL7PBuY24= + dependencies: + babel-plugin-syntax-jsx "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-react-jsx-source@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz#66ac12153f5cd2d17b3c19268f4bf0197f44ecd6" + integrity sha1-ZqwSFT9c0tF7PBkmj0vwGX9E7NY= + dependencies: + babel-plugin-syntax-jsx "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-react-jsx@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz#840a028e7df460dfc3a2d29f0c0d91f6376e66a3" + integrity sha1-hAoCjn30YN/DotKfDA2R9jduZqM= + dependencies: + babel-helper-builder-react-jsx "^6.24.1" + babel-plugin-syntax-jsx "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-regenerator@^6.22.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f" + integrity sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8= + dependencies: + regenerator-transform "^0.10.0" + +babel-plugin-transform-strict-mode@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758" + integrity sha1-1fr3qleKZbvlkc9e2uBKDGcCB1g= + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-polyfill@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.26.0.tgz#379937abc67d7895970adc621f284cd966cf2153" + integrity sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM= + dependencies: + babel-runtime "^6.26.0" + core-js "^2.5.0" + regenerator-runtime "^0.10.5" + +babel-preset-env@1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.4.0.tgz#c8e02a3bcc7792f23cded68e0355b9d4c28f0f7a" + integrity sha1-yOAqO8x3kvI83taOA1W51MKPD3o= + dependencies: + babel-plugin-check-es2015-constants "^6.22.0" + babel-plugin-syntax-trailing-function-commas "^6.22.0" + babel-plugin-transform-async-to-generator "^6.22.0" + babel-plugin-transform-es2015-arrow-functions "^6.22.0" + babel-plugin-transform-es2015-block-scoped-functions "^6.22.0" + babel-plugin-transform-es2015-block-scoping "^6.23.0" + babel-plugin-transform-es2015-classes "^6.23.0" + babel-plugin-transform-es2015-computed-properties "^6.22.0" + babel-plugin-transform-es2015-destructuring "^6.23.0" + babel-plugin-transform-es2015-duplicate-keys "^6.22.0" + babel-plugin-transform-es2015-for-of "^6.23.0" + babel-plugin-transform-es2015-function-name "^6.22.0" + babel-plugin-transform-es2015-literals "^6.22.0" + babel-plugin-transform-es2015-modules-amd "^6.22.0" + babel-plugin-transform-es2015-modules-commonjs "^6.23.0" + babel-plugin-transform-es2015-modules-systemjs "^6.23.0" + babel-plugin-transform-es2015-modules-umd "^6.23.0" + babel-plugin-transform-es2015-object-super "^6.22.0" + babel-plugin-transform-es2015-parameters "^6.23.0" + babel-plugin-transform-es2015-shorthand-properties "^6.22.0" + babel-plugin-transform-es2015-spread "^6.22.0" + babel-plugin-transform-es2015-sticky-regex "^6.22.0" + babel-plugin-transform-es2015-template-literals "^6.22.0" + babel-plugin-transform-es2015-typeof-symbol "^6.23.0" + babel-plugin-transform-es2015-unicode-regex "^6.22.0" + babel-plugin-transform-exponentiation-operator "^6.22.0" + babel-plugin-transform-regenerator "^6.22.0" + browserslist "^1.4.0" + invariant "^2.2.2" + +babel-preset-flow@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz#e71218887085ae9a24b5be4169affb599816c49d" + integrity sha1-5xIYiHCFrpoktb5Baa/7WZgWxJ0= + dependencies: + babel-plugin-transform-flow-strip-types "^6.22.0" + +babel-preset-react@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-preset-react/-/babel-preset-react-6.24.1.tgz#ba69dfaea45fc3ec639b6a4ecea6e17702c91380" + integrity sha1-umnfrqRfw+xjm2pOzqbhdwLJE4A= + dependencies: + babel-plugin-syntax-jsx "^6.3.13" + babel-plugin-transform-react-display-name "^6.23.0" + babel-plugin-transform-react-jsx "^6.24.1" + babel-plugin-transform-react-jsx-self "^6.22.0" + babel-plugin-transform-react-jsx-source "^6.22.0" + babel-preset-flow "^6.23.0" + +babel-register@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" + integrity sha1-btAhFz4vy0htestFxgCahW9kcHE= + dependencies: + babel-core "^6.26.0" + babel-runtime "^6.26.0" + core-js "^2.5.0" + home-or-tmp "^2.0.0" + lodash "^4.17.4" + mkdirp "^0.5.1" + source-map-support "^0.4.15" + +babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" + integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.11.0" + +babel-template@^6.24.1, babel-template@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" + integrity sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI= + dependencies: + babel-runtime "^6.26.0" + babel-traverse "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + lodash "^4.17.4" + +babel-traverse@^6.24.1, babel-traverse@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" + integrity sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4= + dependencies: + babel-code-frame "^6.26.0" + babel-messages "^6.23.0" + babel-runtime "^6.26.0" + babel-types "^6.26.0" + babylon "^6.18.0" + debug "^2.6.8" + globals "^9.18.0" + invariant "^2.2.2" + lodash "^4.17.4" + +babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0: + version "6.26.0" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" + integrity sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc= + dependencies: + babel-runtime "^6.26.0" + esutils "^2.0.2" + lodash "^4.17.4" + to-fast-properties "^1.0.3" + +babylon@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" + integrity sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ== + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= + +base@^0.11.1: + version "0.11.2" + resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== + dependencies: + cache-base "^1.0.1" + class-utils "^0.3.5" + component-emitter "^1.2.1" + define-property "^1.0.0" + isobject "^3.0.1" + mixin-deep "^1.2.0" + pascalcase "^0.1.1" + +binary-extensions@^1.0.0: + version "1.12.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.12.0.tgz#c2d780f53d45bba8317a8902d4ceeaf3a6385b14" + integrity sha512-DYWGk01lDcxeS/K9IHPGWfT8PsJmbXRtRd2Sx72Tnb8pcYZQFF1oSDb8hJtS1vhp212q1Rzi5dUf9+nq0o9UIg== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^1.8.2: + version "1.8.5" + resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" + integrity sha1-uneWLhLf+WnWt2cR6RS3N4V79qc= + dependencies: + expand-range "^1.8.1" + preserve "^0.2.0" + repeat-element "^1.1.2" + +braces@^2.3.1: + version "2.3.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== + dependencies: + arr-flatten "^1.1.0" + array-unique "^0.3.2" + extend-shallow "^2.0.1" + fill-range "^4.0.0" + isobject "^3.0.1" + repeat-element "^1.1.2" + snapdragon "^0.8.1" + snapdragon-node "^2.0.1" + split-string "^3.0.2" + to-regex "^3.0.1" + +browserslist@^1.4.0: + version "1.7.7" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-1.7.7.tgz#0bd76704258be829b2398bb50e4b62d1a166b0b9" + integrity sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk= + dependencies: + caniuse-db "^1.0.30000639" + electron-to-chromium "^1.2.7" + +cache-base@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== + dependencies: + collection-visit "^1.0.0" + component-emitter "^1.2.1" + get-value "^2.0.6" + has-value "^1.0.0" + isobject "^3.0.1" + set-value "^2.0.0" + to-object-path "^0.3.0" + union-value "^1.0.0" + unset-value "^1.0.0" + +caniuse-db@^1.0.30000639: + version "1.0.30000893" + resolved "https://registry.yarnpkg.com/caniuse-db/-/caniuse-db-1.0.30000893.tgz#2781b8d2ce50d8dd9d091425461b528ca4556c07" + integrity sha512-JfGWsX2JtyXnjGAAjXMHiv4OEGFZvArB5pxdd5oa5xCuEYYN1rhBMwogMIKnKoUBi6lKRyO4JQAPJwqchv20Yg== + +chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chokidar@^1.6.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" + integrity sha1-eY5ol3gVHIB2tLNg5e3SjNortGg= + dependencies: + anymatch "^1.3.0" + async-each "^1.0.0" + glob-parent "^2.0.0" + inherits "^2.0.1" + is-binary-path "^1.0.0" + is-glob "^2.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.0.0" + optionalDependencies: + fsevents "^1.0.0" + +chownr@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" + integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g== + +class-utils@^0.3.5: + version "0.3.6" + resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== + dependencies: + arr-union "^3.1.0" + define-property "^0.2.5" + isobject "^3.0.0" + static-extend "^0.1.1" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= + +collection-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= + dependencies: + map-visit "^1.0.0" + object-visit "^1.0.0" + +commander@^2.11.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.19.0.tgz#f6198aa84e5b83c46054b94ddedbfed5ee9ff12a" + integrity sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg== + +component-emitter@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" + integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY= + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= + +convert-source-map@^1.5.0, convert-source-map@^1.5.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20" + integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A== + dependencies: + safe-buffer "~5.1.1" + +copy-descriptor@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= + +core-js@^2.4.0, core-js@^2.5.0: + version "2.5.7" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e" + integrity sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw== + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= + +debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: + version "2.6.9" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== + dependencies: + ms "2.0.0" + +decode-uri-component@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= + +deep-extend@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +define-property@^0.2.5: + version "0.2.5" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= + dependencies: + is-descriptor "^0.1.0" + +define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= + dependencies: + is-descriptor "^1.0.0" + +define-property@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== + dependencies: + is-descriptor "^1.0.2" + isobject "^3.0.1" + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= + +detect-indent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" + integrity sha1-920GQ1LN9Docts5hnE7jqUdd4gg= + dependencies: + repeating "^2.0.0" + +detect-libc@^1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= + +electron-to-chromium@^1.2.7: + version "1.3.80" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.80.tgz#e99ec7efe64c2c6a269d3885ff411ea88852fa53" + integrity sha512-WClidEWEUNx7OfwXehB0qaxCuetjbKjev2SmXWgybWPLKAThBiMTF/2Pd8GSUDtoGOavxVzdkKwfFAPRSWlkLw== + +escape-string-regexp@^1.0.2: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= + +esutils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= + +expand-brackets@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" + integrity sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s= + dependencies: + is-posix-bracket "^0.1.0" + +expand-brackets@^2.1.4: + version "2.1.4" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= + dependencies: + debug "^2.3.3" + define-property "^0.2.5" + extend-shallow "^2.0.1" + posix-character-classes "^0.1.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +expand-range@^1.8.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" + integrity sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc= + dependencies: + fill-range "^2.1.0" + +expect.js@0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/expect.js/-/expect.js-0.3.1.tgz#b0a59a0d2eff5437544ebf0ceaa6015841d09b5b" + integrity sha1-sKWaDS7/VDdUTr8M6qYBWEHQm1s= + +extend-shallow@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= + dependencies: + is-extendable "^0.1.0" + +extend-shallow@^3.0.0, extend-shallow@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= + dependencies: + assign-symbols "^1.0.0" + is-extendable "^1.0.1" + +extglob@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" + integrity sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE= + dependencies: + is-extglob "^1.0.0" + +extglob@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== + dependencies: + array-unique "^0.3.2" + define-property "^1.0.0" + expand-brackets "^2.1.4" + extend-shallow "^2.0.1" + fragment-cache "^0.2.1" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +filename-regex@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" + integrity sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY= + +fill-range@^2.1.0: + version "2.2.4" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.4.tgz#eb1e773abb056dcd8df2bfdf6af59b8b3a936565" + integrity sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q== + dependencies: + is-number "^2.1.0" + isobject "^2.0.0" + randomatic "^3.0.0" + repeat-element "^1.1.2" + repeat-string "^1.5.2" + +fill-range@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= + dependencies: + extend-shallow "^2.0.1" + is-number "^3.0.0" + repeat-string "^1.6.1" + to-regex-range "^2.1.0" + +for-in@^1.0.1, for-in@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= + +for-own@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" + integrity sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4= + dependencies: + for-in "^1.0.1" + +fragment-cache@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= + dependencies: + map-cache "^0.2.2" + +fs-minipass@^1.2.5: + version "1.2.5" + resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" + integrity sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ== + dependencies: + minipass "^2.2.1" + +fs-readdir-recursive@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz#e32fc030a2ccee44a6b5371308da54be0b397d27" + integrity sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@^1.0.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426" + integrity sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg== + dependencies: + nan "^2.9.2" + node-pre-gyp "^0.10.0" + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +get-value@^2.0.3, get-value@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= + +glob-base@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" + integrity sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q= + dependencies: + glob-parent "^2.0.0" + is-glob "^2.0.0" + +glob-parent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" + integrity sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg= + dependencies: + is-glob "^2.0.0" + +glob@^7.0.5, glob@^7.1.2: + version "7.1.3" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^9.18.0: + version "9.18.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" + integrity sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ== + +graceful-fs@^4.1.11, graceful-fs@^4.1.4: + version "4.1.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" + integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg= + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= + dependencies: + ansi-regex "^2.0.0" + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= + +has-value@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= + dependencies: + get-value "^2.0.3" + has-values "^0.1.4" + isobject "^2.0.0" + +has-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= + dependencies: + get-value "^2.0.6" + has-values "^1.0.0" + isobject "^3.0.0" + +has-values@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= + +has-values@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +home-or-tmp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" + integrity sha1-42w/LSyufXRqhX440Y1fMqeILbg= + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.1" + +iconv-lite@^0.4.4: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + +ignore-walk@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" + integrity sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ== + dependencies: + minimatch "^3.0.4" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.1, inherits@~2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= + +ini@~1.3.0: + version "1.3.5" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== + +invariant@^2.2.2: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + +is-accessor-descriptor@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= + dependencies: + kind-of "^3.0.2" + +is-accessor-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== + dependencies: + kind-of "^6.0.0" + +is-binary-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= + dependencies: + binary-extensions "^1.0.0" + +is-buffer@^1.1.5: + version "1.1.6" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== + +is-data-descriptor@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= + dependencies: + kind-of "^3.0.2" + +is-data-descriptor@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== + dependencies: + kind-of "^6.0.0" + +is-descriptor@^0.1.0: + version "0.1.6" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== + dependencies: + is-accessor-descriptor "^0.1.6" + is-data-descriptor "^0.1.4" + kind-of "^5.0.0" + +is-descriptor@^1.0.0, is-descriptor@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== + dependencies: + is-accessor-descriptor "^1.0.0" + is-data-descriptor "^1.0.0" + kind-of "^6.0.2" + +is-dotfile@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" + integrity sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE= + +is-equal-shallow@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" + integrity sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ= + dependencies: + is-primitive "^2.0.0" + +is-extendable@^0.1.0, is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= + +is-extendable@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== + dependencies: + is-plain-object "^2.0.4" + +is-extglob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" + integrity sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA= + +is-finite@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" + integrity sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko= + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= + +is-glob@^2.0.0, is-glob@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" + integrity sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM= + dependencies: + is-extglob "^1.0.0" + +is-number@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" + integrity sha1-Afy7s5NGOlSPL0ZszhbezknbkI8= + dependencies: + kind-of "^3.0.2" + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= + dependencies: + kind-of "^3.0.2" + +is-number@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-4.0.0.tgz#0026e37f5454d73e356dfe6564699867c6a7f0ff" + integrity sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ== + +is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +is-posix-bracket@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" + integrity sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q= + +is-primitive@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" + integrity sha1-IHurkWOEmcB7Kt8kCkGochADRXU= + +is-windows@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== + +isarray@1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= + dependencies: + isarray "1.0.0" + +isobject@^3.0.0, isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= + +"js-tokens@^3.0.0 || ^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +js-tokens@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + integrity sha1-mGbfOVECEw449/mWvOtlRDIJwls= + +jsesc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" + integrity sha1-RsP+yMGJKxKwgz25vHYiF226s0s= + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= + +json5@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" + integrity sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE= + +kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= + dependencies: + is-buffer "^1.1.5" + +kind-of@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== + +kind-of@^6.0.0, kind-of@^6.0.2: + version "6.0.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" + integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== + +lodash@4.17.4: + version "4.17.4" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" + integrity sha1-eCA6TRwyiuHYbcpkYONptX9AVa4= + +lodash@^4.17.4: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== + +"lodash@npm:@elastic/lodash@3.10.1-kibana1": + version "3.10.1-kibana1" + resolved "https://registry.yarnpkg.com/@elastic/lodash/-/lodash-3.10.1-kibana1.tgz#9baa52c296ec06a52461f8516bb94172ef53a7fe" + integrity sha512-JIL1V6Kd9mhm7OCO5nmpMF8hW2iw1godcXgBikhIlTvsu6hBA8Xnxfc/AukdysTfJo4zWaZ8PzlJX/hKN/DlDA== + +loose-envify@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== + dependencies: + js-tokens "^3.0.0 || ^4.0.0" + +map-cache@^0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= + +map-visit@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= + dependencies: + object-visit "^1.0.0" + +math-random@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/math-random/-/math-random-1.0.1.tgz#8b3aac588b8a66e4975e3cdea67f7bb329601fac" + integrity sha1-izqsWIuKZuSXXjzepn97sylgH6w= + +micromatch@^2.1.5: + version "2.3.11" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" + integrity sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU= + dependencies: + arr-diff "^2.0.0" + array-unique "^0.2.1" + braces "^1.8.2" + expand-brackets "^0.1.4" + extglob "^0.3.1" + filename-regex "^2.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.1" + kind-of "^3.0.2" + normalize-path "^2.0.1" + object.omit "^2.0.0" + parse-glob "^3.0.4" + regex-cache "^0.4.2" + +micromatch@^3.1.10: + version "3.1.10" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + braces "^2.3.1" + define-property "^2.0.2" + extend-shallow "^3.0.2" + extglob "^2.0.4" + fragment-cache "^0.2.1" + kind-of "^6.0.2" + nanomatch "^1.2.9" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.2" + +minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= + +minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= + +minipass@^2.2.1, minipass@^2.3.3: + version "2.3.4" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.4.tgz#4768d7605ed6194d6d576169b9e12ef71e9d9957" + integrity sha512-mlouk1OHlaUE8Odt1drMtG1bAJA4ZA6B/ehysgV0LUIrDHdKgo1KorZq3pK0b/7Z7LJIQ12MNM6aC+Tn6lUZ5w== + dependencies: + safe-buffer "^5.1.2" + yallist "^3.0.0" + +minizlib@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.1.tgz#6734acc045a46e61d596a43bb9d9cd326e19cc42" + integrity sha512-TrfjCjk4jLhcJyGMYymBH6oTXcWjYbUAXTHDbtnWHjZC25h0cdajHuPE1zxb4DVmu8crfh+HwH/WMuyLG0nHBg== + dependencies: + minipass "^2.2.1" + +mixin-deep@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" + integrity sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ== + dependencies: + for-in "^1.0.2" + is-extendable "^1.0.1" + +mkdirp@^0.5.0, mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + dependencies: + minimist "0.0.8" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= + +nan@^2.9.2: + version "2.11.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.1.tgz#90e22bccb8ca57ea4cd37cc83d3819b52eea6766" + integrity sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA== + +nanomatch@^1.2.9: + version "1.2.13" + resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== + dependencies: + arr-diff "^4.0.0" + array-unique "^0.3.2" + define-property "^2.0.2" + extend-shallow "^3.0.2" + fragment-cache "^0.2.1" + is-windows "^1.0.2" + kind-of "^6.0.2" + object.pick "^1.3.0" + regex-not "^1.0.0" + snapdragon "^0.8.1" + to-regex "^3.0.1" + +needle@^2.2.1: + version "2.2.4" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.4.tgz#51931bff82533b1928b7d1d69e01f1b00ffd2a4e" + integrity sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA== + dependencies: + debug "^2.1.2" + iconv-lite "^0.4.4" + sax "^1.2.4" + +node-pre-gyp@^0.10.0: + version "0.10.3" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc" + integrity sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A== + dependencies: + detect-libc "^1.0.2" + mkdirp "^0.5.1" + needle "^2.2.1" + nopt "^4.0.1" + npm-packlist "^1.1.6" + npmlog "^4.0.2" + rc "^1.2.7" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^4" + +nopt@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" + integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= + dependencies: + abbrev "1" + osenv "^0.1.4" + +normalize-path@^2.0.0, normalize-path@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= + dependencies: + remove-trailing-separator "^1.0.1" + +npm-bundled@^1.0.1: + version "1.0.5" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.5.tgz#3c1732b7ba936b3a10325aef616467c0ccbcc979" + integrity sha512-m/e6jgWu8/v5niCUKQi9qQl8QdeEduFA96xHDDzFGqly0OOjI7c+60KM/2sppfnUU9JJagf+zs+yGhqSOFj71g== + +npm-packlist@^1.1.6: + version "1.1.12" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.12.tgz#22bde2ebc12e72ca482abd67afc51eb49377243a" + integrity sha512-WJKFOVMeAlsU/pjXuqVdzU0WfgtIBCupkEVwn+1Y0ERAbUfWw8R4GjgVbaKnUjRoD2FoQbHOCbOyT5Mbs9Lw4g== + dependencies: + ignore-walk "^3.0.1" + npm-bundled "^1.0.1" + +npmlog@^4.0.2: + version "4.1.2" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= + +object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= + +object-copy@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= + dependencies: + copy-descriptor "^0.1.0" + define-property "^0.2.5" + kind-of "^3.0.3" + +object-visit@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= + dependencies: + isobject "^3.0.0" + +object.omit@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" + integrity sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo= + dependencies: + for-own "^0.1.4" + is-extendable "^0.1.1" + +object.pick@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= + dependencies: + isobject "^3.0.1" + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= + +os-tmpdir@^1.0.0, os-tmpdir@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= + +osenv@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + +output-file-sync@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/output-file-sync/-/output-file-sync-1.1.2.tgz#d0a33eefe61a205facb90092e826598d5245ce76" + integrity sha1-0KM+7+YaIF+suQCS6CZZjVJFznY= + dependencies: + graceful-fs "^4.1.4" + mkdirp "^0.5.1" + object-assign "^4.1.0" + +parse-glob@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" + integrity sha1-ssN2z7EfNVE7rdFz7wu246OIORw= + dependencies: + glob-base "^0.3.0" + is-dotfile "^1.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.0" + +pascalcase@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= + +path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +posix-character-classes@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= + +preserve@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" + integrity sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks= + +private@^0.1.6, private@^0.1.8: + version "0.1.8" + resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" + integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== + +process-nextick-args@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" + integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== + +randomatic@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-3.1.0.tgz#36f2ca708e9e567f5ed2ec01949026d50aa10116" + integrity sha512-KnGPVE0lo2WoXxIZ7cPR8YBpiol4gsSuOwDSg410oHh80ZMp5EiypNqL2K4Z77vJn6lB5rap7IkAmcUlalcnBQ== + dependencies: + is-number "^4.0.0" + kind-of "^6.0.0" + math-random "^1.0.1" + +rc@^1.2.7: + version "1.2.8" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== + dependencies: + deep-extend "^0.6.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +readable-stream@^2.0.2, readable-stream@^2.0.6: + version "2.3.6" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" + integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.3" + isarray "~1.0.0" + process-nextick-args "~2.0.0" + safe-buffer "~5.1.1" + string_decoder "~1.1.1" + util-deprecate "~1.0.1" + +readdirp@^2.0.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" + integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== + dependencies: + graceful-fs "^4.1.11" + micromatch "^3.1.10" + readable-stream "^2.0.2" + +regenerate@^1.2.1: + version "1.4.0" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" + integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== + +regenerator-runtime@^0.10.5: + version "0.10.5" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" + integrity sha1-M2w+/BIgrc7dosn6tntaeVWjNlg= + +regenerator-runtime@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" + integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== + +regenerator-transform@^0.10.0: + version "0.10.1" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd" + integrity sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q== + dependencies: + babel-runtime "^6.18.0" + babel-types "^6.19.0" + private "^0.1.6" + +regex-cache@^0.4.2: + version "0.4.4" + resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd" + integrity sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ== + dependencies: + is-equal-shallow "^0.1.3" + +regex-not@^1.0.0, regex-not@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== + dependencies: + extend-shallow "^3.0.2" + safe-regex "^1.1.0" + +regexpu-core@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" + integrity sha1-SdA4g3uNz4v6W5pCE5k45uoq4kA= + dependencies: + regenerate "^1.2.1" + regjsgen "^0.2.0" + regjsparser "^0.1.4" + +regjsgen@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" + integrity sha1-bAFq3qxVT3WCP+N6wFuS1aTtsfc= + +regjsparser@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" + integrity sha1-fuj4Tcb6eS0/0K4ijSS9lJ6tIFw= + dependencies: + jsesc "~0.5.0" + +remove-trailing-separator@^1.0.1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= + +repeat-element@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== + +repeat-string@^1.5.2, repeat-string@^1.6.1: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= + +repeating@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= + dependencies: + is-finite "^1.0.0" + +resolve-url@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= + +ret@~0.1.10: + version "0.1.15" + resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== + +rimraf@^2.6.1: + version "2.6.2" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" + integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w== + dependencies: + glob "^7.0.5" + +safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: + version "5.1.2" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== + +safe-regex@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= + dependencies: + ret "~0.1.10" + +"safer-buffer@>= 2.1.2 < 3": + version "2.1.2" + resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== + +sax@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + +semver@^5.3.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" + integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== + +set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= + +set-value@^0.4.3: + version "0.4.3" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1" + integrity sha1-fbCPnT0i3H945Trzw79GZuzfzPE= + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.1" + to-object-path "^0.3.0" + +set-value@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.0.tgz#71ae4a88f0feefbbf52d1ea604f3fb315ebb6274" + integrity sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg== + dependencies: + extend-shallow "^2.0.1" + is-extendable "^0.1.1" + is-plain-object "^2.0.3" + split-string "^3.0.1" + +signal-exit@^3.0.0: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= + +slash@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" + integrity sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU= + +snapdragon-node@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== + dependencies: + define-property "^1.0.0" + isobject "^3.0.0" + snapdragon-util "^3.0.1" + +snapdragon-util@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== + dependencies: + kind-of "^3.2.0" + +snapdragon@^0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== + dependencies: + base "^0.11.1" + debug "^2.2.0" + define-property "^0.2.5" + extend-shallow "^2.0.1" + map-cache "^0.2.2" + source-map "^0.5.6" + source-map-resolve "^0.5.0" + use "^3.1.0" + +source-map-resolve@^0.5.0: + version "0.5.2" + resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" + integrity sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA== + dependencies: + atob "^2.1.1" + decode-uri-component "^0.2.0" + resolve-url "^0.2.1" + source-map-url "^0.4.0" + urix "^0.1.0" + +source-map-support@^0.4.15: + version "0.4.18" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" + integrity sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA== + dependencies: + source-map "^0.5.6" + +source-map-url@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" + integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= + +source-map@^0.5.6, source-map@^0.5.7: + version "0.5.7" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + +split-string@^3.0.1, split-string@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== + dependencies: + extend-shallow "^3.0.0" + +static-extend@^0.1.1: + version "0.1.2" + resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= + dependencies: + define-property "^0.2.5" + object-copy "^0.1.0" + +string-width@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +"string-width@^1.0.2 || 2": + version "2.1.1" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^4.0.0" + +string_decoder@~1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== + dependencies: + safe-buffer "~5.1.0" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= + dependencies: + ansi-regex "^2.0.0" + +strip-ansi@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= + dependencies: + ansi-regex "^3.0.0" + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= + +tar@^4: + version "4.4.6" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.6.tgz#63110f09c00b4e60ac8bcfe1bf3c8660235fbc9b" + integrity sha512-tMkTnh9EdzxyfW+6GK6fCahagXsnYk6kE6S9Gr9pjVdys769+laCTbodXDhPAjzVtEBazRgP0gYqOjnk9dQzLg== + dependencies: + chownr "^1.0.1" + fs-minipass "^1.2.5" + minipass "^2.3.3" + minizlib "^1.1.0" + mkdirp "^0.5.0" + safe-buffer "^5.1.2" + yallist "^3.0.2" + +to-fast-properties@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" + integrity sha1-uDVx+k2MJbguIxsG46MFXeTKGkc= + +to-object-path@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= + dependencies: + kind-of "^3.0.2" + +to-regex-range@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= + dependencies: + is-number "^3.0.0" + repeat-string "^1.6.1" + +to-regex@^3.0.1, to-regex@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== + dependencies: + define-property "^2.0.2" + extend-shallow "^3.0.2" + regex-not "^1.0.2" + safe-regex "^1.1.0" + +traverse@0.6.6: + version "0.6.6" + resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.6.6.tgz#cbdf560fd7b9af632502fed40f918c157ea97137" + integrity sha1-y99WD9e5r2MlAv7UD5GMFX6pcTc= + +trim-right@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" + integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= + +union-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" + integrity sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ= + dependencies: + arr-union "^3.1.0" + get-value "^2.0.6" + is-extendable "^0.1.1" + set-value "^0.4.3" + +unset-value@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= + dependencies: + has-value "^0.3.1" + isobject "^3.0.0" + +urix@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" + integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= + +use@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" + integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== + +user-home@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190" + integrity sha1-K1viOjK2Onyd640PKNSFcko98ZA= + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +v8flags@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4" + integrity sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ= + dependencies: + user-home "^1.1.1" + +wide-align@^1.1.0: + version "1.1.3" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== + dependencies: + string-width "^1.0.2 || 2" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +yallist@^3.0.0, yallist@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9" + integrity sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k= diff --git a/packages/kbn-es/src/cli_commands/snapshot.js b/packages/kbn-es/src/cli_commands/snapshot.js index 0a585e92cfd74..c01a4fa08ec59 100644 --- a/packages/kbn-es/src/cli_commands/snapshot.js +++ b/packages/kbn-es/src/cli_commands/snapshot.js @@ -35,6 +35,7 @@ exports.help = (defaults = {}) => { --install-path Installation path, defaults to 'source' within base-path --password Sets password for elastic user [default: ${password}] -E Additional key=value settings to pass to Elasticsearch + --download-only Download the snapshot but don't actually start it Example: @@ -51,10 +52,16 @@ exports.run = async (defaults = {}) => { esArgs: 'E', }, + boolean: ['download-only'], + default: defaults, }); const cluster = new Cluster(); - const { installPath } = await cluster.installSnapshot(options); - await cluster.run(installPath, { esArgs: options.esArgs }); + if (options['download-only']) { + await cluster.downloadSnapshot(options); + } else { + const { installPath } = await cluster.installSnapshot(options); + await cluster.run(installPath, { esArgs: options.esArgs }); + } }; diff --git a/packages/kbn-es/src/cluster.js b/packages/kbn-es/src/cluster.js index f14be3582f71a..50f3c5db2d8ca 100644 --- a/packages/kbn-es/src/cluster.js +++ b/packages/kbn-es/src/cluster.js @@ -19,7 +19,7 @@ const execa = require('execa'); const chalk = require('chalk'); -const { installSnapshot, installSource, installArchive } = require('./install'); +const { downloadSnapshot, installSnapshot, installSource, installArchive } = require('./install'); const { ES_BIN } = require('./paths'); const { log: defaultLog, parseEsLog, extractConfigFiles } = require('./utils'); const { createCliError } = require('./errors'); @@ -50,6 +50,28 @@ exports.Cluster = class Cluster { return { installPath }; } + /** + * Download ES from a snapshot + * + * @param {Object} options + * @property {Array} options.installPath + * @property {Array} options.sourcePath + * @returns {Promise<{installPath}>} + */ + async downloadSnapshot(options = {}) { + this._log.info(chalk.bold('Downloading snapshot')); + this._log.indent(4); + + const { installPath } = await downloadSnapshot({ + log: this._log, + ...options, + }); + + this._log.indent(-4); + + return { installPath }; + } + /** * Download and installs ES from a snapshot * diff --git a/packages/kbn-es/src/install/index.js b/packages/kbn-es/src/install/index.js index 69de1004e4e72..e2b3f692b2203 100644 --- a/packages/kbn-es/src/install/index.js +++ b/packages/kbn-es/src/install/index.js @@ -19,4 +19,5 @@ exports.installArchive = require('./archive').installArchive; exports.installSnapshot = require('./snapshot').installSnapshot; +exports.downloadSnapshot = require('./snapshot').downloadSnapshot; exports.installSource = require('./source').installSource; diff --git a/packages/kbn-es/src/install/snapshot.js b/packages/kbn-es/src/install/snapshot.js index de1a635b7f9f6..42c1307a04614 100644 --- a/packages/kbn-es/src/install/snapshot.js +++ b/packages/kbn-es/src/install/snapshot.js @@ -28,26 +28,23 @@ const { installArchive } = require('./archive'); const { log: defaultLog, cache } = require('../utils'); /** - * Installs ES from snapshot + * Download an ES snapshot * * @param {Object} options * @property {('oss'|'basic'|'trial')} options.license - * @property {String} options.password * @property {String} options.version * @property {String} options.basePath * @property {String} options.installPath * @property {ToolingLog} options.log */ -exports.installSnapshot = async function installSnapshot({ +exports.downloadSnapshot = async function installSnapshot({ license = 'basic', - password = 'password', version, basePath = BASE_PATH, installPath = path.resolve(basePath, version), log = defaultLog, }) { - // TODO: remove -alpha1 once elastic/elasticsearch#35172 has been merged - const fileName = getFilename(license, version + '-alpha1'); + const fileName = getFilename(license, version); const url = `https://snapshots.elastic.co/downloads/elasticsearch/${fileName}`; const dest = path.resolve(basePath, 'cache', fileName); @@ -56,7 +53,40 @@ exports.installSnapshot = async function installSnapshot({ log.info('license: %s', chalk.bold(license)); await downloadFile(url, dest, log); - return await installArchive(dest, { + + return { + downloadPath: dest, + }; +}; + +/** + * Installs ES from snapshot + * + * @param {Object} options + * @property {('oss'|'basic'|'trial')} options.license + * @property {String} options.password + * @property {String} options.version + * @property {String} options.basePath + * @property {String} options.installPath + * @property {ToolingLog} options.log + */ +exports.installSnapshot = async function installSnapshot({ + license = 'basic', + password = 'password', + version, + basePath = BASE_PATH, + installPath = path.resolve(basePath, version), + log = defaultLog, +}) { + const { downloadPath } = await exports.downloadSnapshot({ + license, + version, + basePath, + installPath, + log, + }); + + return await installArchive(downloadPath, { license, password, basePath, diff --git a/packages/kbn-i18n/package.json b/packages/kbn-i18n/package.json index 7ef8d10c3a916..f2350f958c023 100644 --- a/packages/kbn-i18n/package.json +++ b/packages/kbn-i18n/package.json @@ -7,12 +7,12 @@ "license": "Apache-2.0", "private": true, "scripts": { - "build": "yarn build:web && yarn build:node && yarn build:types", - "build:types": "tsc --emitDeclarationOnly", - "build:web": "cross-env BABEL_ENV=web babel src --config-file ./babel.config.js --out-dir target/web --extensions \".ts,.js,.tsx\"", - "build:node": "cross-env BABEL_ENV=node babel src --config-file ./babel.config.js --out-dir target/node --extensions \".ts,.js,.tsx\"", - "kbn:bootstrap": "yarn build", - "kbn:watch": "yarn build --watch" + "build": "run-p build:**", + "kbn:bootstrap": "run-p \"build:babel:** --quiet\" build:tsc", + "kbn:watch": "run-p \"build:** --watch\"", + "build:tsc": "tsc --emitDeclarationOnly", + "build:babel:web": "cross-env BABEL_ENV=web babel src --config-file ./babel.config.js --out-dir target/web --extensions \".ts,.js,.tsx\"", + "build:babel:node": "cross-env BABEL_ENV=node babel src --config-file ./babel.config.js --out-dir target/node --extensions \".ts,.js,.tsx\"" }, "devDependencies": { "@babel/cli": "^7.1.0", @@ -26,6 +26,7 @@ "@types/json5": "^0.0.30", "@types/react-intl": "^2.3.11", "cross-env": "^5.2.0", + "npm-run-all": "^4.1.3", "typescript": "^3.0.3" }, "dependencies": { diff --git a/packages/kbn-interpreter/.babelrc b/packages/kbn-interpreter/.babelrc new file mode 100644 index 0000000000000..57a5cc3669eb4 --- /dev/null +++ b/packages/kbn-interpreter/.babelrc @@ -0,0 +1,9 @@ +{ + "presets": ["@kbn/babel-preset/webpack_preset"], + "plugins": [ + ["babel-plugin-transform-runtime", { + "polyfill": false, + "regenerator": true + }] + ] +} diff --git a/packages/kbn-interpreter/.npmignore b/packages/kbn-interpreter/.npmignore new file mode 100644 index 0000000000000..b9bc539e63ce4 --- /dev/null +++ b/packages/kbn-interpreter/.npmignore @@ -0,0 +1,3 @@ +src +tasks +.babelrc diff --git a/packages/kbn-interpreter/common/package.json b/packages/kbn-interpreter/common/package.json new file mode 100644 index 0000000000000..1324e51a661cd --- /dev/null +++ b/packages/kbn-interpreter/common/package.json @@ -0,0 +1,5 @@ +{ + "private": true, + "main": "../target/common/index.js", + "jsnext:main": "../src/common/index.js" +} diff --git a/packages/kbn-interpreter/package.json b/packages/kbn-interpreter/package.json new file mode 100644 index 0000000000000..3c7c1e47a74d7 --- /dev/null +++ b/packages/kbn-interpreter/package.json @@ -0,0 +1,36 @@ +{ + "name": "@kbn/interpreter", + "version": "1.0.0", + "license": "Apache-2.0", + "scripts": { + "canvas:peg": "pegjs common/lib/grammar.peg", + "build": "node scripts/build", + "kbn:bootstrap": "node scripts/build --dev", + "kbn:watch": "node scripts/build --dev --watch" + }, + "dependencies": { + "lodash": "npm:@elastic/lodash@3.10.1-kibana1", + "lodash.clone": "^4.5.0", + "scriptjs": "^2.5.8", + "socket.io-client": "^2.1.1", + "uuid": "3.0.1" + }, + "devDependencies": { + "@kbn/babel-preset": "1.0.0", + "@kbn/dev-utils": "1.0.0", + "babel-cli": "^6.26.0", + "babel-core": "^6.26.0", + "babel-loader": "7.1.2", + "babel-plugin-transform-runtime": "^6.23.0", + "babel-polyfill": "6.20.0", + "css-loader": "0.28.7", + "del": "^3.0.0", + "getopts": "^2.2.3", + "pegjs": "0.9.0", + "sass-loader": "^7.1.0", + "style-loader": "0.19.0", + "supports-color": "^5.5.0", + "url-loader": "0.5.9", + "webpack": "3.6.0" + } +} diff --git a/packages/kbn-interpreter/plugin_paths.js b/packages/kbn-interpreter/plugin_paths.js new file mode 100644 index 0000000000000..af05a6c3cfba4 --- /dev/null +++ b/packages/kbn-interpreter/plugin_paths.js @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const { resolve } = require('path'); + +exports.pluginPaths = { + commonFunctions: resolve(__dirname, 'target/plugin/functions/common'), + types: resolve(__dirname, 'target/plugin/types'), +}; diff --git a/packages/kbn-interpreter/public/package.json b/packages/kbn-interpreter/public/package.json new file mode 100644 index 0000000000000..3aae7e53c3eaa --- /dev/null +++ b/packages/kbn-interpreter/public/package.json @@ -0,0 +1,5 @@ +{ + "private": true, + "main": "../target/public/index.js", + "jsnext:main": "../src/public/index.js" +} diff --git a/packages/kbn-interpreter/scripts/build.js b/packages/kbn-interpreter/scripts/build.js new file mode 100644 index 0000000000000..23e6e42a8e78a --- /dev/null +++ b/packages/kbn-interpreter/scripts/build.js @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +require('../tasks/build/cli'); diff --git a/packages/kbn-interpreter/server/package.json b/packages/kbn-interpreter/server/package.json new file mode 100644 index 0000000000000..2f4ed7dae00d4 --- /dev/null +++ b/packages/kbn-interpreter/server/package.json @@ -0,0 +1,5 @@ +{ + "private": true, + "main": "../target/server/index.js", + "jsnext:main": "../src/server/index.js" +} diff --git a/packages/kbn-interpreter/src/common/index.js b/packages/kbn-interpreter/src/common/index.js new file mode 100644 index 0000000000000..886106d39f4e1 --- /dev/null +++ b/packages/kbn-interpreter/src/common/index.js @@ -0,0 +1,32 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { pathsRegistry } from './lib/paths_registry'; +export { functionsRegistry } from './lib/functions_registry'; +export { typesRegistry } from './lib/types_registry'; +export { createError } from './interpreter/create_error'; +export { interpretProvider } from './interpreter/interpret'; +export { serializeProvider } from './lib/serialize'; +export { fromExpression, toExpression, safeElementFromExpression } from './lib/ast'; +export { Fn } from './lib/fn'; +export { getType } from './lib/get_type'; +export { castProvider } from './interpreter/cast'; +export { parse } from './lib/grammar'; +export { getByAlias } from './lib/get_by_alias'; +export { Registry } from './lib/registry'; diff --git a/x-pack/plugins/canvas/common/interpreter/cast.js b/packages/kbn-interpreter/src/common/interpreter/cast.js similarity index 53% rename from x-pack/plugins/canvas/common/interpreter/cast.js rename to packages/kbn-interpreter/src/common/interpreter/cast.js index 7e559afcba40e..cc257a7dc55e0 100644 --- a/x-pack/plugins/canvas/common/interpreter/cast.js +++ b/packages/kbn-interpreter/src/common/interpreter/cast.js @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import { getType } from '../lib/get_type'; @@ -19,8 +32,9 @@ export function castProvider(types) { for (let i = 0; i < toTypeNames.length; i++) { // First check if the current type can cast to this type - if (fromTypeDef && fromTypeDef.castsTo(toTypeNames[i])) + if (fromTypeDef && fromTypeDef.castsTo(toTypeNames[i])) { return fromTypeDef.to(node, toTypeNames[i], types); + } // If that isn't possible, check if this type can cast from the current type const toTypeDef = types[toTypeNames[i]]; diff --git a/packages/kbn-interpreter/src/common/interpreter/create_error.js b/packages/kbn-interpreter/src/common/interpreter/create_error.js new file mode 100644 index 0000000000000..2740358b1c960 --- /dev/null +++ b/packages/kbn-interpreter/src/common/interpreter/create_error.js @@ -0,0 +1,26 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const createError = err => ({ + type: 'error', + error: { + stack: process.env.NODE_ENV === 'production' ? undefined : err.stack, + message: typeof err === 'string' ? err : err.message, + }, +}); diff --git a/x-pack/plugins/canvas/common/interpreter/interpret.js b/packages/kbn-interpreter/src/common/interpreter/interpret.js similarity index 88% rename from x-pack/plugins/canvas/common/interpreter/interpret.js rename to packages/kbn-interpreter/src/common/interpreter/interpret.js index ff7a2547f236f..d2a786cd3c85d 100644 --- a/x-pack/plugins/canvas/common/interpreter/interpret.js +++ b/packages/kbn-interpreter/src/common/interpreter/interpret.js @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import clone from 'lodash.clone'; @@ -112,8 +125,9 @@ export function interpretProvider(config) { (argAsts, argAst, argName) => { const argDef = getByAlias(argDefs, argName); // TODO: Implement a system to allow for undeclared arguments - if (!argDef) + if (!argDef) { throw new Error(`Unknown argument '${argName}' passed to function '${fnDef.name}'`); + } argAsts[argDef.name] = (argAsts[argDef.name] || []).concat(argAst); return argAsts; @@ -142,8 +156,9 @@ export function interpretProvider(config) { const argAstsWithDefaults = reduce( argDefs, (argAsts, argDef, argName) => { - if (typeof argAsts[argName] === 'undefined' && typeof argDef.default !== 'undefined') + if (typeof argAsts[argName] === 'undefined' && typeof argDef.default !== 'undefined') { argAsts[argName] = [fromExpression(argDef.default, 'argument')]; + } return argAsts; }, diff --git a/x-pack/plugins/canvas/common/interpreter/socket_interpret.js b/packages/kbn-interpreter/src/common/interpreter/socket_interpret.js similarity index 72% rename from x-pack/plugins/canvas/common/interpreter/socket_interpret.js rename to packages/kbn-interpreter/src/common/interpreter/socket_interpret.js index c8d5acf4fdd52..1ea95e0f5f6f1 100644 --- a/x-pack/plugins/canvas/common/interpreter/socket_interpret.js +++ b/packages/kbn-interpreter/src/common/interpreter/socket_interpret.js @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import uuid from 'uuid/v4'; @@ -40,8 +53,9 @@ export function socketInterpreterProvider({ // Get the list of functions that are known elsewhere return Promise.resolve(referableFunctions).then(referableFunctionMap => { // Check if the not-found function is in the list of alternatives, if not, throw - if (!getByAlias(referableFunctionMap, functionName)) + if (!getByAlias(referableFunctionMap, functionName)) { throw new Error(`Function not found: ${functionName}`); + } // set a unique message ID so the code knows what response to process const id = uuid(); diff --git a/packages/kbn-interpreter/src/common/lib/arg.js b/packages/kbn-interpreter/src/common/lib/arg.js new file mode 100644 index 0000000000000..0aa2b52e35acb --- /dev/null +++ b/packages/kbn-interpreter/src/common/lib/arg.js @@ -0,0 +1,37 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { includes } from 'lodash'; + +export function Arg(config) { + if (config.name === '_') throw Error('Arg names must not be _. Use it in aliases instead.'); + this.name = config.name; + this.required = config.required || false; + this.help = config.help || ''; + this.types = config.types || []; + this.default = config.default; + this.aliases = config.aliases || []; + this.multi = config.multi == null ? false : config.multi; + this.resolve = config.resolve == null ? true : config.resolve; + this.options = config.options || []; + this.accepts = type => { + if (!this.types.length) return true; + return includes(config.types, type); + }; +} diff --git a/packages/kbn-interpreter/src/common/lib/arg.test.js b/packages/kbn-interpreter/src/common/lib/arg.test.js new file mode 100644 index 0000000000000..2edd65cd4af49 --- /dev/null +++ b/packages/kbn-interpreter/src/common/lib/arg.test.js @@ -0,0 +1,35 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Arg } from './arg'; + +describe('Arg', () => { + it('sets required to false by default', () => { + const isOptional = new Arg({ + name: 'optional_me', + }); + expect(isOptional.required).toBe(false); + + const isRequired = new Arg({ + name: 'require_me', + required: true, + }); + expect(isRequired.required).toBe(true); + }); +}); diff --git a/x-pack/plugins/canvas/common/lib/__tests__/ast.from_expression.js b/packages/kbn-interpreter/src/common/lib/ast.from_expression.test.js similarity index 59% rename from x-pack/plugins/canvas/common/lib/__tests__/ast.from_expression.js rename to packages/kbn-interpreter/src/common/lib/ast.from_expression.test.js index 631973247dc6c..c144770f94c54 100644 --- a/x-pack/plugins/canvas/common/lib/__tests__/ast.from_expression.js +++ b/packages/kbn-interpreter/src/common/lib/ast.from_expression.test.js @@ -1,35 +1,47 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ -import expect from 'expect.js'; -import { fromExpression } from '../ast'; -import { getType } from '../../lib/get_type'; +import { fromExpression } from './ast'; +import { getType } from './get_type'; describe('ast fromExpression', () => { describe('invalid expression', () => { it('throws when empty', () => { const check = () => fromExpression(''); - expect(check).to.throwException(/Unable to parse expression/i); + expect(check).toThrowError(/Unable to parse expression/i); }); it('throws with invalid expression', () => { const check = () => fromExpression('wat!'); - expect(check).to.throwException(/Unable to parse expression/i); + expect(check).toThrowError(/Unable to parse expression/i); }); }); describe('single item expression', () => { it('is a chain', () => { const expression = 'whatever'; - expect(fromExpression(expression)).to.have.property('chain'); + expect(fromExpression(expression)).toHaveProperty('chain'); }); it('is a value', () => { const expression = '"hello"'; - expect(fromExpression(expression, 'argument')).to.equal('hello'); + expect(fromExpression(expression, 'argument')).toBe('hello'); }); describe('function without arguments', () => { @@ -44,15 +56,15 @@ describe('ast fromExpression', () => { }); it('is a function ', () => { - expect(getType(block)).to.equal('function'); + expect(getType(block)).toBe('function'); }); it('is csv function', () => { - expect(block.function).to.equal('csv'); + expect(block.function).toBe('csv'); }); it('has no arguments', () => { - expect(block.arguments).to.eql({}); + expect(block.arguments).toEqual({}); }); }); @@ -68,17 +80,17 @@ describe('ast fromExpression', () => { }); it('has arguemnts properties', () => { - expect(block.arguments).not.to.eql({}); + expect(block.arguments).not.toEqual({}); }); it('has index argument with string value', () => { - expect(block.arguments).to.have.property('index'); - expect(block.arguments.index).to.eql(['logstash-*']); + expect(block.arguments).toHaveProperty('index'); + expect(block.arguments.index).toEqual(['logstash-*']); }); it('has oranges argument with string value', () => { - expect(block.arguments).to.have.property('oranges'); - expect(block.arguments.oranges).to.eql(['bananas']); + expect(block.arguments).toHaveProperty('oranges'); + expect(block.arguments.oranges).toEqual(['bananas']); }); }); @@ -94,12 +106,12 @@ describe('ast fromExpression', () => { }); it('is expression type', () => { - expect(block.arguments).to.have.property('exampleFunction'); - expect(block.arguments.exampleFunction[0]).to.have.property('type', 'expression'); + expect(block.arguments).toHaveProperty('exampleFunction'); + expect(block.arguments.exampleFunction[0]).toHaveProperty('type'); }); it('has expected shape', () => { - expect(block.arguments.exampleFunction).to.eql([ + expect(block.arguments.exampleFunction).toEqual([ { type: 'expression', chain: [ @@ -128,12 +140,12 @@ describe('ast fromExpression', () => { }); it('is expression type', () => { - expect(block.arguments).to.have.property('examplePartial'); - expect(block.arguments.examplePartial[0]).to.have.property('type', 'expression'); + expect(block.arguments).toHaveProperty('examplePartial'); + expect(block.arguments.examplePartial[0]).toHaveProperty('type'); }); it('has expected shape', () => { - expect(block.arguments.examplePartial).to.eql([ + expect(block.arguments.examplePartial).toEqual([ { type: 'expression', chain: [ diff --git a/x-pack/plugins/canvas/common/lib/ast.js b/packages/kbn-interpreter/src/common/lib/ast.js similarity index 81% rename from x-pack/plugins/canvas/common/lib/ast.js rename to packages/kbn-interpreter/src/common/lib/ast.js index b31848944e9db..61cfe94ac955c 100644 --- a/x-pack/plugins/canvas/common/lib/ast.js +++ b/packages/kbn-interpreter/src/common/lib/ast.js @@ -1,10 +1,23 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ -import { getType } from '../lib/get_type'; +import { getType } from './get_type'; import { parse } from './grammar'; function getArgumentString(arg, argKey, level = 0) { @@ -48,8 +61,9 @@ function getExpressionArgs(block, level = 0) { const lineLength = acc.split('\n').pop().length; // if arg values are too long, move it to the next line - if (level === 0 && lineLength + argString.length > MAX_LINE_LENGTH) + if (level === 0 && lineLength + argString.length > MAX_LINE_LENGTH) { return `${acc}\n ${argString}`; + } // append arg values to existing arg values if (lineLength > 0) return `${acc} ${argString}`; diff --git a/x-pack/plugins/canvas/common/lib/__tests__/ast.to_expression.js b/packages/kbn-interpreter/src/common/lib/ast.to_expression.test.js similarity index 83% rename from x-pack/plugins/canvas/common/lib/__tests__/ast.to_expression.js rename to packages/kbn-interpreter/src/common/lib/ast.to_expression.test.js index 4b5985832e6ab..455b12f583f30 100644 --- a/x-pack/plugins/canvas/common/lib/__tests__/ast.to_expression.js +++ b/packages/kbn-interpreter/src/common/lib/ast.to_expression.test.js @@ -1,18 +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. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ -import expect from 'expect.js'; -import { toExpression } from '../ast'; +import { toExpression } from './ast'; describe('ast toExpression', () => { describe('single expression', () => { + it('throws if no type included', () => { + const errMsg = 'Objects must have a type property'; + const astObject = { hello: 'world' }; + expect(() => toExpression(astObject)).toThrowError(errMsg); + }); + it('throws if not correct type', () => { const errMsg = 'Expression must be an expression or argument function'; - const astObject = { hello: 'world' }; - expect(() => toExpression(astObject)).to.throwException(errMsg); + const astObject = { + type: 'hi', + hello: 'world', + }; + expect(() => toExpression(astObject)).toThrowError(errMsg); }); it('throws if expression without chain', () => { @@ -21,7 +42,7 @@ describe('ast toExpression', () => { type: 'expression', hello: 'world', }; - expect(() => toExpression(astObject)).to.throwException(errMsg); + expect(() => toExpression(astObject)).toThrowError(errMsg); }); it('throws if arguments type is invalid', () => { @@ -29,7 +50,7 @@ describe('ast toExpression', () => { const invalidTypes = [null, []]; function validate(obj) { - expect(() => toExpression(obj)).to.throwException(errMsg); + expect(() => toExpression(obj)).toThrowError(errMsg); } for (let i = 0; i < invalidTypes.length; i++) { @@ -56,12 +77,12 @@ describe('ast toExpression', () => { function: 'pointseries', arguments: null, }; - expect(() => toExpression(astObject)).to.throwException(errMsg); + expect(() => toExpression(astObject)).toThrowError(errMsg); }); it('throws on invalid argument type', () => { const argType = '__invalid__wat__'; - const errMsg = `invalid argument type: ${argType}`; + const errMsg = `Invalid argument type in AST: ${argType}`; const astObject = { type: 'expression', chain: [ @@ -80,7 +101,7 @@ describe('ast toExpression', () => { ], }; - expect(() => toExpression(astObject)).to.throwException(errMsg); + expect(() => toExpression(astObject)).toThrowError(errMsg); }); it('throws on expressions without chains', () => { @@ -104,7 +125,7 @@ describe('ast toExpression', () => { ], }; - expect(() => toExpression(astObject)).to.throwException(errMsg); + expect(() => toExpression(astObject)).toThrowError(errMsg); }); it('throws on nameless functions and partials', () => { @@ -120,7 +141,7 @@ describe('ast toExpression', () => { ], }; - expect(() => toExpression(astObject)).to.throwException(errMsg); + expect(() => toExpression(astObject)).toThrowError(errMsg); }); it('single expression', () => { @@ -136,7 +157,7 @@ describe('ast toExpression', () => { }; const expression = toExpression(astObj); - expect(expression).to.equal('csv'); + expect(expression).toBe('csv'); }); it('single expression with string argument', () => { @@ -154,7 +175,7 @@ describe('ast toExpression', () => { }; const expression = toExpression(astObj); - expect(expression).to.equal('csv input="stuff\nthings"'); + expect(expression).toBe('csv input="stuff\nthings"'); }); it('single expression string value with a backslash', () => { @@ -172,7 +193,7 @@ describe('ast toExpression', () => { }; const expression = toExpression(astObj); - expect(expression).to.equal('csv input="slash \\\\\\\\ slash"'); + expect(expression).toBe('csv input="slash \\\\\\\\ slash"'); }); it('single expression string value with a double quote', () => { @@ -190,7 +211,7 @@ describe('ast toExpression', () => { }; const expression = toExpression(astObj); - expect(expression).to.equal('csv input="stuff\nthings\n\\"such\\""'); + expect(expression).toBe('csv input="stuff\nthings\n\\"such\\""'); }); it('single expression with number argument', () => { @@ -208,7 +229,7 @@ describe('ast toExpression', () => { }; const expression = toExpression(astObj); - expect(expression).to.equal('series input=1234'); + expect(expression).toBe('series input=1234'); }); it('single expression with boolean argument', () => { @@ -226,7 +247,7 @@ describe('ast toExpression', () => { }; const expression = toExpression(astObj); - expect(expression).to.equal('series input=true'); + expect(expression).toBe('series input=true'); }); it('single expression with null argument', () => { @@ -244,7 +265,7 @@ describe('ast toExpression', () => { }; const expression = toExpression(astObj); - expect(expression).to.equal('series input=null'); + expect(expression).toBe('series input=null'); }); it('single expression with multiple arguments', () => { @@ -263,7 +284,7 @@ describe('ast toExpression', () => { }; const expression = toExpression(astObj); - expect(expression).to.equal('csv input="stuff\nthings" separator="\\\\n"'); + expect(expression).toBe('csv input="stuff\nthings" separator="\\\\n"'); }); it('single expression with multiple and repeated arguments', () => { @@ -282,12 +303,12 @@ describe('ast toExpression', () => { }; const expression = toExpression(astObj); - expect(expression).to.equal( + expect(expression).toBe( 'csv input="stuff\nthings" input="more,things\nmore,stuff" separator="\\\\n"' ); }); - it('single expression with expression argument', () => { + it('single expression with `getcalc` expression argument', () => { const astObj = { type: 'expression', chain: [ @@ -314,10 +335,10 @@ describe('ast toExpression', () => { }; const expression = toExpression(astObj); - expect(expression).to.equal('csv calc={getcalc} input="stuff\nthings"'); + expect(expression).toBe('csv calc={getcalc} input="stuff\nthings"'); }); - it('single expression with expression argument', () => { + it('single expression with `partcalc` expression argument', () => { const astObj = { type: 'expression', chain: [ @@ -344,7 +365,7 @@ describe('ast toExpression', () => { }; const expression = toExpression(astObj); - expect(expression).to.equal('csv calc={partcalc} input="stuff\nthings"'); + expect(expression).toBe('csv calc={partcalc} input="stuff\nthings"'); }); it('single expression with expression arguments, with arguments', () => { @@ -390,7 +411,7 @@ describe('ast toExpression', () => { }; const expression = toExpression(astObj); - expect(expression).to.equal( + expect(expression).toBe( 'csv sep={partcalc type="comma"} input="stuff\nthings" break={setBreak type="newline"}' ); }); @@ -468,7 +489,7 @@ describe('ast toExpression', () => { '2016,honda,fit,15890,', '2016,honda,civic,18640"\n| line x={distinct f="year"} y={sum f="price"} colors={distinct f="model"}', ]; - expect(expression).to.equal(expected.join('\n')); + expect(expression).toBe(expected.join('\n')); }); it('three chained expressions', () => { @@ -563,7 +584,7 @@ describe('ast toExpression', () => { '2016,honda,civic,18640"\n| pointseries x={distinct f="year"} y={sum f="price"} ' + 'colors={distinct f="model"}\n| line pallette={getColorPallette name="elastic"}', ]; - expect(expression).to.equal(expected.join('\n')); + expect(expression).toBe(expected.join('\n')); }); }); @@ -583,7 +604,7 @@ describe('ast toExpression', () => { }; const expression = toExpression(astObj); - expect(expression).to.equal('list "one" "two" "three"'); + expect(expression).toBe('list "one" "two" "three"'); }); it('named and unnamed', () => { @@ -603,7 +624,7 @@ describe('ast toExpression', () => { }; const expression = toExpression(astObj); - expect(expression).to.equal('both named="example" another="item" "one" "two" "three"'); + expect(expression).toBe('both named="example" another="item" "one" "two" "three"'); }); }); }); diff --git a/x-pack/plugins/canvas/common/lib/fn.js b/packages/kbn-interpreter/src/common/lib/fn.js similarity index 54% rename from x-pack/plugins/canvas/common/lib/fn.js rename to packages/kbn-interpreter/src/common/lib/fn.js index 70948c76579b3..c6b2fcbe67799 100644 --- a/x-pack/plugins/canvas/common/lib/fn.js +++ b/packages/kbn-interpreter/src/common/lib/fn.js @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import { mapValues, includes } from 'lodash'; diff --git a/packages/kbn-interpreter/src/common/lib/functions_registry.js b/packages/kbn-interpreter/src/common/lib/functions_registry.js new file mode 100644 index 0000000000000..1c71707d84722 --- /dev/null +++ b/packages/kbn-interpreter/src/common/lib/functions_registry.js @@ -0,0 +1,29 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Registry } from './registry'; +import { Fn } from './fn'; + +class FunctionsRegistry extends Registry { + wrapper(obj) { + return new Fn(obj); + } +} + +export const functionsRegistry = new FunctionsRegistry(); diff --git a/packages/kbn-interpreter/src/common/lib/get_by_alias.js b/packages/kbn-interpreter/src/common/lib/get_by_alias.js new file mode 100644 index 0000000000000..d7bb1bbf9e79d --- /dev/null +++ b/packages/kbn-interpreter/src/common/lib/get_by_alias.js @@ -0,0 +1,33 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * This is used for looking up function/argument definitions. It looks through + * the given object/array for a case-insensitive match, which could be either the + * `name` itself, or something under the `aliases` property. + */ +export function getByAlias(specs, name) { + const lowerCaseName = name.toLowerCase(); + return Object.values(specs).find(({ name, aliases }) => { + if (name.toLowerCase() === lowerCaseName) return true; + return (aliases || []).some(alias => { + return alias.toLowerCase() === lowerCaseName; + }); + }); +} diff --git a/packages/kbn-interpreter/src/common/lib/get_by_alias.test.js b/packages/kbn-interpreter/src/common/lib/get_by_alias.test.js new file mode 100644 index 0000000000000..9cfc37fd8f304 --- /dev/null +++ b/packages/kbn-interpreter/src/common/lib/get_by_alias.test.js @@ -0,0 +1,86 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { getByAlias } from './get_by_alias'; + +describe('getByAlias', () => { + const fnsObject = { + foo: { name: 'foo', aliases: ['f'] }, + bar: { name: 'bar', aliases: ['b'] }, + }; + + const fnsArray = [{ name: 'foo', aliases: ['f'] }, { name: 'bar', aliases: ['b'] }]; + + it('returns the function by name', () => { + expect(getByAlias(fnsObject, 'foo')).toBe(fnsObject.foo); + expect(getByAlias(fnsObject, 'bar')).toBe(fnsObject.bar); + expect(getByAlias(fnsArray, 'foo')).toBe(fnsArray[0]); + expect(getByAlias(fnsArray, 'bar')).toBe(fnsArray[1]); + }); + + it('returns the function by alias', () => { + expect(getByAlias(fnsObject, 'f')).toBe(fnsObject.foo); + expect(getByAlias(fnsObject, 'b')).toBe(fnsObject.bar); + expect(getByAlias(fnsArray, 'f')).toBe(fnsArray[0]); + expect(getByAlias(fnsArray, 'b')).toBe(fnsArray[1]); + }); + + it('returns the function by case-insensitive name', () => { + expect(getByAlias(fnsObject, 'FOO')).toBe(fnsObject.foo); + expect(getByAlias(fnsObject, 'BAR')).toBe(fnsObject.bar); + expect(getByAlias(fnsArray, 'FOO')).toBe(fnsArray[0]); + expect(getByAlias(fnsArray, 'BAR')).toBe(fnsArray[1]); + }); + + it('returns the function by case-insensitive alias', () => { + expect(getByAlias(fnsObject, 'F')).toBe(fnsObject.foo); + expect(getByAlias(fnsObject, 'B')).toBe(fnsObject.bar); + expect(getByAlias(fnsArray, 'F')).toBe(fnsArray[0]); + expect(getByAlias(fnsArray, 'B')).toBe(fnsArray[1]); + }); + + it('handles empty strings', () => { + const emptyStringFnsObject = { '': { name: '' } }; + const emptyStringAliasFnsObject = { foo: { name: 'foo', aliases: [''] } }; + expect(getByAlias(emptyStringFnsObject, '')).toBe(emptyStringFnsObject['']); + expect(getByAlias(emptyStringAliasFnsObject, '')).toBe(emptyStringAliasFnsObject.foo); + + const emptyStringFnsArray = [{ name: '' }]; + const emptyStringAliasFnsArray = [{ name: 'foo', aliases: [''] }]; + expect(getByAlias(emptyStringFnsArray, '')).toBe(emptyStringFnsArray[0]); + expect(getByAlias(emptyStringAliasFnsArray, '')).toBe(emptyStringAliasFnsArray[0]); + }); + + it('handles "undefined" strings', () => { + const undefinedFnsObject = { undefined: { name: 'undefined' } }; + const undefinedAliasFnsObject = { foo: { name: 'undefined', aliases: ['undefined'] } }; + expect(getByAlias(undefinedFnsObject, 'undefined')).toBe(undefinedFnsObject.undefined); + expect(getByAlias(undefinedAliasFnsObject, 'undefined')).toBe(undefinedAliasFnsObject.foo); + + const emptyStringFnsArray = [{ name: 'undefined' }]; + const emptyStringAliasFnsArray = [{ name: 'foo', aliases: ['undefined'] }]; + expect(getByAlias(emptyStringFnsArray, 'undefined')).toBe(emptyStringFnsArray[0]); + expect(getByAlias(emptyStringAliasFnsArray, 'undefined')).toBe(emptyStringAliasFnsArray[0]); + }); + + it('returns undefined if not found', () => { + expect(getByAlias(fnsObject, 'baz')).toBe(undefined); + expect(getByAlias(fnsArray, 'baz')).toBe(undefined); + }); +}); diff --git a/packages/kbn-interpreter/src/common/lib/get_type.js b/packages/kbn-interpreter/src/common/lib/get_type.js new file mode 100644 index 0000000000000..ac440acf8da5d --- /dev/null +++ b/packages/kbn-interpreter/src/common/lib/get_type.js @@ -0,0 +1,28 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export function getType(node) { + if (node == null) return 'null'; + if (typeof node === 'object') { + if (!node.type) throw new Error('Objects must have a type property'); + return node.type; + } + + return typeof node; +} diff --git a/x-pack/plugins/canvas/common/lib/grammar.js b/packages/kbn-interpreter/src/common/lib/grammar.js similarity index 100% rename from x-pack/plugins/canvas/common/lib/grammar.js rename to packages/kbn-interpreter/src/common/lib/grammar.js diff --git a/x-pack/plugins/canvas/common/lib/grammar.peg b/packages/kbn-interpreter/src/common/lib/grammar.peg similarity index 100% rename from x-pack/plugins/canvas/common/lib/grammar.peg rename to packages/kbn-interpreter/src/common/lib/grammar.peg diff --git a/packages/kbn-interpreter/src/common/lib/paths_registry.js b/packages/kbn-interpreter/src/common/lib/paths_registry.js new file mode 100644 index 0000000000000..3ad2b5dddf82e --- /dev/null +++ b/packages/kbn-interpreter/src/common/lib/paths_registry.js @@ -0,0 +1,65 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +class PathsRegistry { + + constructor() { + this.paths = new Map(); + } + + register = (type, paths) => { + if (!type) { + throw new Error(`Register requires a type`); + } + const lowerCaseType = type.toLowerCase(); + + const pathArray = Array.isArray(paths) ? paths : [paths]; + if (!this.paths.has(lowerCaseType)) { + this.paths.set(lowerCaseType, []); + } + + pathArray.forEach(p => { + this.paths.get(lowerCaseType).push(p); + }); + }; + + registerAll = (paths) => { + Object.keys(paths).forEach(type => { + this.register(type, paths[type]); + }); + }; + + toArray = () => { + return [...this.paths.values()]; + }; + + get = (type) => { + if (!type) { + return []; + } + const lowerCaseType = type.toLowerCase(); + return this.paths.has(lowerCaseType) ? this.paths.get(lowerCaseType) : []; + }; + + reset = () => { + this.paths.clear(); + }; +} + +export const pathsRegistry = new PathsRegistry(); diff --git a/packages/kbn-interpreter/src/common/lib/paths_registry.test.js b/packages/kbn-interpreter/src/common/lib/paths_registry.test.js new file mode 100644 index 0000000000000..ad2b9d949deb3 --- /dev/null +++ b/packages/kbn-interpreter/src/common/lib/paths_registry.test.js @@ -0,0 +1,92 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +describe('pathsRegistry', () => { + let registry; + beforeEach(() => { + jest.resetModules(); + registry = require('./paths_registry').pathsRegistry; + }); + + const paths = { + foo: 'bar', + sometype: [ + 'Here', + 'be', + 'more', + 'paths!' + ], + anothertype: ['with just one lonely path'] + }; + + it('throws when no type is provided', () => { + const check = () => registry.register(null, paths.foo); + expect(check).toThrowError(/requires a type/); + }); + + it('accepts paths as a string', () => { + registry.register('foo', paths.foo); + expect(registry.get('foo')).toEqual([paths.foo]); + }); + + it('accepts paths as an array', () => { + registry.register('sometype', paths.sometype); + expect(registry.get('sometype')).toEqual(paths.sometype); + }); + + it('ignores case when setting items', () => { + registry.register('FOO', paths.foo); + expect(registry.get('foo')).toEqual([paths.foo]); + }); + + it('gets items by lookup property', () => { + registry.register('sometype', paths.sometype); + expect(registry.get('sometype')).toEqual(paths.sometype); + }); + + it('can register an object of `type: path` key-value pairs', () => { + registry.registerAll(paths); + expect(registry.get('foo')).toEqual([paths.foo]); + expect(registry.get('sometype')).toEqual(paths.sometype); + expect(registry.get('anothertype')).toEqual(paths.anothertype); + }); + + it('ignores case when getting items', () => { + registry.registerAll(paths); + expect(registry.get('FOO')).toEqual([paths.foo]); + expect(registry.get('SOmEType')).toEqual(paths.sometype); + expect(registry.get('anoThertYPE')).toEqual(paths.anothertype); + }); + + it('returns an empty array with no match', () => { + expect(registry.get('@@nope_nope')).toEqual([]); + }); + + it('returns an array of all path values', () => { + registry.registerAll(paths); + expect(registry.toArray()).toEqual([[paths.foo], paths.sometype, paths.anothertype]); + }); + + it('resets the registry', () => { + registry.registerAll(paths); + expect(registry.get('sometype')).toEqual(paths.sometype); + registry.reset(); + expect(registry.get('sometype')).toEqual([]); + }); +}); \ No newline at end of file diff --git a/x-pack/plugins/canvas/common/lib/registry.js b/packages/kbn-interpreter/src/common/lib/registry.js similarity index 56% rename from x-pack/plugins/canvas/common/lib/registry.js rename to packages/kbn-interpreter/src/common/lib/registry.js index accabae4bc5eb..9882f3abde723 100644 --- a/x-pack/plugins/canvas/common/lib/registry.js +++ b/packages/kbn-interpreter/src/common/lib/registry.js @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import clone from 'lodash.clone'; @@ -22,8 +35,9 @@ export class Registry { const obj = fn(); - if (typeof obj !== 'object' || !obj[this._prop]) + if (typeof obj !== 'object' || !obj[this._prop]) { throw new Error(`Registered functions must return an object with a ${this._prop} property`); + } this._indexed[obj[this._prop].toLowerCase()] = this.wrapper(obj); } diff --git a/x-pack/plugins/canvas/common/lib/__tests__/registry.js b/packages/kbn-interpreter/src/common/lib/registry.test.js similarity index 60% rename from x-pack/plugins/canvas/common/lib/__tests__/registry.js rename to packages/kbn-interpreter/src/common/lib/registry.test.js index fd19bf0300417..dbeeb16dc1ff0 100644 --- a/x-pack/plugins/canvas/common/lib/__tests__/registry.js +++ b/packages/kbn-interpreter/src/common/lib/registry.test.js @@ -1,51 +1,63 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ -import expect from 'expect.js'; -import { Registry } from '../registry'; +import { Registry } from './registry'; function validateRegistry(registry, elements) { it('gets items by lookup property', () => { - expect(registry.get('__test2')).to.eql(elements[1]()); + expect(registry.get('__test2')).toEqual(elements[1]()); }); it('ignores case when getting items', () => { - expect(registry.get('__TeSt2')).to.eql(elements[1]()); - expect(registry.get('__tESt2')).to.eql(elements[1]()); + expect(registry.get('__TeSt2')).toEqual(elements[1]()); + expect(registry.get('__tESt2')).toEqual(elements[1]()); }); it('gets a shallow clone', () => { - expect(registry.get('__test2')).to.not.equal(elements[1]()); + expect(registry.get('__test2')).not.toBe(elements[1]()); }); it('is null with no match', () => { - expect(registry.get('@@nope_nope')).to.be(null); + expect(registry.get('@@nope_nope')).toBe(null); }); it('returns shallow clone of the whole registry via toJS', () => { const regAsJs = registry.toJS(); - expect(regAsJs).to.eql({ + expect(regAsJs).toEqual({ __test1: elements[0](), __test2: elements[1](), }); - expect(regAsJs.__test1).to.eql(elements[0]()); - expect(regAsJs.__test1).to.not.equal(elements[0]()); + expect(regAsJs.__test1).toEqual(elements[0]()); + expect(regAsJs.__test1).not.toBe(elements[0]()); }); it('returns shallow clone array via toArray', () => { const regAsArray = registry.toArray(); - expect(regAsArray).to.be.an(Array); - expect(regAsArray[0]).to.eql(elements[0]()); - expect(regAsArray[0]).to.not.equal(elements[0]()); + expect(regAsArray).toBeInstanceOf(Array); + expect(regAsArray[0]).toEqual(elements[0]()); + expect(regAsArray[0]).not.toBe(elements[0]()); }); it('resets the registry', () => { - expect(registry.get('__test2')).to.eql(elements[1]()); + expect(registry.get('__test2')).toEqual(elements[1]()); registry.reset(); - expect(registry.get('__test2')).to.equal(null); + expect(registry.get('__test2')).toBe(null); }); } @@ -70,12 +82,12 @@ describe('Registry', () => { validateRegistry(registry, elements); it('has a prop of name', () => { - expect(registry.getProp()).to.equal('name'); + expect(registry.getProp()).toBe('name'); }); it('throws when object is missing the lookup prop', () => { const check = () => registry.register(() => ({ hello: 'world' })); - expect(check).to.throwException(/object with a name property/i); + expect(check).toThrowError(/object with a name property/); }); }); @@ -99,12 +111,12 @@ describe('Registry', () => { validateRegistry(registry, elements); it('has a prop of type', () => { - expect(registry.getProp()).to.equal('type'); + expect(registry.getProp()).toBe('type'); }); it('throws when object is missing the lookup prop', () => { const check = () => registry.register(() => ({ hello: 'world' })); - expect(check).to.throwException(/object with a type property/i); + expect(check).toThrowError(/object with a type property/); }); }); @@ -137,9 +149,8 @@ describe('Registry', () => { registry.register(elements[1]); it('contains wrapped elements', () => { - // test for the custom prop on the returned elements - expect(registry.get(elements[0]().name)).to.have.property('__CUSTOM_PROP__', 1); - expect(registry.get(elements[1]().name)).to.have.property('__CUSTOM_PROP__', 2); + expect(registry.get(elements[0]().name)).toHaveProperty('__CUSTOM_PROP__'); + expect(registry.get(elements[1]().name)).toHaveProperty('__CUSTOM_PROP__'); }); }); @@ -171,20 +182,18 @@ describe('Registry', () => { }); it('get contains the full prototype', () => { - expect(thing().baseFunc).to.be.a('function'); - expect(registry.get(name).baseFunc).to.be.a('function'); + expect(typeof thing().baseFunc).toBe('function'); + expect(typeof registry.get(name).baseFunc).toBe('function'); }); it('toJS contains the full prototype', () => { const val = registry.toJS(); - expect(val[name].baseFunc).to.be.a('function'); + expect(typeof val[name].baseFunc).toBe('function'); }); }); describe('throws when lookup prop is not a string', () => { const check = () => new Registry(2); - expect(check).to.throwException(e => { - expect(e.message).to.be('Registry property name must be a string'); - }); + expect(check).toThrowError(/must be a string/); }); }); diff --git a/packages/kbn-interpreter/src/common/lib/serialize.js b/packages/kbn-interpreter/src/common/lib/serialize.js new file mode 100644 index 0000000000000..2f881db3c77e0 --- /dev/null +++ b/packages/kbn-interpreter/src/common/lib/serialize.js @@ -0,0 +1,37 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { get, identity } from 'lodash'; +import { getType } from './get_type'; + +export function serializeProvider(types) { + return { + serialize: provider('serialize'), + deserialize: provider('deserialize'), + }; + + function provider(key) { + return context => { + const type = getType(context); + const typeDef = types[type]; + const fn = get(typeDef, key) || identity; + return fn(context); + }; + } +} diff --git a/x-pack/plugins/canvas/common/lib/type.js b/packages/kbn-interpreter/src/common/lib/type.js similarity index 61% rename from x-pack/plugins/canvas/common/lib/type.js rename to packages/kbn-interpreter/src/common/lib/type.js index d917750e3848e..356b82bf91cbd 100644 --- a/x-pack/plugins/canvas/common/lib/type.js +++ b/packages/kbn-interpreter/src/common/lib/type.js @@ -1,12 +1,25 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ // All types must be universal and be castable on the client or on the server import { get } from 'lodash'; -import { getType } from '../lib/get_type'; +import { getType } from './get_type'; // TODO: Currently all casting functions must be syncronous. @@ -35,10 +48,12 @@ export function Type(config) { this.to = (node, toTypeName, types) => { const typeName = getType(node); - if (typeName !== this.name) + if (typeName !== this.name) { throw new Error(`Can not cast object of type '${typeName}' using '${this.name}'`); - else if (!this.castsTo(toTypeName)) + } + else if (!this.castsTo(toTypeName)) { throw new Error(`Can not cast '${typeName}' to '${toTypeName}'`); + } return getToFn(toTypeName)(node, types); }; diff --git a/packages/kbn-interpreter/src/common/lib/types_registry.js b/packages/kbn-interpreter/src/common/lib/types_registry.js new file mode 100644 index 0000000000000..97e28875d7e20 --- /dev/null +++ b/packages/kbn-interpreter/src/common/lib/types_registry.js @@ -0,0 +1,29 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Registry } from './registry'; +import { Type } from './type'; + +class TypesRegistry extends Registry { + wrapper(obj) { + return new Type(obj); + } +} + +export const typesRegistry = new TypesRegistry(); diff --git a/packages/kbn-interpreter/src/plugin/functions/common/clog.js b/packages/kbn-interpreter/src/plugin/functions/common/clog.js new file mode 100644 index 0000000000000..634d166f5f0bb --- /dev/null +++ b/packages/kbn-interpreter/src/plugin/functions/common/clog.js @@ -0,0 +1,27 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const clog = () => ({ + name: 'clog', + help: 'Outputs the context to the console', + fn: context => { + console.log(context); //eslint-disable-line no-console + return context; + }, +}); diff --git a/packages/kbn-interpreter/src/plugin/functions/common/index.js b/packages/kbn-interpreter/src/plugin/functions/common/index.js new file mode 100644 index 0000000000000..2f5f91181faec --- /dev/null +++ b/packages/kbn-interpreter/src/plugin/functions/common/index.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { clog } from './clog'; + +export const commonFunctions = [ + clog, +]; diff --git a/packages/kbn-interpreter/src/plugin/functions/common/register.js b/packages/kbn-interpreter/src/plugin/functions/common/register.js new file mode 100644 index 0000000000000..8b146b8f849c3 --- /dev/null +++ b/packages/kbn-interpreter/src/plugin/functions/common/register.js @@ -0,0 +1,23 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { commonFunctions } from './index'; + +// eslint-disable-next-line no-undef +commonFunctions.forEach(canvas.register); diff --git a/packages/kbn-interpreter/src/plugin/types/boolean.js b/packages/kbn-interpreter/src/plugin/types/boolean.js new file mode 100644 index 0000000000000..cc5f0a79e39a8 --- /dev/null +++ b/packages/kbn-interpreter/src/plugin/types/boolean.js @@ -0,0 +1,42 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const boolean = () => ({ + name: 'boolean', + from: { + null: () => false, + number: n => Boolean(n), + string: s => Boolean(s), + }, + to: { + render: value => { + const text = `${value}`; + return { + type: 'render', + as: 'text', + value: { text }, + }; + }, + datatable: value => ({ + type: 'datatable', + columns: [{ name: 'value', type: 'boolean' }], + rows: [{ value }], + }), + }, +}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/types/datatable.js b/packages/kbn-interpreter/src/plugin/types/datatable.js similarity index 63% rename from x-pack/plugins/canvas/canvas_plugin_src/types/datatable.js rename to packages/kbn-interpreter/src/plugin/types/datatable.js index cfe75605f1ebf..92bd2c9b1b59e 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/types/datatable.js +++ b/packages/kbn-interpreter/src/plugin/types/datatable.js @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import { map, zipObject } from 'lodash'; @@ -10,8 +23,9 @@ export const datatable = () => ({ name: 'datatable', validate: datatable => { // TODO: Check columns types. Only string, boolean, number, date, allowed for now. - if (!datatable.columns) + if (!datatable.columns) { throw new Error('datatable must have a columns array, even if it is empty'); + } if (!datatable.rows) throw new Error('datatable must have a rows array, even if it is empty'); }, diff --git a/packages/kbn-interpreter/src/plugin/types/error.js b/packages/kbn-interpreter/src/plugin/types/error.js new file mode 100644 index 0000000000000..1415a065d810e --- /dev/null +++ b/packages/kbn-interpreter/src/plugin/types/error.js @@ -0,0 +1,35 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const error = () => ({ + name: 'error', + to: { + render: input => { + const { error, info } = input; + return { + type: 'render', + as: 'error', + value: { + error, + info, + }, + }; + }, + }, +}); diff --git a/packages/kbn-interpreter/src/plugin/types/filter.js b/packages/kbn-interpreter/src/plugin/types/filter.js new file mode 100644 index 0000000000000..484050671b2f9 --- /dev/null +++ b/packages/kbn-interpreter/src/plugin/types/filter.js @@ -0,0 +1,33 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const filter = () => ({ + name: 'filter', + from: { + null: () => { + return { + type: 'filter', + // Any meta data you wish to pass along. + meta: {}, + // And filters. If you need an "or", create a filter type for it. + and: [], + }; + }, + }, +}); diff --git a/packages/kbn-interpreter/src/plugin/types/image.js b/packages/kbn-interpreter/src/plugin/types/image.js new file mode 100644 index 0000000000000..7666451145f5d --- /dev/null +++ b/packages/kbn-interpreter/src/plugin/types/image.js @@ -0,0 +1,31 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const image = () => ({ + name: 'image', + to: { + render: input => { + return { + type: 'render', + as: 'image', + value: input, + }; + }, + }, +}); diff --git a/packages/kbn-interpreter/src/plugin/types/index.js b/packages/kbn-interpreter/src/plugin/types/index.js new file mode 100644 index 0000000000000..1ae5f874835c3 --- /dev/null +++ b/packages/kbn-interpreter/src/plugin/types/index.js @@ -0,0 +1,46 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { boolean } from './boolean'; +import { datatable } from './datatable'; +import { error } from './error'; +import { filter } from './filter'; +import { image } from './image'; +import { nullType } from './null'; +import { number } from './number'; +import { pointseries } from './pointseries'; +import { render } from './render'; +import { shape } from './shape'; +import { string } from './string'; +import { style } from './style'; + +export const typeSpecs = [ + boolean, + datatable, + error, + filter, + image, + number, + nullType, + pointseries, + render, + shape, + string, + style, +]; diff --git a/packages/kbn-interpreter/src/plugin/types/null.js b/packages/kbn-interpreter/src/plugin/types/null.js new file mode 100644 index 0000000000000..2789ce330ac6c --- /dev/null +++ b/packages/kbn-interpreter/src/plugin/types/null.js @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const nullType = () => ({ + name: 'null', + from: { + '*': () => null, + }, +}); diff --git a/packages/kbn-interpreter/src/plugin/types/number.js b/packages/kbn-interpreter/src/plugin/types/number.js new file mode 100644 index 0000000000000..8f8f31ea8a2fb --- /dev/null +++ b/packages/kbn-interpreter/src/plugin/types/number.js @@ -0,0 +1,42 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const number = () => ({ + name: 'number', + from: { + null: () => 0, + boolean: b => Number(b), + string: n => Number(n), + }, + to: { + render: value => { + const text = `${value}`; + return { + type: 'render', + as: 'text', + value: { text }, + }; + }, + datatable: value => ({ + type: 'datatable', + columns: [{ name: 'value', type: 'number' }], + rows: [{ value }], + }), + }, +}); diff --git a/packages/kbn-interpreter/src/plugin/types/pointseries.js b/packages/kbn-interpreter/src/plugin/types/pointseries.js new file mode 100644 index 0000000000000..2275ea9e04094 --- /dev/null +++ b/packages/kbn-interpreter/src/plugin/types/pointseries.js @@ -0,0 +1,44 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const pointseries = () => ({ + name: 'pointseries', + from: { + null: () => { + return { + type: 'pointseries', + rows: [], + columns: [], + }; + }, + }, + to: { + render: (pointseries, types) => { + const datatable = types.datatable.from(pointseries, types); + return { + type: 'render', + as: 'table', + value: { + datatable, + showHeader: true, + }, + }; + }, + }, +}); diff --git a/packages/kbn-interpreter/src/plugin/types/register.js b/packages/kbn-interpreter/src/plugin/types/register.js new file mode 100644 index 0000000000000..17b03f0229672 --- /dev/null +++ b/packages/kbn-interpreter/src/plugin/types/register.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import 'babel-polyfill'; +import { typeSpecs } from './index'; + +// eslint-disable-next-line no-undef +typeSpecs.forEach(canvas.register); diff --git a/packages/kbn-interpreter/src/plugin/types/render.js b/packages/kbn-interpreter/src/plugin/types/render.js new file mode 100644 index 0000000000000..99ce3ca7d1cd7 --- /dev/null +++ b/packages/kbn-interpreter/src/plugin/types/render.js @@ -0,0 +1,29 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const render = () => ({ + name: 'render', + from: { + '*': v => ({ + type: 'render', + as: 'debug', + value: v, + }), + }, +}); diff --git a/packages/kbn-interpreter/src/plugin/types/shape.js b/packages/kbn-interpreter/src/plugin/types/shape.js new file mode 100644 index 0000000000000..1ed7a111268d1 --- /dev/null +++ b/packages/kbn-interpreter/src/plugin/types/shape.js @@ -0,0 +1,31 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const shape = () => ({ + name: 'shape', + to: { + render: input => { + return { + type: 'render', + as: 'shape', + value: input, + }; + }, + }, +}); diff --git a/packages/kbn-interpreter/src/plugin/types/string.js b/packages/kbn-interpreter/src/plugin/types/string.js new file mode 100644 index 0000000000000..90e6b17cc9dcf --- /dev/null +++ b/packages/kbn-interpreter/src/plugin/types/string.js @@ -0,0 +1,41 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const string = () => ({ + name: 'string', + from: { + null: () => '', + boolean: b => String(b), + number: n => String(n), + }, + to: { + render: text => { + return { + type: 'render', + as: 'text', + value: { text }, + }; + }, + datatable: value => ({ + type: 'datatable', + columns: [{ name: 'value', type: 'string' }], + rows: [{ value }], + }), + }, +}); diff --git a/packages/kbn-interpreter/src/plugin/types/style.js b/packages/kbn-interpreter/src/plugin/types/style.js new file mode 100644 index 0000000000000..97057b415a475 --- /dev/null +++ b/packages/kbn-interpreter/src/plugin/types/style.js @@ -0,0 +1,31 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const style = () => ({ + name: 'style', + from: { + null: () => { + return { + type: 'style', + spec: {}, + css: '', + }; + }, + }, +}); diff --git a/packages/kbn-interpreter/src/public/browser_registries.js b/packages/kbn-interpreter/src/public/browser_registries.js new file mode 100644 index 0000000000000..312c934cdedec --- /dev/null +++ b/packages/kbn-interpreter/src/public/browser_registries.js @@ -0,0 +1,82 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import $script from 'scriptjs'; + +let resolvePromise = null; +let called = false; + +let populatePromise = new Promise(_resolve => { + resolvePromise = _resolve; +}); + +export const getBrowserRegistries = () => { + return populatePromise; +}; + +const loadBrowserRegistries = (registries, basePath) => { + const remainingTypes = Object.keys(registries); + const populatedTypes = {}; + + return new Promise(resolve => { + function loadType() { + if (!remainingTypes.length) { + resolve(populatedTypes); + return; + } + const type = remainingTypes.pop(); + window.canvas = window.canvas || {}; + window.canvas.register = d => registries[type].register(d); + + // Load plugins one at a time because each needs a different loader function + // $script will only load each of these once, we so can call this as many times as we need? + const pluginPath = `${basePath}/api/canvas/plugins?type=${type}`; + $script(pluginPath, () => { + populatedTypes[type] = registries[type]; + loadType(); + }); + } + + loadType(); + }); +}; + +export const populateBrowserRegistries = (registries, basePath) => { + if (called) { + const oldPromise = populatePromise; + let newResolve; + populatePromise = new Promise(_resolve => { + newResolve = _resolve; + }); + oldPromise.then(oldTypes => { + loadBrowserRegistries(registries, basePath).then(newTypes => { + newResolve({ + ...oldTypes, + ...newTypes, + }); + }); + }); + return populatePromise; + } + called = true; + loadBrowserRegistries(registries, basePath).then(registries => { + resolvePromise(registries); + }); + return populatePromise; +}; diff --git a/packages/kbn-interpreter/src/public/create_handlers.js b/packages/kbn-interpreter/src/public/create_handlers.js new file mode 100644 index 0000000000000..3446a945ae76e --- /dev/null +++ b/packages/kbn-interpreter/src/public/create_handlers.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export function createHandlers(/*socket*/) { + return { + environment: 'client', + }; +} diff --git a/packages/kbn-interpreter/src/public/index.js b/packages/kbn-interpreter/src/public/index.js new file mode 100644 index 0000000000000..de8caad1dbc5b --- /dev/null +++ b/packages/kbn-interpreter/src/public/index.js @@ -0,0 +1,22 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { populateBrowserRegistries, getBrowserRegistries } from './browser_registries'; +export { createSocket } from './socket'; +export { initializeInterpreter, interpretAst } from './interpreter'; diff --git a/x-pack/plugins/canvas/public/lib/interpreter.js b/packages/kbn-interpreter/src/public/interpreter.js similarity index 53% rename from x-pack/plugins/canvas/public/lib/interpreter.js rename to packages/kbn-interpreter/src/public/interpreter.js index 36878871b8b15..ba38df27b6f85 100644 --- a/x-pack/plugins/canvas/public/lib/interpreter.js +++ b/packages/kbn-interpreter/src/public/interpreter.js @@ -1,22 +1,35 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ -import { socketInterpreterProvider } from '../../common/interpreter/socket_interpret'; -import { serializeProvider } from '../../common/lib/serialize'; -import { getSocket } from '../socket'; -import { typesRegistry } from '../../common/lib/types_registry'; +import { socketInterpreterProvider } from '../common/interpreter/socket_interpret'; +import { serializeProvider } from '../common/lib/serialize'; +import { getSocket } from './socket'; +import { typesRegistry } from '../common/lib/types_registry'; import { createHandlers } from './create_handlers'; -import { functionsRegistry } from './functions_registry'; +import { functionsRegistry } from '../common/lib/functions_registry'; import { getBrowserRegistries } from './browser_registries'; let socket; let resolve; const functionList = new Promise(_resolve => (resolve = _resolve)); -export async function initialize() { +export async function initializeInterpreter() { socket = getSocket(); // Listen for interpreter runs diff --git a/x-pack/plugins/canvas/public/socket.js b/packages/kbn-interpreter/src/public/socket.js similarity index 64% rename from x-pack/plugins/canvas/public/socket.js rename to packages/kbn-interpreter/src/public/socket.js index 92deedd488c06..9143f0018377b 100644 --- a/x-pack/plugins/canvas/public/socket.js +++ b/packages/kbn-interpreter/src/public/socket.js @@ -1,12 +1,25 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import io from 'socket.io-client'; import { functionsRegistry } from '../common/lib/functions_registry'; -import { getBrowserRegistries } from './lib/browser_registries'; +import { getBrowserRegistries } from './browser_registries'; const SOCKET_CONNECTION_TIMEOUT = 5000; // timeout in ms let socket; diff --git a/packages/kbn-interpreter/src/server/get_plugin_paths.js b/packages/kbn-interpreter/src/server/get_plugin_paths.js new file mode 100644 index 0000000000000..f6520563c912f --- /dev/null +++ b/packages/kbn-interpreter/src/server/get_plugin_paths.js @@ -0,0 +1,55 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import fs from 'fs'; +import { resolve } from 'path'; +import { promisify } from 'util'; +import { flatten } from 'lodash'; +import { pathsRegistry } from '../common/lib/paths_registry'; + +const lstat = promisify(fs.lstat); +const readdir = promisify(fs.readdir); + +const isDirectory = path => + lstat(path) + .then(stat => stat.isDirectory()) + .catch(() => false); + +export const getPluginPaths = type => { + const typePaths = pathsRegistry.get(type); + if (!typePaths) { + throw new Error(`Unknown type: ${type}`); + } + + return Promise.all(typePaths.map(async path => { + const isDir = await isDirectory(path); + if (!isDir) { + return; + } + // Get the full path of all js files in the directory + return readdir(path).then(files => { + return files.reduce((acc, file) => { + if (file.endsWith('.js')) { + acc.push(resolve(path, file)); + } + return acc; + }, []); + }).catch(); + })).then(flatten); +}; diff --git a/packages/kbn-interpreter/src/server/index.js b/packages/kbn-interpreter/src/server/index.js new file mode 100644 index 0000000000000..e481a3ec1fd60 --- /dev/null +++ b/packages/kbn-interpreter/src/server/index.js @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { populateServerRegistries, getServerRegistries } from './server_registries'; +export { getPluginPaths } from './get_plugin_paths'; diff --git a/packages/kbn-interpreter/src/server/server_registries.js b/packages/kbn-interpreter/src/server/server_registries.js new file mode 100644 index 0000000000000..3a732f0d04e23 --- /dev/null +++ b/packages/kbn-interpreter/src/server/server_registries.js @@ -0,0 +1,77 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { typesRegistry } from '../common/lib/types_registry'; +import { functionsRegistry as serverFunctions } from '../common/lib/functions_registry'; +import { getPluginPaths } from './get_plugin_paths'; + +const registries = { + serverFunctions: serverFunctions, + commonFunctions: serverFunctions, + types: typesRegistry, +}; + +let resolve = null; +let called = false; + +const populatePromise = new Promise(_resolve => { + resolve = _resolve; +}); + +export const getServerRegistries = () => { + return populatePromise; +}; + +export const populateServerRegistries = types => { + if (called) { + return populatePromise; + } + called = true; + if (!types || !types.length) throw new Error('types is required'); + + const remainingTypes = types; + const populatedTypes = {}; + + const globalKeys = Object.keys(global); + + const loadType = () => { + const type = remainingTypes.pop(); + getPluginPaths(type).then(paths => { + global.canvas = global.canvas || {}; + global.canvas.register = d => registries[type].register(d); + + paths.forEach(path => { + require(path); + }); + + Object.keys(global).forEach(key => { + if (!globalKeys.includes(key)) { + delete global[key]; + } + }); + + populatedTypes[type] = registries[type]; + if (remainingTypes.length) loadType(); + else resolve(populatedTypes); + }); + }; + + if (remainingTypes.length) loadType(); + return populatePromise; +}; diff --git a/packages/kbn-interpreter/tasks/build/cli.js b/packages/kbn-interpreter/tasks/build/cli.js new file mode 100644 index 0000000000000..88097c2e90271 --- /dev/null +++ b/packages/kbn-interpreter/tasks/build/cli.js @@ -0,0 +1,113 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const { relative } = require('path'); + +const getopts = require('getopts'); +const del = require('del'); +const supportsColor = require('supports-color'); +const { ToolingLog, withProcRunner, pickLevelFromFlags } = require('@kbn/dev-utils'); + +const { + ROOT_DIR, + PLUGIN_SOURCE_DIR, + BUILD_DIR, + WEBPACK_CONFIG_PATH +} = require('./paths'); + +const unknownFlags = []; +const flags = getopts(process.argv, { + boolean: [ + 'watch', + 'dev', + 'help', + 'debug' + ], + unknown(name) { + unknownFlags.push(name); + } +}); + +const log = new ToolingLog({ + level: pickLevelFromFlags(flags), + writeTo: process.stdout +}); + +if (unknownFlags.length) { + log.error(`Unknown flag(s): ${unknownFlags.join(', ')}`); + flags.help = true; + process.exitCode = 1; +} + +if (flags.help) { + log.info(` + Simple build tool for @kbn/interpreter package + + --dev Build for development, include source maps + --watch Run in watch mode + --debug Turn on debug logging + `); + process.exit(); +} + +withProcRunner(log, async (proc) => { + log.info('Deleting old output'); + await del(BUILD_DIR); + + const cwd = ROOT_DIR; + const env = { ...process.env }; + if (supportsColor.stdout) { + env.FORCE_COLOR = 'true'; + } + + log.info(`Starting babel and webpack${flags.watch ? ' in watch mode' : ''}`); + await Promise.all([ + proc.run('babel ', { + cmd: 'babel', + args: [ + 'src', + '--ignore', `${relative(cwd, PLUGIN_SOURCE_DIR)},*.test.js`, + '--out-dir', relative(cwd, BUILD_DIR), + '--copy-files', + ...(flags.dev ? ['--source-maps', 'inline'] : []), + ...(flags.watch ? ['--watch'] : ['--quiet']) + ], + wait: true, + env, + cwd + }), + + proc.run('webpack', { + cmd: 'webpack', + args: [ + '--config', relative(cwd, WEBPACK_CONFIG_PATH), + '--env.sourceMaps', String(Boolean(flags.dev)), + ...(flags.watch ? ['--watch'] : []), + ], + wait: true, + env, + cwd + }), + ]); + + log.success('Complete'); +}).catch((error) => { + log.error(error); + process.exit(1); +}); diff --git a/packages/kbn-interpreter/tasks/build/paths.js b/packages/kbn-interpreter/tasks/build/paths.js new file mode 100644 index 0000000000000..5b9128a892e70 --- /dev/null +++ b/packages/kbn-interpreter/tasks/build/paths.js @@ -0,0 +1,31 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const { resolve } = require('path'); + +exports.ROOT_DIR = resolve(__dirname, '../../'); +exports.SOURCE_DIR = resolve(exports.ROOT_DIR, 'src'); +exports.BUILD_DIR = resolve(exports.ROOT_DIR, 'target'); + +exports.PLUGIN_SOURCE_DIR = resolve(exports.SOURCE_DIR, 'plugin'); +exports.PLUGIN_BUILD_DIR = resolve(exports.BUILD_DIR, 'plugin'); + +exports.WEBPACK_CONFIG_PATH = require.resolve('./webpack.config'); +exports.BABEL_PRESET_PATH = require.resolve('@kbn/babel-preset/webpack_preset'); + diff --git a/packages/kbn-interpreter/tasks/build/webpack.config.js b/packages/kbn-interpreter/tasks/build/webpack.config.js new file mode 100644 index 0000000000000..9dc9398563efd --- /dev/null +++ b/packages/kbn-interpreter/tasks/build/webpack.config.js @@ -0,0 +1,110 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const { resolve } = require('path'); +const { + PLUGIN_SOURCE_DIR, + PLUGIN_BUILD_DIR, + BABEL_PRESET_PATH, +} = require('./paths'); + +module.exports = function ({ sourceMaps }, { watch }) { + return { + devtool: sourceMaps ? 'inline-cheap-module-source-map' : undefined, + + entry: { + 'types/all': resolve(PLUGIN_SOURCE_DIR, 'types/register.js'), + 'functions/common/all': resolve(PLUGIN_SOURCE_DIR, 'functions/common/register.js'), + }, + + // there were problems with the node and web targets since this code is actually + // targetting both the browser and node.js. If there was a hybrid target we'd use + // it, but this seems to work either way. + target: 'webworker', + + output: { + path: PLUGIN_BUILD_DIR, + filename: '[name].js', // Need long paths here. + libraryTarget: 'umd', + }, + + resolve: { + extensions: ['.js', '.json'], + mainFields: ['browser', 'main'], + }, + + module: { + rules: [ + { + test: /\.js$/, + include: PLUGIN_SOURCE_DIR, + loaders: 'babel-loader', + options: { + babelrc: false, + presets: [BABEL_PRESET_PATH], + }, + }, + { + test: /\.(png|jpg|gif|jpeg|svg)$/, + loaders: ['url-loader'], + }, + { + test: /\.(css|scss)$/, + loaders: ['style-loader', 'css-loader', 'sass-loader'], + }, + ], + }, + + node: { + // Don't replace built-in globals + __filename: false, + __dirname: false, + }, + + watchOptions: { + ignored: [/node_modules/], + }, + + stats: 'errors-only', + + plugins: [ + function loaderFailHandler() { + if (!watch) { + return; + } + + let lastBuildFailed = false; + + // bails on error, including loader errors + // see https://github.com/webpack/webpack/issues/708, which does not fix loader errors + this.plugin('done', function (stats) { + if (stats.hasErrors() || stats.hasWarnings()) { + lastBuildFailed = true; + return; + } + + if (lastBuildFailed) { + lastBuildFailed = false; + console.log('✅ Webpack error resolved'); + } + }); + }, + ] + }; +}; diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 93c0e111f0d38..4660a71d482e0 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -11064,7 +11064,8 @@ let run = exports.run = (() => { e: 'exclude', h: 'help', i: 'include' - } + }, + boolean: ['prefer-offline', 'frozen-lockfile'] }); const args = options._; if (options.help || args.length === 0) { @@ -12199,8 +12200,7 @@ const BootstrapCommand = exports.BootstrapCommand = { batchByWorkspace: true }); const batchedProjects = (0, _projects.topologicallyBatchProjects)(projects, projectGraph); - const frozenLockfile = options['frozen-lockfile'] === true; - const extraArgs = frozenLockfile ? ['--frozen-lockfile'] : []; + const extraArgs = [...(options['frozen-lockfile'] === true ? ['--frozen-lockfile'] : []), ...(options['prefer-offline'] === true ? ['--prefer-offline'] : [])]; _log.log.write(_chalk2.default.bold('\nRunning installs in topological order:')); for (const batch of batchedProjectsByWorkspace) { for (const project of batch) { @@ -19655,7 +19655,7 @@ exports.runScriptInPackage = exports.installInDir = undefined; */ let installInDir = exports.installInDir = (() => { var _ref = _asyncToGenerator(function* (directory, extraArgs = []) { - const options = ['install', '--non-interactive', '--mutex=file', ...extraArgs]; + const options = ['install', '--non-interactive', ...extraArgs]; // We pass the mutex flag to ensure only one instance of yarn runs at any // given time (e.g. to avoid conflicts). yield (0, _child_process.spawn)('yarn', options, { diff --git a/packages/kbn-pm/src/cli.ts b/packages/kbn-pm/src/cli.ts index 07383f89b8165..d7e3aafd13fce 100644 --- a/packages/kbn-pm/src/cli.ts +++ b/packages/kbn-pm/src/cli.ts @@ -65,6 +65,7 @@ export async function run(argv: string[]) { h: 'help', i: 'include', }, + boolean: ['prefer-offline', 'frozen-lockfile'], }); const args = options._; diff --git a/packages/kbn-pm/src/commands/bootstrap.ts b/packages/kbn-pm/src/commands/bootstrap.ts index 2469e4cca1cb1..be4b9da7bf516 100644 --- a/packages/kbn-pm/src/commands/bootstrap.ts +++ b/packages/kbn-pm/src/commands/bootstrap.ts @@ -35,8 +35,10 @@ export const BootstrapCommand: ICommand = { }); const batchedProjects = topologicallyBatchProjects(projects, projectGraph); - const frozenLockfile = options['frozen-lockfile'] === true; - const extraArgs = frozenLockfile ? ['--frozen-lockfile'] : []; + const extraArgs = [ + ...(options['frozen-lockfile'] === true ? ['--frozen-lockfile'] : []), + ...(options['prefer-offline'] === true ? ['--prefer-offline'] : []), + ]; log.write(chalk.bold('\nRunning installs in topological order:')); diff --git a/packages/kbn-pm/src/utils/scripts.ts b/packages/kbn-pm/src/utils/scripts.ts index 177068b9b1112..f1e0a45222673 100644 --- a/packages/kbn-pm/src/utils/scripts.ts +++ b/packages/kbn-pm/src/utils/scripts.ts @@ -24,7 +24,7 @@ import { Project } from './project'; * Install all dependencies in the given directory */ export async function installInDir(directory: string, extraArgs: string[] = []) { - const options = ['install', '--non-interactive', '--mutex=file', ...extraArgs]; + const options = ['install', '--non-interactive', ...extraArgs]; // We pass the mutex flag to ensure only one instance of yarn runs at any // given time (e.g. to avoid conflicts). diff --git a/packages/kbn-test/package.json b/packages/kbn-test/package.json index 773add70b6923..22eb066751a13 100644 --- a/packages/kbn-test/package.json +++ b/packages/kbn-test/package.json @@ -6,7 +6,7 @@ "private": true, "scripts": { "build": "babel src --out-dir target", - "kbn:bootstrap": "yarn build", + "kbn:bootstrap": "yarn build --quiet", "kbn:watch": "yarn build --watch" }, "devDependencies": { diff --git a/packages/kbn-test/src/functional_tests/cli/run_tests/__snapshots__/args.test.js.snap b/packages/kbn-test/src/functional_tests/cli/run_tests/__snapshots__/args.test.js.snap index 93d129213ff02..c55b8abf6c78c 100644 --- a/packages/kbn-test/src/functional_tests/cli/run_tests/__snapshots__/args.test.js.snap +++ b/packages/kbn-test/src/functional_tests/cli/run_tests/__snapshots__/args.test.js.snap @@ -18,6 +18,7 @@ Options: --updateBaselines Replace baseline screenshots with whatever is generated from the test. --include-tag Tags that suites must include to be run, can be included multiple times. --exclude-tag Tags that suites must NOT include to be run, can be included multiple times. + --assert-none-excluded Exit with 1/0 based on if any test is excluded with the current set of tags. --verbose Log everything. --debug Run in debug mode. --quiet Only log errors. @@ -26,8 +27,9 @@ Options: exports[`process options for run tests CLI accepts boolean value for updateBaselines 1`] = ` Object { + "assertNoneExcluded": false, "configs": Array [ - "foo", + /foo, ], "createLogger": [Function], "extraKbnOpts": undefined, @@ -41,8 +43,9 @@ Object { exports[`process options for run tests CLI accepts debug option 1`] = ` Object { + "assertNoneExcluded": false, "configs": Array [ - "foo", + /foo, ], "createLogger": [Function], "debug": true, @@ -56,9 +59,10 @@ Object { exports[`process options for run tests CLI accepts empty config value if default passed 1`] = ` Object { + "assertNoneExcluded": false, "config": "", "configs": Array [ - "foo", + /foo, ], "createLogger": [Function], "extraKbnOpts": undefined, @@ -74,8 +78,9 @@ Object { "_": Object { "server.foo": "bar", }, + "assertNoneExcluded": false, "configs": Array [ - "foo", + /foo, ], "createLogger": [Function], "extraKbnOpts": Object { @@ -90,8 +95,9 @@ Object { exports[`process options for run tests CLI accepts quiet option 1`] = ` Object { + "assertNoneExcluded": false, "configs": Array [ - "foo", + /foo, ], "createLogger": [Function], "extraKbnOpts": undefined, @@ -105,8 +111,9 @@ Object { exports[`process options for run tests CLI accepts silent option 1`] = ` Object { + "assertNoneExcluded": false, "configs": Array [ - "foo", + /foo, ], "createLogger": [Function], "extraKbnOpts": undefined, @@ -120,8 +127,9 @@ Object { exports[`process options for run tests CLI accepts source value for esFrom 1`] = ` Object { + "assertNoneExcluded": false, "configs": Array [ - "foo", + /foo, ], "createLogger": [Function], "esFrom": "source", @@ -135,8 +143,9 @@ Object { exports[`process options for run tests CLI accepts string value for kibana-install-dir 1`] = ` Object { + "assertNoneExcluded": false, "configs": Array [ - "foo", + /foo, ], "createLogger": [Function], "extraKbnOpts": undefined, @@ -150,8 +159,9 @@ Object { exports[`process options for run tests CLI accepts value for grep 1`] = ` Object { + "assertNoneExcluded": false, "configs": Array [ - "foo", + /foo, ], "createLogger": [Function], "extraKbnOpts": undefined, @@ -165,8 +175,9 @@ Object { exports[`process options for run tests CLI accepts verbose option 1`] = ` Object { + "assertNoneExcluded": false, "configs": Array [ - "foo", + /foo, ], "createLogger": [Function], "extraKbnOpts": undefined, diff --git a/packages/kbn-test/src/functional_tests/cli/run_tests/__snapshots__/cli.test.js.snap b/packages/kbn-test/src/functional_tests/cli/run_tests/__snapshots__/cli.test.js.snap index 6d4b35f754b42..e9f3dfac56746 100644 --- a/packages/kbn-test/src/functional_tests/cli/run_tests/__snapshots__/cli.test.js.snap +++ b/packages/kbn-test/src/functional_tests/cli/run_tests/__snapshots__/cli.test.js.snap @@ -18,6 +18,7 @@ Options: --updateBaselines Replace baseline screenshots with whatever is generated from the test. --include-tag Tags that suites must include to be run, can be included multiple times. --exclude-tag Tags that suites must NOT include to be run, can be included multiple times. + --assert-none-excluded Exit with 1/0 based on if any test is excluded with the current set of tags. --verbose Log everything. --debug Run in debug mode. --quiet Only log errors. diff --git a/packages/kbn-test/src/functional_tests/cli/run_tests/args.js b/packages/kbn-test/src/functional_tests/cli/run_tests/args.js index 6aa1e1aa5c31b..eb7809b91cebc 100644 --- a/packages/kbn-test/src/functional_tests/cli/run_tests/args.js +++ b/packages/kbn-test/src/functional_tests/cli/run_tests/args.js @@ -17,6 +17,8 @@ * under the License. */ +import { resolve } from 'path'; + import dedent from 'dedent'; import { ToolingLog, pickLevelFromFlags } from '@kbn/dev-utils'; @@ -52,6 +54,9 @@ const options = { arg: '', desc: 'Tags that suites must NOT include to be run, can be included multiple times.', }, + 'assert-none-excluded': { + desc: 'Exit with 1/0 based on if any test is excluded with the current set of tags.', + }, verbose: { desc: 'Log everything.' }, debug: { desc: 'Run in debug mode.' }, quiet: { desc: 'Only log errors.' }, @@ -113,6 +118,9 @@ export function processOptions(userOptions, defaultConfigPaths) { delete userOptions['include-tag']; delete userOptions['exclude-tag']; + userOptions.assertNoneExcluded = !!userOptions['assert-none-excluded']; + delete userOptions['assert-none-excluded']; + function createLogger() { return new ToolingLog({ level: pickLevelFromFlags(userOptions), @@ -122,7 +130,7 @@ export function processOptions(userOptions, defaultConfigPaths) { return { ...userOptions, - configs, + configs: configs.map(c => resolve(c)), createLogger, extraKbnOpts: userOptions._, }; diff --git a/packages/kbn-test/src/functional_tests/cli/run_tests/args.test.js b/packages/kbn-test/src/functional_tests/cli/run_tests/args.test.js index 2e0c2fb44022d..a6d5bf72ffc85 100644 --- a/packages/kbn-test/src/functional_tests/cli/run_tests/args.test.js +++ b/packages/kbn-test/src/functional_tests/cli/run_tests/args.test.js @@ -18,6 +18,9 @@ */ import { displayHelp, processOptions } from './args'; +import { createAbsolutePathSerializer } from '@kbn/dev-utils'; + +expect.addSnapshotSerializer(createAbsolutePathSerializer(process.cwd())); describe('display help for run tests CLI', () => { it('displays as expected', () => { diff --git a/packages/kbn-test/src/functional_tests/cli/run_tests/cli.test.js b/packages/kbn-test/src/functional_tests/cli/run_tests/cli.test.js index 7e2e6c5a40e78..90caaf2cc6ce4 100644 --- a/packages/kbn-test/src/functional_tests/cli/run_tests/cli.test.js +++ b/packages/kbn-test/src/functional_tests/cli/run_tests/cli.test.js @@ -17,6 +17,8 @@ * under the License. */ +import { Writable } from 'stream'; + import { runTestsCli } from './cli'; import { checkMockConsoleLogSnapshot } from '../../test_helpers'; @@ -36,7 +38,7 @@ describe('run tests CLI', () => { const processMock = { exit: exitMock, argv: argvMock, - stdout: { on: jest.fn(), once: jest.fn(), emit: jest.fn() }, + stdout: new Writable(), cwd: jest.fn(), }; diff --git a/packages/kbn-test/src/functional_tests/cli/start_servers/__snapshots__/args.test.js.snap b/packages/kbn-test/src/functional_tests/cli/start_servers/__snapshots__/args.test.js.snap index 50da9f6836883..106ddcb00faf6 100644 --- a/packages/kbn-test/src/functional_tests/cli/start_servers/__snapshots__/args.test.js.snap +++ b/packages/kbn-test/src/functional_tests/cli/start_servers/__snapshots__/args.test.js.snap @@ -21,9 +21,7 @@ Options: exports[`process options for start servers CLI accepts debug option 1`] = ` Object { - "config": Array [ - "foo", - ], + "config": /foo, "createLogger": [Function], "debug": true, "esFrom": "snapshot", @@ -33,9 +31,7 @@ Object { exports[`process options for start servers CLI accepts empty config value if default passed 1`] = ` Object { - "config": Array [ - "foo", - ], + "config": /foo, "createLogger": [Function], "esFrom": "snapshot", "extraKbnOpts": undefined, @@ -47,9 +43,7 @@ Object { "_": Object { "server.foo": "bar", }, - "config": Array [ - "foo", - ], + "config": /foo, "createLogger": [Function], "esFrom": "snapshot", "extraKbnOpts": Object { @@ -60,9 +54,7 @@ Object { exports[`process options for start servers CLI accepts quiet option 1`] = ` Object { - "config": Array [ - "foo", - ], + "config": /foo, "createLogger": [Function], "esFrom": "snapshot", "extraKbnOpts": undefined, @@ -72,9 +64,7 @@ Object { exports[`process options for start servers CLI accepts silent option 1`] = ` Object { - "config": Array [ - "foo", - ], + "config": /foo, "createLogger": [Function], "esFrom": "snapshot", "extraKbnOpts": undefined, @@ -84,9 +74,7 @@ Object { exports[`process options for start servers CLI accepts source value for esFrom 1`] = ` Object { - "config": Array [ - "foo", - ], + "config": /foo, "createLogger": [Function], "esFrom": "source", "extraKbnOpts": undefined, @@ -95,9 +83,7 @@ Object { exports[`process options for start servers CLI accepts string value for kibana-install-dir 1`] = ` Object { - "config": Array [ - "foo", - ], + "config": /foo, "createLogger": [Function], "esFrom": "snapshot", "extraKbnOpts": undefined, @@ -107,9 +93,7 @@ Object { exports[`process options for start servers CLI accepts verbose option 1`] = ` Object { - "config": Array [ - "foo", - ], + "config": /foo, "createLogger": [Function], "esFrom": "snapshot", "extraKbnOpts": undefined, diff --git a/packages/kbn-test/src/functional_tests/cli/start_servers/args.js b/packages/kbn-test/src/functional_tests/cli/start_servers/args.js index 170de1942232b..026c23ab46f37 100644 --- a/packages/kbn-test/src/functional_tests/cli/start_servers/args.js +++ b/packages/kbn-test/src/functional_tests/cli/start_servers/args.js @@ -17,6 +17,8 @@ * under the License. */ +import { resolve } from 'path'; + import dedent from 'dedent'; import { ToolingLog, pickLevelFromFlags } from '@kbn/dev-utils'; @@ -97,7 +99,7 @@ export function processOptions(userOptions, defaultConfigPath) { return { ...userOptions, - config, + config: resolve(config), createLogger, extraKbnOpts: userOptions._, }; diff --git a/packages/kbn-test/src/functional_tests/cli/start_servers/args.test.js b/packages/kbn-test/src/functional_tests/cli/start_servers/args.test.js index 22c06b6ae3231..bc2b958d793fd 100644 --- a/packages/kbn-test/src/functional_tests/cli/start_servers/args.test.js +++ b/packages/kbn-test/src/functional_tests/cli/start_servers/args.test.js @@ -18,6 +18,9 @@ */ import { displayHelp, processOptions } from './args'; +import { createAbsolutePathSerializer } from '@kbn/dev-utils'; + +expect.addSnapshotSerializer(createAbsolutePathSerializer(process.cwd())); describe('display help for start servers CLI', () => { it('displays as expected', () => { @@ -39,60 +42,60 @@ describe('process options for start servers CLI', () => { }); it('accepts empty config value if default passed', () => { - const options = processOptions({ config: '' }, ['foo']); + const options = processOptions({ config: '' }, 'foo'); expect(options).toMatchSnapshot(); }); it('rejects invalid option', () => { expect(() => { - processOptions({ bail: true }, ['foo']); + processOptions({ bail: true }, 'foo'); }).toThrow('functional_tests_server: invalid option [bail]'); }); it('accepts string value for kibana-install-dir', () => { - const options = processOptions({ 'kibana-install-dir': 'foo' }, ['foo']); + const options = processOptions({ 'kibana-install-dir': 'foo' }, 'foo'); expect(options).toMatchSnapshot(); }); it('rejects boolean value for kibana-install-dir', () => { expect(() => { - processOptions({ 'kibana-install-dir': true }, ['foo']); + processOptions({ 'kibana-install-dir': true }, 'foo'); }).toThrow('functional_tests_server: invalid argument [true] to option [kibana-install-dir]'); }); it('accepts source value for esFrom', () => { - const options = processOptions({ esFrom: 'source' }, ['foo']); + const options = processOptions({ esFrom: 'source' }, 'foo'); expect(options).toMatchSnapshot(); }); it('accepts debug option', () => { - const options = processOptions({ debug: true }, ['foo']); + const options = processOptions({ debug: true }, 'foo'); expect(options).toMatchSnapshot(); }); it('accepts silent option', () => { - const options = processOptions({ silent: true }, ['foo']); + const options = processOptions({ silent: true }, 'foo'); expect(options).toMatchSnapshot(); }); it('accepts quiet option', () => { - const options = processOptions({ quiet: true }, ['foo']); + const options = processOptions({ quiet: true }, 'foo'); expect(options).toMatchSnapshot(); }); it('accepts verbose option', () => { - const options = processOptions({ verbose: true }, ['foo']); + const options = processOptions({ verbose: true }, 'foo'); expect(options).toMatchSnapshot(); }); it('accepts extra server options', () => { - const options = processOptions({ _: { 'server.foo': 'bar' } }, ['foo']); + const options = processOptions({ _: { 'server.foo': 'bar' } }, 'foo'); expect(options).toMatchSnapshot(); }); it('rejects invalid options even if valid options exist', () => { expect(() => { - processOptions({ debug: true, aintnothang: true, bail: true }, ['foo']); + processOptions({ debug: true, aintnothang: true, bail: true }, 'foo'); }).toThrow('functional_tests_server: invalid option [aintnothang]'); }); }); diff --git a/packages/kbn-test/src/functional_tests/cli/start_servers/cli.test.js b/packages/kbn-test/src/functional_tests/cli/start_servers/cli.test.js index 43bab5a4afe89..5a195c813f390 100644 --- a/packages/kbn-test/src/functional_tests/cli/start_servers/cli.test.js +++ b/packages/kbn-test/src/functional_tests/cli/start_servers/cli.test.js @@ -17,6 +17,8 @@ * under the License. */ +import { Writable } from 'stream'; + import { startServersCli } from './cli'; import { checkMockConsoleLogSnapshot } from '../../test_helpers'; @@ -36,7 +38,7 @@ describe('start servers CLI', () => { const processMock = { exit: exitMock, argv: argvMock, - stdout: { on: jest.fn(), once: jest.fn(), emit: jest.fn() }, + stdout: new Writable(), cwd: jest.fn(), }; diff --git a/packages/kbn-test/src/functional_tests/lib/index.js b/packages/kbn-test/src/functional_tests/lib/index.js index d66f641c10f68..ec381b56b0699 100644 --- a/packages/kbn-test/src/functional_tests/lib/index.js +++ b/packages/kbn-test/src/functional_tests/lib/index.js @@ -19,6 +19,6 @@ export { runKibanaServer } from './run_kibana_server'; export { runElasticsearch } from './run_elasticsearch'; -export { runFtr } from './run_ftr'; +export { runFtr, hasTests, assertNoneExcluded } from './run_ftr'; export { KIBANA_ROOT, KIBANA_FTR_SCRIPT, FUNCTIONAL_CONFIG_PATH, API_CONFIG_PATH } from './paths'; export { runCli } from './run_cli'; diff --git a/packages/kbn-test/src/functional_tests/lib/run_ftr.js b/packages/kbn-test/src/functional_tests/lib/run_ftr.js index 779286212f5c7..05e5cbb3d4b55 100644 --- a/packages/kbn-test/src/functional_tests/lib/run_ftr.js +++ b/packages/kbn-test/src/functional_tests/lib/run_ftr.js @@ -17,14 +17,11 @@ * under the License. */ -import { createFunctionalTestRunner } from '../../../../../src/functional_test_runner'; +import * as FunctionalTestRunner from '../../../../../src/functional_test_runner'; import { CliError } from './run_cli'; -export async function runFtr({ - configPath, - options: { log, bail, grep, updateBaselines, suiteTags }, -}) { - const ftr = createFunctionalTestRunner({ +function createFtr({ configPath, options: { log, bail, grep, updateBaselines, suiteTags } }) { + return FunctionalTestRunner.createFunctionalTestRunner({ log, configFile: configPath, configOverrides: { @@ -36,6 +33,27 @@ export async function runFtr({ suiteTags, }, }); +} + +export async function assertNoneExcluded({ configPath, options }) { + const ftr = createFtr({ configPath, options }); + + const stats = await ftr.getTestStats(); + if (stats.excludedTests.length > 0) { + throw new CliError(` + ${stats.excludedTests.length} tests in the ${configPath} config + are excluded when filtering by the tags run on CI. Make sure that all suites are + tagged with one of the following tags, or extend the list of tags in test/scripts/jenkins_xpack.sh + + tags: ${JSON.stringify(options.suiteTags)} + + - ${stats.excludedTests.join('\n - ')} + `); + } +} + +export async function runFtr({ configPath, options }) { + const ftr = createFtr({ configPath, options }); const failureCount = await ftr.run(); if (failureCount > 0) { @@ -44,3 +62,9 @@ export async function runFtr({ ); } } + +export async function hasTests({ configPath, options }) { + const ftr = createFtr({ configPath, options }); + const stats = await ftr.getTestStats(); + return stats.testCount > 0; +} diff --git a/packages/kbn-test/src/functional_tests/tasks.js b/packages/kbn-test/src/functional_tests/tasks.js index 0cdcc77161a60..b7f22836ab769 100644 --- a/packages/kbn-test/src/functional_tests/tasks.js +++ b/packages/kbn-test/src/functional_tests/tasks.js @@ -17,12 +17,19 @@ * under the License. */ -import { relative, resolve } from 'path'; +import { relative } from 'path'; import * as Rx from 'rxjs'; import { startWith, switchMap, take } from 'rxjs/operators'; import { withProcRunner } from '@kbn/dev-utils'; -import { runElasticsearch, runKibanaServer, runFtr, KIBANA_FTR_SCRIPT } from './lib'; +import { + runElasticsearch, + runKibanaServer, + runFtr, + assertNoneExcluded, + hasTests, + KIBANA_FTR_SCRIPT, +} from './lib'; import { readConfigFile } from '../../../../src/functional_test_runner/lib'; @@ -38,37 +45,63 @@ in another terminal session by running this command from this directory: /** * Run servers and tests for each config * @param {object} options Optional - * @property {string[]} configPaths Array of paths to configs - * @property {function} options.createLogger Optional logger creation function + * @property {string[]} options.configs Array of paths to configs + * @property {function} options.log An instance of the ToolingLog * @property {string} options.installDir Optional installation dir from which to run Kibana * @property {boolean} options.bail Whether to exit test run at the first failure * @property {string} options.esFrom Optionally run from source instead of snapshot */ export async function runTests(options) { for (const configPath of options.configs) { - await runSingleConfig(resolve(process.cwd(), configPath), options); + const log = options.createLogger(); + const opts = { + ...options, + log, + }; + + log.info('Running', configPath); + log.indent(2); + + if (options.assertNoneExcluded) { + await assertNoneExcluded({ configPath, options: opts }); + continue; + } + + if (!(await hasTests({ configPath, options: opts }))) { + log.info('Skipping', configPath, 'since all tests are excluded'); + continue; + } + + await withProcRunner(log, async procs => { + const config = await readConfigFile(log, configPath); + + const es = await runElasticsearch({ config, options: opts }); + await runKibanaServer({ procs, config, options: opts }); + await runFtr({ configPath, options: opts }); + + await procs.stop('kibana'); + await es.cleanup(); + }); } } /** * Start only servers using single config * @param {object} options Optional - * @property {string} options.configPath Path to a config file - * @property {function} options.createLogger Optional logger creation function + * @property {string} options.config Path to a config file + * @property {function} options.log An instance of the ToolingLog * @property {string} options.installDir Optional installation dir from which to run Kibana * @property {string} options.esFrom Optionally run from source instead of snapshot */ export async function startServers(options) { - const { config: configOption, createLogger } = options; - const configPath = resolve(process.cwd(), configOption); - const log = createLogger(); + const log = options.createLogger(); const opts = { ...options, log, }; await withProcRunner(log, async procs => { - const config = await readConfigFile(log, configPath); + const config = await readConfigFile(log, options.config); const es = await runElasticsearch({ config, options: opts }); await runKibanaServer({ @@ -100,25 +133,3 @@ async function silence(milliseconds, { log }) { ) .toPromise(); } - -/* - * Start servers and run tests for single config - */ -async function runSingleConfig(configPath, options) { - const log = options.createLogger(); - const opts = { - ...options, - log, - }; - - await withProcRunner(log, async procs => { - const config = await readConfigFile(log, configPath); - - const es = await runElasticsearch({ config, options: opts }); - await runKibanaServer({ procs, config, options: opts }); - await runFtr({ configPath, options: opts }); - - await procs.stop('kibana'); - await es.cleanup(); - }); -} diff --git a/src/core/public/chrome/chrome_service.test.ts b/src/core/public/chrome/chrome_service.test.ts index 5840f826ba125..caaa588c2c9f8 100644 --- a/src/core/public/chrome/chrome_service.test.ts +++ b/src/core/public/chrome/chrome_service.test.ts @@ -202,19 +202,62 @@ Array [ "baz", ], ] +`); + }); + }); + + describe('breadcrumbs', () => { + it('updates/emits the current set of breadcrumbs', async () => { + const service = new ChromeService(); + const start = service.start(); + const promise = start + .getBreadcrumbs$() + .pipe(toArray()) + .toPromise(); + + start.setBreadcrumbs([{ text: 'foo' }, { text: 'bar' }]); + start.setBreadcrumbs([{ text: 'foo' }]); + start.setBreadcrumbs([{ text: 'bar' }]); + start.setBreadcrumbs([]); + service.stop(); + + await expect(promise).resolves.toMatchInlineSnapshot(` +Array [ + Array [], + Array [ + Object { + "text": "foo", + }, + Object { + "text": "bar", + }, + ], + Array [ + Object { + "text": "foo", + }, + ], + Array [ + Object { + "text": "bar", + }, + ], + Array [], +] `); }); }); }); describe('stop', () => { - it('completes applicationClass$, isCollapsed$, isVisible$, and brand$ observables', async () => { + it('completes applicationClass$, isCollapsed$, breadcrumbs$, isVisible$, and brand$ observables', async () => { const service = new ChromeService(); const start = service.start(); const promise = Rx.combineLatest( start.getBrand$(), start.getApplicationClasses$(), start.getIsCollapsed$(), + start.getBreadcrumbs$(), start.getIsVisible$() ).toPromise(); @@ -232,6 +275,7 @@ describe('stop', () => { start.getBrand$(), start.getApplicationClasses$(), start.getIsCollapsed$(), + start.getBreadcrumbs$(), start.getIsVisible$() ).toPromise() ).resolves.toBe(undefined); diff --git a/src/core/public/chrome/chrome_service.ts b/src/core/public/chrome/chrome_service.ts index 1e634aa42e2d8..8695385c9d20c 100644 --- a/src/core/public/chrome/chrome_service.ts +++ b/src/core/public/chrome/chrome_service.ts @@ -34,6 +34,11 @@ export interface Brand { smallLogo?: string; } +export interface Breadcrumb { + text: string; + href?: string; +} + export class ChromeService { private readonly stop$ = new Rx.ReplaySubject(1); @@ -44,6 +49,7 @@ export class ChromeService { const isVisible$ = new Rx.BehaviorSubject(true); const isCollapsed$ = new Rx.BehaviorSubject(!!localStorage.getItem(IS_COLLAPSED_KEY)); const applicationClasses$ = new Rx.BehaviorSubject>(new Set()); + const breadcrumbs$ = new Rx.BehaviorSubject([]); return { /** @@ -135,6 +141,18 @@ export class ChromeService { map(set => [...set]), takeUntil(this.stop$) ), + + /** + * Get an observable of the current list of breadcrumbs + */ + getBreadcrumbs$: () => breadcrumbs$.pipe(takeUntil(this.stop$)), + + /** + * Override the current set of breadcrumbs + */ + setBreadcrumbs: (newBreadcrumbs: Breadcrumb[]) => { + breadcrumbs$.next(newBreadcrumbs); + }, }; } diff --git a/src/core/public/chrome/index.ts b/src/core/public/chrome/index.ts index afc3d237ececb..ac54469e20bd4 100644 --- a/src/core/public/chrome/index.ts +++ b/src/core/public/chrome/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export { ChromeService, ChromeStartContract, Brand } from './chrome_service'; +export { Breadcrumb, ChromeService, ChromeStartContract, Brand } from './chrome_service'; diff --git a/src/core/public/legacy_platform/__snapshots__/legacy_platform_service.test.ts.snap b/src/core/public/legacy_platform/__snapshots__/legacy_platform_service.test.ts.snap index b4a3fb8eface6..a1474127605dd 100644 --- a/src/core/public/legacy_platform/__snapshots__/legacy_platform_service.test.ts.snap +++ b/src/core/public/legacy_platform/__snapshots__/legacy_platform_service.test.ts.snap @@ -11,6 +11,7 @@ Array [ "ui/chrome/api/injected_vars", "ui/chrome/api/controls", "ui/chrome/api/theme", + "ui/chrome/api/breadcrumbs", "ui/chrome/services/global_nav_state", "ui/chrome", "legacy files", @@ -28,6 +29,7 @@ Array [ "ui/chrome/api/injected_vars", "ui/chrome/api/controls", "ui/chrome/api/theme", + "ui/chrome/api/breadcrumbs", "ui/chrome/services/global_nav_state", "ui/test_harness", "legacy files", diff --git a/src/core/public/legacy_platform/legacy_platform_service.test.ts b/src/core/public/legacy_platform/legacy_platform_service.test.ts index 8abf529a2a6bf..957731fb7bcbf 100644 --- a/src/core/public/legacy_platform/legacy_platform_service.test.ts +++ b/src/core/public/legacy_platform/legacy_platform_service.test.ts @@ -110,6 +110,14 @@ jest.mock('ui/chrome/api/theme', () => { }; }); +const mockChromeBreadcrumbsInit = jest.fn(); +jest.mock('ui/chrome/api/breadcrumbs', () => { + mockLoadOrder.push('ui/chrome/api/breadcrumbs'); + return { + __newPlatformInit__: mockChromeBreadcrumbsInit, + }; +}); + const mockGlobalNavStateInit = jest.fn(); jest.mock('ui/chrome/services/global_nav_state', () => { mockLoadOrder.push('ui/chrome/services/global_nav_state'); @@ -272,6 +280,17 @@ describe('#start()', () => { expect(mockChromeThemeInit).toHaveBeenCalledWith(chromeStartContract); }); + it('passes chrome service to ui/chrome/api/breadcrumbs', () => { + const legacyPlatform = new LegacyPlatformService({ + ...defaultParams, + }); + + legacyPlatform.start(defaultStartDeps); + + expect(mockChromeBreadcrumbsInit).toHaveBeenCalledTimes(1); + expect(mockChromeBreadcrumbsInit).toHaveBeenCalledWith(chromeStartContract); + }); + it('passes chrome service to ui/chrome/api/global_nav_state', () => { const legacyPlatform = new LegacyPlatformService({ ...defaultParams, diff --git a/src/core/public/legacy_platform/legacy_platform_service.ts b/src/core/public/legacy_platform/legacy_platform_service.ts index 8354b9592f840..54bb912614cb2 100644 --- a/src/core/public/legacy_platform/legacy_platform_service.ts +++ b/src/core/public/legacy_platform/legacy_platform_service.ts @@ -72,6 +72,7 @@ export class LegacyPlatformService { require('ui/chrome/api/injected_vars').__newPlatformInit__(injectedMetadata); require('ui/chrome/api/controls').__newPlatformInit__(chrome); require('ui/chrome/api/theme').__newPlatformInit__(chrome); + require('ui/chrome/api/breadcrumbs').__newPlatformInit__(chrome); require('ui/chrome/services/global_nav_state').__newPlatformInit__(chrome); // Load the bootstrap module before loading the legacy platform files so that diff --git a/src/core_plugins/input_control_vis/public/components/vis/__snapshots__/input_control_vis.test.js.snap b/src/core_plugins/input_control_vis/public/components/vis/__snapshots__/input_control_vis.test.js.snap index bbc30723109ee..86fce47fa887f 100644 --- a/src/core_plugins/input_control_vis/public/components/vis/__snapshots__/input_control_vis.test.js.snap +++ b/src/core_plugins/input_control_vis/public/components/vis/__snapshots__/input_control_vis.test.js.snap @@ -28,19 +28,14 @@ exports[`Apply and Cancel change btns enabled when there are changes 1`] = ` controlIndex={0} disableMsg={null} fetchOptions={[Function]} + formatOptionLabel={[Function]} id="mock-list-control" label="list control" multiselect={true} options={ Array [ - Object { - "label": "choice1", - "value": "choice1", - }, - Object { - "label": "choice2", - "value": "choice2", - }, + "choice1", + "choice2", ] } selectedOptions={Array []} @@ -167,19 +162,14 @@ exports[`Clear btns enabled when there are values 1`] = ` controlIndex={0} disableMsg={null} fetchOptions={[Function]} + formatOptionLabel={[Function]} id="mock-list-control" label="list control" multiselect={true} options={ Array [ - Object { - "label": "choice1", - "value": "choice1", - }, - Object { - "label": "choice2", - "value": "choice2", - }, + "choice1", + "choice2", ] } selectedOptions={Array []} @@ -306,19 +296,14 @@ exports[`Renders list control 1`] = ` controlIndex={0} disableMsg={null} fetchOptions={[Function]} + formatOptionLabel={[Function]} id="mock-list-control" label="list control" multiselect={true} options={ Array [ - Object { - "label": "choice1", - "value": "choice1", - }, - Object { - "label": "choice2", - "value": "choice2", - }, + "choice1", + "choice2", ] } selectedOptions={Array []} @@ -444,6 +429,7 @@ exports[`Renders range control 1`] = ` { return value; }, }; const mockRangeControl = { id: 'mock-range-control', @@ -53,7 +51,8 @@ const mockRangeControl = { label: 'range control', value: { min: 0, max: 0 }, min: 0, - max: 100 + max: 100, + format: (value) => { return value; } }; const updateFiltersOnChange = false; diff --git a/src/core_plugins/input_control_vis/public/components/vis/list_control.js b/src/core_plugins/input_control_vis/public/components/vis/list_control.js index cdf81f6f36c3a..125596431ed3b 100644 --- a/src/core_plugins/input_control_vis/public/components/vis/list_control.js +++ b/src/core_plugins/input_control_vis/public/components/vis/list_control.js @@ -43,7 +43,10 @@ class ListControlUi extends Component { } handleOnChange = (selectedOptions) => { - this.props.stageFilter(this.props.controlIndex, selectedOptions); + const selectedValues = selectedOptions.map(({ value }) => { + return value; + }); + this.props.stageFilter(this.props.controlIndex, selectedValues); } debouncedFetch = _.debounce(async (searchValue) => { @@ -77,11 +80,22 @@ class ListControlUi extends Component { ); } - const options = this.props.options.map(option => { + const options = this.props.options + .map(option => { + return { + label: this.props.formatOptionLabel(option).toString(), + value: option, + ['data-test-subj']: `option_${option.toString().replace(' ', '_')}` + }; + }) + .sort((a, b) => { + return a.label.toLowerCase().localeCompare(b.label.toLowerCase()); + }); + + const selectedOptions = this.props.selectedOptions.map(selectedOption => { return { - label: option.label, - value: option.value, - ['data-test-subj']: `option_${option.value.replace(' ', '_')}` + label: this.props.formatOptionLabel(selectedOption).toString(), + value: selectedOption, }; }); @@ -95,7 +109,7 @@ class ListControlUi extends Component { isLoading={this.state.isLoading} async={this.props.dynamicOptions} onSearchChange={this.props.dynamicOptions ? this.onSearchChange : undefined} - selectedOptions={this.props.selectedOptions} + selectedOptions={selectedOptions} onChange={this.handleOnChange} singleSelection={!this.props.multiselect} data-test-subj={`listControlSelect${this.props.controlIndex}`} @@ -117,16 +131,12 @@ class ListControlUi extends Component { } } -const comboBoxOptionShape = PropTypes.shape({ - label: PropTypes.string.isRequired, - value: PropTypes.string.isRequired, -}); - ListControlUi.propTypes = { id: PropTypes.string.isRequired, label: PropTypes.string.isRequired, - selectedOptions: PropTypes.arrayOf(comboBoxOptionShape).isRequired, - options: PropTypes.arrayOf(comboBoxOptionShape), + selectedOptions: PropTypes.array.isRequired, + options: PropTypes.array, + formatOptionLabel: PropTypes.func.isRequired, disableMsg: PropTypes.string, multiselect: PropTypes.bool, dynamicOptions: PropTypes.bool, diff --git a/src/core_plugins/input_control_vis/public/components/vis/list_control.test.js b/src/core_plugins/input_control_vis/public/components/vis/list_control.test.js index e8b5c5fc8c814..729a35c4b9ed0 100644 --- a/src/core_plugins/input_control_vis/public/components/vis/list_control.test.js +++ b/src/core_plugins/input_control_vis/public/components/vis/list_control.test.js @@ -25,10 +25,11 @@ import { ListControl, } from './list_control'; -const options = [ - { label: 'choice1', value: 'choice1' }, - { label: 'choice2', value: 'choice2' } -]; +const options = ['choice1', 'choice2']; + +const formatOptionLabel = (value) => { + return `${value} + formatting`; +}; let stageFilter; @@ -45,6 +46,7 @@ test('renders ListControl', () => { multiselect={true} controlIndex={0} stageFilter={stageFilter} + formatOptionLabel={formatOptionLabel} />); expect(component).toMatchSnapshot(); // eslint-disable-line }); @@ -56,6 +58,7 @@ test('disableMsg', () => { multiselect={true} controlIndex={0} stageFilter={stageFilter} + formatOptionLabel={formatOptionLabel} disableMsg={'control is disabled to test rendering when disabled'} />); expect(component).toMatchSnapshot(); // eslint-disable-line diff --git a/src/core_plugins/input_control_vis/public/control/control.js b/src/core_plugins/input_control_vis/public/control/control.js index f8637c852a0f0..9a27a3d3f1dbd 100644 --- a/src/core_plugins/input_control_vis/public/control/control.js +++ b/src/core_plugins/input_control_vis/public/control/control.js @@ -60,7 +60,7 @@ export class Control { throw new Error('fetch method not defined, subclass are required to implement'); } - format(value) { + format = (value) => { const field = this.filterManager.getField(); if (field) { return field.format.convert(value); diff --git a/src/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.js b/src/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.js index 4c3a9265c3b67..fac023ab982a9 100644 --- a/src/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.js +++ b/src/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.js @@ -19,8 +19,7 @@ import _ from 'lodash'; import { FilterManager } from './filter_manager.js'; -import { buildPhraseFilter } from 'ui/filter_manager/lib/phrase'; -import { buildPhrasesFilter } from 'ui/filter_manager/lib/phrases'; +import { buildPhraseFilter, buildPhrasesFilter } from '@kbn/es-query'; export class PhraseFilterManager extends FilterManager { constructor(controlId, fieldName, indexPattern, queryFilter) { @@ -30,15 +29,12 @@ export class PhraseFilterManager extends FilterManager { /** * Convert phrases into filter * - * @param {array} + * @param {array} phrases * @return {object} query filter * single phrase: match query * multiple phrases: bool query with should containing list of match_phrase queries */ - createFilter(selectedOptions) { - const phrases = selectedOptions.map(phrase => { - return phrase.value; - }); + createFilter(phrases) { let newFilter; if (phrases.length === 1) { newFilter = buildPhraseFilter( @@ -74,10 +70,7 @@ export class PhraseFilterManager extends FilterManager { return values .reduce((accumulator, currentValue) => { return accumulator.concat(currentValue); - }, []) - .map(value => { - return { value, label: value }; - }); + }, []); } /** diff --git a/src/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.test.js b/src/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.test.js index 98b6eef1da51a..76519e0f58edc 100644 --- a/src/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.test.js +++ b/src/core_plugins/input_control_vis/public/control/filter_manager/phrase_filter_manager.test.js @@ -47,7 +47,7 @@ describe('PhraseFilterManager', function () { }); test('should create match phrase filter from single value', function () { - const newFilter = filterManager.createFilter([{ value: 'ios' }]); + const newFilter = filterManager.createFilter(['ios']); expect(newFilter).to.have.property('meta'); expect(newFilter.meta.index).to.be(indexPatternId); expect(newFilter.meta.controlledBy).to.be(controlId); @@ -56,7 +56,7 @@ describe('PhraseFilterManager', function () { }); test('should create bool filter from multiple values', function () { - const newFilter = filterManager.createFilter([{ value: 'ios' }, { value: 'win xp' }]); + const newFilter = filterManager.createFilter(['ios', 'win xp']); expect(newFilter).to.have.property('meta'); expect(newFilter.meta.index).to.be(indexPatternId); expect(newFilter.meta.controlledBy).to.be(controlId); @@ -102,7 +102,7 @@ describe('PhraseFilterManager', function () { } } ]); - expect(filterManager.getValueFromFilterBar()).to.eql([{ value: 'ios', label: 'ios' }]); + expect(filterManager.getValueFromFilterBar()).to.eql(['ios']); }); test('should extract value from multiple filters', function () { @@ -128,7 +128,7 @@ describe('PhraseFilterManager', function () { } }, ]); - expect(filterManager.getValueFromFilterBar()).to.eql([{ value: 'ios', label: 'ios' }, { value: 'win xp', label: 'win xp' }]); + expect(filterManager.getValueFromFilterBar()).to.eql(['ios', 'win xp']); }); test('should extract value from bool filter', function () { @@ -152,7 +152,7 @@ describe('PhraseFilterManager', function () { } } ]); - expect(filterManager.getValueFromFilterBar()).to.eql([{ value: 'ios', label: 'ios' }, { value: 'win xp', label: 'win xp' }]); + expect(filterManager.getValueFromFilterBar()).to.eql(['ios', 'win xp']); }); test('should return undefined when filter value can not be extracted from Kibana filter', function () { diff --git a/src/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.js b/src/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.js index 3ce646716f48c..3b23614f7be6d 100644 --- a/src/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.js +++ b/src/core_plugins/input_control_vis/public/control/filter_manager/range_filter_manager.js @@ -19,7 +19,7 @@ import _ from 'lodash'; import { FilterManager } from './filter_manager.js'; -import { buildRangeFilter } from 'ui/filter_manager/lib/range'; +import { buildRangeFilter } from '@kbn/es-query'; // Convert slider value into ES range filter function toRange(sliderValue) { diff --git a/src/core_plugins/input_control_vis/public/control/list_control_factory.js b/src/core_plugins/input_control_vis/public/control/list_control_factory.js index 4afe13619e59d..b15448f9b22df 100644 --- a/src/core_plugins/input_control_vis/public/control/list_control_factory.js +++ b/src/core_plugins/input_control_vis/public/control/list_control_factory.js @@ -131,9 +131,7 @@ class ListControl extends Control { } const selectOptions = _.get(resp, 'aggregations.termsAgg.buckets', []).map((bucket) => { - return { label: this.format(bucket.key), value: bucket.key.toString() }; - }).sort((a, b) => { - return a.label.toLowerCase().localeCompare(b.label.toLowerCase()); + return bucket.key; }); if(selectOptions.length === 0 && !query) { diff --git a/src/core_plugins/input_control_vis/public/control/list_control_factory.test.js b/src/core_plugins/input_control_vis/public/control/list_control_factory.test.js index ad2ea56756f3c..a28066948896c 100644 --- a/src/core_plugins/input_control_vis/public/control/list_control_factory.test.js +++ b/src/core_plugins/input_control_vis/public/control/list_control_factory.test.js @@ -119,16 +119,7 @@ describe('fetch', () => { test('should set selectOptions to results of terms aggregation', async () => { await listControl.fetch(); - expect(listControl.selectOptions).toEqual([ - { - 'label': 'Xi an Xianyang International Airport', - 'value': 'Xi an Xianyang International Airport', - }, - { - 'label': 'Zurich Airport', - 'value': 'Zurich Airport', - } - ]); + expect(listControl.selectOptions).toEqual(['Zurich Airport', 'Xi an Xianyang International Airport']); }); }); diff --git a/src/core_plugins/input_control_vis/public/register_vis.js b/src/core_plugins/input_control_vis/public/register_vis.js index 859ed40d9ed1a..50a8c4312171d 100644 --- a/src/core_plugins/input_control_vis/public/register_vis.js +++ b/src/core_plugins/input_control_vis/public/register_vis.js @@ -17,7 +17,6 @@ * under the License. */ -import { CATEGORY } from 'ui/vis/vis_category'; import { VisFactoryProvider } from 'ui/vis/vis_factory'; import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; import { VisController } from './vis_controller'; @@ -40,8 +39,7 @@ function InputControlVisProvider(Private) { description: i18n.translate('inputControl.register.controlsDescription', { defaultMessage: 'Create interactive controls for easy dashboard manipulation.' }), - category: CATEGORY.OTHER, - stage: 'lab', + stage: 'experimental', requiresUpdateStatus: [Status.PARAMS, Status.TIME], feedbackMessage: defaultFeedbackMessage, visualization: VisController, diff --git a/src/core_plugins/interpreter/common/constants.js b/src/core_plugins/interpreter/common/constants.js new file mode 100644 index 0000000000000..a5751ee72e826 --- /dev/null +++ b/src/core_plugins/interpreter/common/constants.js @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const SECURITY_AUTH_MESSAGE = 'Authentication failed'; +export const API_ROUTE = '/api/canvas'; diff --git a/src/core_plugins/interpreter/index.js b/src/core_plugins/interpreter/index.js new file mode 100644 index 0000000000000..61ff5e1395f10 --- /dev/null +++ b/src/core_plugins/interpreter/index.js @@ -0,0 +1,41 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { resolve } from 'path'; +import init from './init'; +import { pathsRegistry } from '@kbn/interpreter/common'; +import { pluginPaths } from '@kbn/interpreter/plugin_paths'; + +export default function (kibana) { + return new kibana.Plugin({ + id: 'interpreter', + require: ['kibana', 'elasticsearch'], + publicDir: resolve(__dirname, 'public'), + uiExports: { + hacks: [ + 'plugins/interpreter/load_browser_plugins.js', + ], + }, + preInit: () => { + pathsRegistry.registerAll(pluginPaths); + }, + init, + }); +} + diff --git a/src/core_plugins/interpreter/init.js b/src/core_plugins/interpreter/init.js new file mode 100644 index 0000000000000..c48c60ec4bd07 --- /dev/null +++ b/src/core_plugins/interpreter/init.js @@ -0,0 +1,42 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { routes } from './server/routes'; +import { functionsRegistry } from '@kbn/interpreter/common'; +import { populateServerRegistries } from '@kbn/interpreter/server'; + +export default async function (server /*options*/) { + server.injectUiAppVars('canvas', () => { + const config = server.config(); + const basePath = config.get('server.basePath'); + const reportingBrowserType = config.get('xpack.reporting.capture.browser.type'); + + return { + kbnIndex: config.get('kibana.index'), + esShardTimeout: config.get('elasticsearch.shardTimeout'), + esApiVersion: config.get('elasticsearch.apiVersion'), + serverFunctions: functionsRegistry.toArray(), + basePath, + reportingBrowserType, + }; + }); + + await populateServerRegistries(['serverFunctions', 'types']); + routes(server); +} diff --git a/src/core_plugins/interpreter/package.json b/src/core_plugins/interpreter/package.json new file mode 100644 index 0000000000000..3265dadd7fbfc --- /dev/null +++ b/src/core_plugins/interpreter/package.json @@ -0,0 +1,4 @@ +{ + "name": "interpreter", + "version": "kibana" +} diff --git a/src/core_plugins/interpreter/public/load_browser_plugins.js b/src/core_plugins/interpreter/public/load_browser_plugins.js new file mode 100644 index 0000000000000..de550f5c6a351 --- /dev/null +++ b/src/core_plugins/interpreter/public/load_browser_plugins.js @@ -0,0 +1,30 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import chrome from 'ui/chrome'; +import { populateBrowserRegistries } from '@kbn/interpreter/public'; +import { typesRegistry, functionsRegistry } from '@kbn/interpreter/common'; + +const types = { + commonFunctions: functionsRegistry, + browserFunctions: functionsRegistry, + types: typesRegistry +}; + +populateBrowserRegistries(types, chrome.getBasePath()); diff --git a/x-pack/plugins/canvas/server/lib/__tests__/create_handlers.js b/src/core_plugins/interpreter/server/lib/__tests__/create_handlers.js similarity index 83% rename from x-pack/plugins/canvas/server/lib/__tests__/create_handlers.js rename to src/core_plugins/interpreter/server/lib/__tests__/create_handlers.js index 9dbe0e413a1af..9afe458c444a7 100644 --- a/x-pack/plugins/canvas/server/lib/__tests__/create_handlers.js +++ b/src/core_plugins/interpreter/server/lib/__tests__/create_handlers.js @@ -1,12 +1,25 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import expect from 'expect.js'; import { createHandlers } from '../create_handlers'; -import { SECURITY_AUTH_MESSAGE } from '../../../common/lib/constants'; +import { SECURITY_AUTH_MESSAGE } from '../../../common/constants'; let securityMode = 'pass'; let isSecurityAvailable = true; diff --git a/x-pack/plugins/canvas/server/lib/create_handlers.js b/src/core_plugins/interpreter/server/lib/create_handlers.js similarity index 58% rename from x-pack/plugins/canvas/server/lib/create_handlers.js rename to src/core_plugins/interpreter/server/lib/create_handlers.js index f42f8fbe1a59d..9c4dcd112c928 100644 --- a/x-pack/plugins/canvas/server/lib/create_handlers.js +++ b/src/core_plugins/interpreter/server/lib/create_handlers.js @@ -1,12 +1,25 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import boom from 'boom'; -import { SECURITY_AUTH_MESSAGE } from '../../common/lib/constants'; import { isSecurityEnabled } from './feature_check'; +import { SECURITY_AUTH_MESSAGE } from '../../common/constants'; export const createHandlers = (request, server) => { const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); @@ -24,8 +37,9 @@ export const createHandlers = (request, server) => { if (isSecurityEnabled(server)) { try { const authenticationResult = await server.plugins.security.authenticate(request); - if (!authenticationResult.succeeded()) + if (!authenticationResult.succeeded()) { throw boom.unauthorized(authenticationResult.error); + } } catch (e) { // if authenticate throws, show error in development if (process.env.NODE_ENV !== 'production') { diff --git a/src/core_plugins/interpreter/server/lib/feature_check.js b/src/core_plugins/interpreter/server/lib/feature_check.js new file mode 100644 index 0000000000000..9f7a8993fa3ff --- /dev/null +++ b/src/core_plugins/interpreter/server/lib/feature_check.js @@ -0,0 +1,26 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// TODO: replace this when we use the method exposed by security https://github.com/elastic/kibana/pull/24616 +export const isSecurityEnabled = server => { + const kibanaSecurity = server.plugins.security; + const esSecurity = server.plugins.xpack_main.info.feature('security'); + + return kibanaSecurity && esSecurity.isAvailable() && esSecurity.isEnabled(); +}; diff --git a/src/core_plugins/interpreter/server/lib/get_plugin_stream.js b/src/core_plugins/interpreter/server/lib/get_plugin_stream.js new file mode 100644 index 0000000000000..3cfe2a89a612e --- /dev/null +++ b/src/core_plugins/interpreter/server/lib/get_plugin_stream.js @@ -0,0 +1,37 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import fs from 'fs'; +import ss from 'stream-stream'; +import { getPluginPaths } from '@kbn/interpreter/server'; + +export const getPluginStream = type => { + const stream = ss({ + separator: '\n', + }); + + getPluginPaths(type).then(files => { + files.forEach(file => { + stream.write(fs.createReadStream(file)); + }); + stream.end(); + }); + + return stream; +}; diff --git a/src/core_plugins/interpreter/server/lib/get_request.js b/src/core_plugins/interpreter/server/lib/get_request.js new file mode 100644 index 0000000000000..2b29b05fd07aa --- /dev/null +++ b/src/core_plugins/interpreter/server/lib/get_request.js @@ -0,0 +1,44 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import boom from 'boom'; +import { API_ROUTE } from '../../common/constants'; + +export function getRequest(server, { headers }) { + const url = `${API_ROUTE}/ping`; + + return server + .inject({ + method: 'POST', + url, + headers, + }) + .then(res => { + if (res.statusCode !== 200) { + if (process.env.NODE_ENV !== 'production') { + console.error( + new Error(`Auth request failed: [${res.statusCode}] ${res.result.message}`) + ); + } + throw boom.unauthorized('Failed to authenticate socket connection'); + } + + return res.request; + }); +} diff --git a/x-pack/plugins/canvas/server/lib/route_expression/browser.js b/src/core_plugins/interpreter/server/lib/route_expression/browser.js similarity index 65% rename from x-pack/plugins/canvas/server/lib/route_expression/browser.js rename to src/core_plugins/interpreter/server/lib/route_expression/browser.js index feae107873ac6..0fe27f4d27c68 100644 --- a/x-pack/plugins/canvas/server/lib/route_expression/browser.js +++ b/src/core_plugins/interpreter/server/lib/route_expression/browser.js @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import uuid from 'uuid/v4'; diff --git a/x-pack/plugins/canvas/server/lib/route_expression/index.js b/src/core_plugins/interpreter/server/lib/route_expression/index.js similarity index 52% rename from x-pack/plugins/canvas/server/lib/route_expression/index.js rename to src/core_plugins/interpreter/server/lib/route_expression/index.js index 3533b55687246..cd56b7e7bfe7e 100644 --- a/x-pack/plugins/canvas/server/lib/route_expression/index.js +++ b/src/core_plugins/interpreter/server/lib/route_expression/index.js @@ -1,9 +1,23 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ -import { createError } from '../../../common/interpreter/create_error'; + +import { createError } from '@kbn/interpreter/common'; export const routeExpressionProvider = environments => { async function routeExpression(ast, context = null) { diff --git a/src/core_plugins/interpreter/server/lib/route_expression/server.js b/src/core_plugins/interpreter/server/lib/route_expression/server.js new file mode 100644 index 0000000000000..23fe1487ddcca --- /dev/null +++ b/src/core_plugins/interpreter/server/lib/route_expression/server.js @@ -0,0 +1,40 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { getServerRegistries } from '@kbn/interpreter/server'; +import { interpretProvider } from '@kbn/interpreter/common'; +import { createHandlers } from '../create_handlers'; + +export const server = async ({ onFunctionNotFound, server, request }) => { + const { serverFunctions, types } = await getServerRegistries(['serverFunctions', 'types']); + + return { + interpret: (ast, context) => { + const interpret = interpretProvider({ + types: types.toJS(), + functions: serverFunctions.toJS(), + handlers: createHandlers(request, server), + onFunctionNotFound, + }); + + return interpret(ast, context); + }, + getFunctions: () => Object.keys(serverFunctions.toJS()), + }; +}; diff --git a/src/core_plugins/interpreter/server/lib/route_expression/thread/babeled.js b/src/core_plugins/interpreter/server/lib/route_expression/thread/babeled.js new file mode 100644 index 0000000000000..837aed5e78f7e --- /dev/null +++ b/src/core_plugins/interpreter/server/lib/route_expression/thread/babeled.js @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +require('../../../../../../setup_node_env'); +require('./worker'); diff --git a/x-pack/plugins/canvas/server/lib/route_expression/thread/index.js b/src/core_plugins/interpreter/server/lib/route_expression/thread/index.js similarity index 78% rename from x-pack/plugins/canvas/server/lib/route_expression/thread/index.js rename to src/core_plugins/interpreter/server/lib/route_expression/thread/index.js index d3748db02f65c..ff476793325e9 100644 --- a/x-pack/plugins/canvas/server/lib/route_expression/thread/index.js +++ b/src/core_plugins/interpreter/server/lib/route_expression/thread/index.js @@ -1,7 +1,20 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import { fork } from 'child_process'; diff --git a/x-pack/plugins/canvas/server/lib/route_expression/thread/worker.js b/src/core_plugins/interpreter/server/lib/route_expression/thread/worker.js similarity index 65% rename from x-pack/plugins/canvas/server/lib/route_expression/thread/worker.js rename to src/core_plugins/interpreter/server/lib/route_expression/thread/worker.js index d81df410f7af7..e26e3052aedc8 100644 --- a/x-pack/plugins/canvas/server/lib/route_expression/thread/worker.js +++ b/src/core_plugins/interpreter/server/lib/route_expression/thread/worker.js @@ -1,13 +1,25 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import uuid from 'uuid/v4'; -import { populateServerRegistries } from '../../server_registries'; -import { interpretProvider } from '../../../../common/interpreter/interpret'; -import { serializeProvider } from '../../../../common/lib/serialize'; +import { populateServerRegistries } from '@kbn/interpreter/server'; +import { interpretProvider, serializeProvider } from '@kbn/interpreter/common'; // We actually DO need populateServerRegistries here since this is a different node process const pluginsReady = populateServerRegistries(['commonFunctions', 'types']); @@ -44,8 +56,9 @@ process.on('message', msg => { }, }); - if (type === 'getFunctions') + if (type === 'getFunctions') { process.send({ type: 'functionList', value: Object.keys(commonFunctions.toJS()) }); + } if (type === 'msgSuccess') { heap[id].resolve(deserialize(value)); diff --git a/src/core_plugins/interpreter/server/routes/index.js b/src/core_plugins/interpreter/server/routes/index.js new file mode 100644 index 0000000000000..f78baf4ad496d --- /dev/null +++ b/src/core_plugins/interpreter/server/routes/index.js @@ -0,0 +1,28 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { socketApi } from './socket'; +import { translate } from './translate'; +import { plugins } from './plugins'; + +export function routes(server) { + plugins(server); + socketApi(server); + translate(server); +} diff --git a/src/core_plugins/interpreter/server/routes/plugins.js b/src/core_plugins/interpreter/server/routes/plugins.js new file mode 100644 index 0000000000000..3d8c8614cc107 --- /dev/null +++ b/src/core_plugins/interpreter/server/routes/plugins.js @@ -0,0 +1,35 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { getPluginStream } from '../lib/get_plugin_stream'; + +export function plugins(server) { + server.route({ + method: 'GET', + path: '/api/canvas/plugins', + handler: function (request) { + const { type } = request.query; + + return getPluginStream(type); + }, + config: { + auth: false, + }, + }); +} diff --git a/x-pack/plugins/canvas/server/routes/socket.js b/src/core_plugins/interpreter/server/routes/socket.js similarity index 70% rename from x-pack/plugins/canvas/server/routes/socket.js rename to src/core_plugins/interpreter/server/routes/socket.js index 8e06c25769d4c..80733690a7d11 100644 --- a/x-pack/plugins/canvas/server/routes/socket.js +++ b/src/core_plugins/interpreter/server/routes/socket.js @@ -1,19 +1,31 @@ /* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ import socket from 'socket.io'; -import { serializeProvider } from '../../common/lib/serialize'; -import { typesRegistry } from '../../common/lib/types_registry'; -import { getServerRegistries } from '../lib/server_registries'; -import { routeExpressionProvider } from '../lib/route_expression'; +import { serializeProvider, typesRegistry } from '@kbn/interpreter/common'; +import { getServerRegistries } from '@kbn/interpreter/server'; +import { routeExpressionProvider } from '../lib/route_expression/index'; import { browser } from '../lib/route_expression/browser'; -import { thread } from '../lib/route_expression/thread'; +import { thread } from '../lib/route_expression/thread/index'; import { server as serverEnv } from '../lib/route_expression/server'; import { getRequest } from '../lib/get_request'; -import { API_ROUTE } from '../../common/lib/constants'; +import { API_ROUTE } from '../../common/constants'; async function getModifiedRequest(server, socket) { try { diff --git a/src/core_plugins/interpreter/server/routes/translate.js b/src/core_plugins/interpreter/server/routes/translate.js new file mode 100644 index 0000000000000..01cd2a2401167 --- /dev/null +++ b/src/core_plugins/interpreter/server/routes/translate.js @@ -0,0 +1,48 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { fromExpression, toExpression } from '@kbn/interpreter/common'; + +export function translate(server) { + /* + Get AST from expression + */ + server.route({ + method: 'GET', + path: '/api/canvas/ast', + handler: function (request, h) { + if (!request.query.expression) { + return h.response({ error: '"expression" query is required' }).code(400); + } + return fromExpression(request.query.expression); + }, + }); + + server.route({ + method: 'POST', + path: '/api/canvas/expression', + handler: function (request, h) { + try { + return toExpression(request.payload); + } catch (e) { + return h.response({ error: e.message }).code(400); + } + }, + }); +} diff --git a/src/core_plugins/kbn_vislib_vis_types/public/area.js b/src/core_plugins/kbn_vislib_vis_types/public/area.js index 22910838e4258..475b1cb5c1c9a 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/area.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/area.js @@ -19,7 +19,6 @@ import { VisFactoryProvider } from 'ui/vis/vis_factory'; import { Schemas } from 'ui/vis/editors/default/schemas'; -import { CATEGORY } from 'ui/vis/vis_category'; import pointSeriesTemplate from './editors/point_series.html'; export default function PointSeriesVisType(Private, i18n) { @@ -30,7 +29,6 @@ export default function PointSeriesVisType(Private, i18n) { title: i18n('kbnVislibVisTypes.area.areaTitle', { defaultMessage: 'Area' }), icon: 'visArea', description: i18n('kbnVislibVisTypes.area.areaDescription', { defaultMessage: 'Emphasize the quantity beneath a line chart' }), - category: CATEGORY.BASIC, visConfig: { defaults: { type: 'area', diff --git a/src/core_plugins/kbn_vislib_vis_types/public/gauge.js b/src/core_plugins/kbn_vislib_vis_types/public/gauge.js index 708e2afff7265..ea0e9817b70b4 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/gauge.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/gauge.js @@ -19,7 +19,6 @@ import { VisFactoryProvider } from 'ui/vis/vis_factory'; import { Schemas } from 'ui/vis/editors/default/schemas'; -import { CATEGORY } from 'ui/vis/vis_category'; import gaugeTemplate from './editors/gauge.html'; import { vislibColorMaps } from 'ui/vislib/components/color/colormaps'; @@ -33,7 +32,6 @@ export default function GaugeVisType(Private, i18n) { description: i18n('kbnVislibVisTypes.gauge.gaugeDescription', { defaultMessage: 'Gauges indicate the status of a metric. Use it to show how a metric\'s value relates to reference threshold values.' }), - category: CATEGORY.DATA, visConfig: { defaults: { type: 'gauge', diff --git a/src/core_plugins/kbn_vislib_vis_types/public/goal.js b/src/core_plugins/kbn_vislib_vis_types/public/goal.js index 0826df5c2de98..78e212ff10b70 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/goal.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/goal.js @@ -19,7 +19,6 @@ import { VisFactoryProvider } from 'ui/vis/vis_factory'; import { Schemas } from 'ui/vis/editors/default/schemas'; -import { CATEGORY } from 'ui/vis/vis_category'; import gaugeTemplate from './editors/gauge.html'; import { vislibColorMaps } from 'ui/vislib/components/color/colormaps'; @@ -33,7 +32,6 @@ export default function GoalVisType(Private, i18n) { description: i18n('kbnVislibVisTypes.goal.goalDescription', { defaultMessage: 'A goal chart indicates how close you are to your final goal.' }), - category: CATEGORY.DATA, visConfig: { defaults: { addTooltip: true, diff --git a/src/core_plugins/kbn_vislib_vis_types/public/heatmap.js b/src/core_plugins/kbn_vislib_vis_types/public/heatmap.js index f8a5a64a26300..a62e6df930072 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/heatmap.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/heatmap.js @@ -19,7 +19,6 @@ import { VisFactoryProvider } from 'ui/vis/vis_factory'; import { Schemas } from 'ui/vis/editors/default/schemas'; -import { CATEGORY } from 'ui/vis/vis_category'; import heatmapTemplate from './editors/heatmap.html'; import { vislibColorMaps } from 'ui/vislib/components/color/colormaps'; @@ -31,7 +30,6 @@ export default function HeatmapVisType(Private, i18n) { title: i18n('kbnVislibVisTypes.heatmap.heatmapTitle', { defaultMessage: 'Heat Map' }), icon: 'visHeatmap', description: i18n('kbnVislibVisTypes.heatmap.heatmapDescription', { defaultMessage: 'Shade cells within a matrix' }), - category: CATEGORY.BASIC, visConfig: { defaults: { type: 'heatmap', diff --git a/src/core_plugins/kbn_vislib_vis_types/public/histogram.js b/src/core_plugins/kbn_vislib_vis_types/public/histogram.js index 5aa5ad8246bea..4f3e32ea135ee 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/histogram.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/histogram.js @@ -19,7 +19,6 @@ import { VisFactoryProvider } from 'ui/vis/vis_factory'; import { Schemas } from 'ui/vis/editors/default/schemas'; -import { CATEGORY } from 'ui/vis/vis_category'; import pointSeriesTemplate from './editors/point_series.html'; export default function PointSeriesVisType(Private, i18n) { @@ -32,7 +31,6 @@ export default function PointSeriesVisType(Private, i18n) { description: i18n('kbnVislibVisTypes.histogram.histogramDescription', { defaultMessage: 'Assign a continuous variable to each axis' } ), - category: CATEGORY.BASIC, visConfig: { defaults: { type: 'histogram', diff --git a/src/core_plugins/kbn_vislib_vis_types/public/horizontal_bar.js b/src/core_plugins/kbn_vislib_vis_types/public/horizontal_bar.js index 3bb14019b217c..94ee6056ff339 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/horizontal_bar.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/horizontal_bar.js @@ -19,7 +19,6 @@ import { VisFactoryProvider } from 'ui/vis/vis_factory'; import { Schemas } from 'ui/vis/editors/default/schemas'; -import { CATEGORY } from 'ui/vis/vis_category'; import pointSeriesTemplate from './editors/point_series.html'; export default function PointSeriesVisType(Private, i18n) { @@ -32,7 +31,6 @@ export default function PointSeriesVisType(Private, i18n) { description: i18n('kbnVislibVisTypes.horizontalBar.horizontalBarDescription', { defaultMessage: 'Assign a continuous variable to each axis' } ), - category: CATEGORY.BASIC, visConfig: { defaults: { type: 'histogram', diff --git a/src/core_plugins/kbn_vislib_vis_types/public/line.js b/src/core_plugins/kbn_vislib_vis_types/public/line.js index 886439eb57244..21855c6bfe3b5 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/line.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/line.js @@ -19,7 +19,6 @@ import { VisFactoryProvider } from 'ui/vis/vis_factory'; import { Schemas } from 'ui/vis/editors/default/schemas'; -import { CATEGORY } from 'ui/vis/vis_category'; import pointSeriesTemplate from './editors/point_series.html'; export default function PointSeriesVisType(Private, i18n) { @@ -30,7 +29,6 @@ export default function PointSeriesVisType(Private, i18n) { title: i18n('kbnVislibVisTypes.line.lineTitle', { defaultMessage: 'Line' }), icon: 'visLine', description: i18n('kbnVislibVisTypes.line.lineDescription', { defaultMessage: 'Emphasize trends' }), - category: CATEGORY.BASIC, visConfig: { defaults: { type: 'line', diff --git a/src/core_plugins/kbn_vislib_vis_types/public/pie.js b/src/core_plugins/kbn_vislib_vis_types/public/pie.js index 366cce5e2c939..70bfc2f2613c4 100644 --- a/src/core_plugins/kbn_vislib_vis_types/public/pie.js +++ b/src/core_plugins/kbn_vislib_vis_types/public/pie.js @@ -19,7 +19,6 @@ import { VisFactoryProvider } from 'ui/vis/vis_factory'; import { Schemas } from 'ui/vis/editors/default/schemas'; -import { CATEGORY } from 'ui/vis/vis_category'; import pieTemplate from './editors/pie.html'; export default function HistogramVisType(Private, i18n) { @@ -30,7 +29,6 @@ export default function HistogramVisType(Private, i18n) { title: i18n('kbnVislibVisTypes.pie.pieTitle', { defaultMessage: 'Pie' }), icon: 'visPie', description: i18n('kbnVislibVisTypes.pie.pieDescription', { defaultMessage: 'Compare parts of a whole' }), - category: CATEGORY.BASIC, visConfig: { defaults: { type: 'pie', diff --git a/src/core_plugins/kibana/public/dashboard/_index.scss b/src/core_plugins/kibana/public/dashboard/_index.scss index de00e0d670d22..98e918e507d92 100644 --- a/src/core_plugins/kibana/public/dashboard/_index.scss +++ b/src/core_plugins/kibana/public/dashboard/_index.scss @@ -41,8 +41,7 @@ @import 'panel/index'; @import 'viewport/index'; - // Vis imports -- will have some duplicate styling - // because they will be imported via ui/public as well - // (without .theme-[] prefix) - @import 'src/ui/public/vis/map/index'; + // Vis imports + @import 'src/ui/public/vis/index'; + @import 'src/ui/public/vislib/index'; } diff --git a/src/core_plugins/kibana/public/dashboard/components/__snapshots__/exit_full_screen_button.test.js.snap b/src/core_plugins/kibana/public/dashboard/components/__snapshots__/exit_full_screen_button.test.js.snap index e41e0bf21420d..2fe29dd29a59b 100644 --- a/src/core_plugins/kibana/public/dashboard/components/__snapshots__/exit_full_screen_button.test.js.snap +++ b/src/core_plugins/kibana/public/dashboard/components/__snapshots__/exit_full_screen_button.test.js.snap @@ -28,7 +28,7 @@ exports[`is rendered 1`] = ` class="dshExitFullScreenButton__text" data-test-subj="exitFullScreenModeText" > - Exit full screen + Exit full screen diff --git a/src/core_plugins/kibana/public/dashboard/components/exit_full_screen_button.js b/src/core_plugins/kibana/public/dashboard/components/exit_full_screen_button.js index 1d95ff0d09aac..d24d3a81017ae 100644 --- a/src/core_plugins/kibana/public/dashboard/components/exit_full_screen_button.js +++ b/src/core_plugins/kibana/public/dashboard/components/exit_full_screen_button.js @@ -20,6 +20,7 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import chrome from 'ui/chrome'; +import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { KuiButton, @@ -30,7 +31,7 @@ import { EuiScreenReaderOnly, } from '@elastic/eui'; -export class ExitFullScreenButton extends PureComponent { +class ExitFullScreenButtonUi extends PureComponent { onKeyDown = (e) => { if (e.keyCode === keyCodes.ESCAPE) { @@ -49,11 +50,16 @@ export class ExitFullScreenButton extends PureComponent { } render() { + const { intl } = this.props; + return (

- In full screen mode, press ESC to exit. +

- Exit full screen + +
@@ -76,6 +89,8 @@ export class ExitFullScreenButton extends PureComponent { } } -ExitFullScreenButton.propTypes = { +ExitFullScreenButtonUi.propTypes = { onExitFullScreenMode: PropTypes.func.isRequired, }; + +export const ExitFullScreenButton = injectI18n(ExitFullScreenButtonUi); diff --git a/src/core_plugins/kibana/public/dashboard/components/exit_full_screen_button.test.js b/src/core_plugins/kibana/public/dashboard/components/exit_full_screen_button.test.js index 0fb74f1a29ff9..80a52584cf9ba 100644 --- a/src/core_plugins/kibana/public/dashboard/components/exit_full_screen_button.test.js +++ b/src/core_plugins/kibana/public/dashboard/components/exit_full_screen_button.test.js @@ -24,7 +24,7 @@ jest.mock('ui/chrome', }), { virtual: true }); import React from 'react'; -import { render, mount } from 'enzyme'; +import { mountWithIntl, renderWithIntl } from 'test_utils/enzyme_helpers'; import sinon from 'sinon'; import chrome from 'ui/chrome'; @@ -36,8 +36,8 @@ import { keyCodes } from '@elastic/eui'; test('is rendered', () => { - const component = render( - {}}/> + const component = renderWithIntl( + {}}/> ); expect(component) @@ -48,8 +48,8 @@ describe('onExitFullScreenMode', () => { test('is called when the button is pressed', () => { const onExitHandler = sinon.stub(); - const component = mount( - + const component = mountWithIntl( + ); component.find('button').simulate('click'); @@ -60,7 +60,7 @@ describe('onExitFullScreenMode', () => { test('is called when the ESC key is pressed', () => { const onExitHandler = sinon.stub(); - mount(); + mountWithIntl(); const escapeKeyEvent = new KeyboardEvent('keydown', { keyCode: keyCodes.ESCAPE }); document.dispatchEvent(escapeKeyEvent); @@ -73,8 +73,8 @@ describe('chrome.setVisible', () => { test('is called with false when the component is rendered', () => { chrome.setVisible = sinon.stub(); - const component = mount( - {}} /> + const component = mountWithIntl( + {}} /> ); component.find('button').simulate('click'); @@ -84,8 +84,8 @@ describe('chrome.setVisible', () => { }); test('is called with true the component is unmounted', () => { - const component = mount( - {}} /> + const component = mountWithIntl( + {}} /> ); chrome.setVisible = sinon.stub(); diff --git a/src/core_plugins/kibana/public/dashboard/dashboard_app.html b/src/core_plugins/kibana/public/dashboard/dashboard_app.html index cc020f33e3dba..18c40bda026f4 100644 --- a/src/core_plugins/kibana/public/dashboard/dashboard_app.html +++ b/src/core_plugins/kibana/public/dashboard/dashboard_app.html @@ -15,7 +15,12 @@ aria-level="1" ng-if="showPluginBreadcrumbs">
- Dashboard +
{{ getDashTitle() }} @@ -46,22 +51,64 @@ ng-show="getShouldShowEditHelp()" class="dshStartScreen" > -

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

-

- Click the Add button in the menu bar above to add a visualization to the dashboard.
If you haven't set up any visualizations yet, visit the Visualize app to create your first visualization. +

+ + +

-

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

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

diff --git a/src/core_plugins/kibana/public/dashboard/dashboard_app.js b/src/core_plugins/kibana/public/dashboard/dashboard_app.js index 6a9a903eef71a..0b0ea54da372f 100644 --- a/src/core_plugins/kibana/public/dashboard/dashboard_app.js +++ b/src/core_plugins/kibana/public/dashboard/dashboard_app.js @@ -37,7 +37,6 @@ import { FilterBarQueryFilterProvider } from 'ui/filter_bar/query_filter'; import { DocTitleProvider } from 'ui/doc_title'; import { getTopNavConfig } from './top_nav/get_top_nav_config'; import { DashboardConstants, createDashboardEditUrl } from './dashboard_constants'; -import { VisualizeConstants } from '../visualize/visualize_constants'; import { DashboardStateManager } from './dashboard_state_manager'; import { saveDashboard } from './lib'; import { showCloneModal } from './top_nav/show_clone_modal'; @@ -45,6 +44,7 @@ import { showSaveModal } from 'ui/saved_objects/show_saved_object_save_modal'; import { DashboardSaveModal } from './top_nav/save_modal'; import { showAddPanel } from './top_nav/show_add_panel'; import { showOptionsPopover } from './top_nav/show_options_popover'; +import { showNewVisModal } from '../visualize/wizard'; import { showShareContextMenu, ShareContextMenuExtensionsRegistryProvider } from 'ui/share'; import { migrateLegacyQuery } from 'ui/utils/migrate_legacy_query'; import * as filterActions from 'ui/doc_table/actions/filter'; @@ -56,7 +56,6 @@ import { timefilter } from 'ui/timefilter'; import { getUnhashableStatesProvider } from 'ui/state_management/state_hashing'; import { DashboardViewportProvider } from './viewport/dashboard_viewport_provider'; -import { i18n } from '@kbn/i18n'; const app = uiModules.get('app/dashboard', [ 'elasticsearch', @@ -86,11 +85,10 @@ app.directive('dashboardApp', function ($injector) { $rootScope, $route, $routeParams, - $location, getAppState, dashboardConfig, localStorage, - breadcrumbState, + i18n, ) { const filterManager = Private(FilterManagerProvider); const filterBar = Private(FilterBarQueryFilterProvider); @@ -182,9 +180,9 @@ app.directive('dashboardApp', function ($injector) { // Push breadcrumbs to new header navigation const updateBreadcrumbs = () => { - breadcrumbState.set([ + chrome.breadcrumbs.set([ { - text: i18n.translate('kbn.dashboard.dashboardAppBreadcrumbsTitle', { + text: i18n('kbn.dashboard.dashboardAppBreadcrumbsTitle', { defaultMessage: 'Dashboard', }), href: $scope.landingPageUrl() @@ -273,14 +271,22 @@ app.directive('dashboardApp', function ($injector) { } confirmModal( - `Once you discard your changes, there's no getting them back.`, + i18n('kbn.dashboard.changeViewModeConfirmModal.discardChangesDescription', + { defaultMessage: `Once you discard your changes, there's no getting them back.` } + ), { onConfirm: revertChangesAndExitEditMode, onCancel: _.noop, - confirmButtonText: 'Discard changes', - cancelButtonText: 'Continue editing', + confirmButtonText: i18n('kbn.dashboard.changeViewModeConfirmModal.confirmButtonLabel', + { defaultMessage: 'Discard changes' } + ), + cancelButtonText: i18n('kbn.dashboard.changeViewModeConfirmModal.cancelButtonLabel', + { defaultMessage: 'Continue editing' } + ), defaultFocusedButton: ConfirmationButtonTypes.CANCEL, - title: 'Discard changes to dashboard?' + title: i18n('kbn.dashboard.changeViewModeConfirmModal.discardChangesTitle', + { defaultMessage: 'Discard changes to dashboard?' } + ) } ); }; @@ -302,7 +308,12 @@ app.directive('dashboardApp', function ($injector) { .then(function (id) { if (id) { toastNotifications.addSuccess({ - title: `Dashboard '${dash.title}' was saved`, + title: i18n('kbn.dashboard.dashboardWasSavedSuccessMessage', + { + defaultMessage: `Dashboard '{dashTitle}' was saved`, + values: { dashTitle: dash.title }, + }, + ), 'data-test-subj': 'saveDashboardSuccess', }); @@ -316,7 +327,15 @@ app.directive('dashboardApp', function ($injector) { return { id }; }).catch((error) => { toastNotifications.addDanger({ - title: `Dashboard '${dash.title}' was not saved. Error: ${error.message}`, + title: i18n('kbn.dashboard.dashboardWasNotSavedDangerMessage', + { + defaultMessage: `Dashboard '{dashTitle}' was not saved. Error: {errorMessage}`, + values: { + dashTitle: dash.title, + errorMessage: error.message, + }, + }, + ), 'data-test-subj': 'saveDashboardFailure', }); return { error }; @@ -398,10 +417,7 @@ app.directive('dashboardApp', function ($injector) { }; navActions[TopNavIds.ADD] = () => { const addNewVis = () => { - kbnUrl.change( - `${VisualizeConstants.WIZARD_STEP_1_PAGE_PATH}?${DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM}`); - // Function is called outside of angular. Must apply digest cycle to trigger URL update - $scope.$apply(); + showNewVisModal(visTypes, { editorParams: [DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM] }); }; showAddPanel(dashboardStateManager.addNewPanel, addNewVis, visTypes); diff --git a/src/core_plugins/kibana/public/dashboard/dashboard_state_manager.js b/src/core_plugins/kibana/public/dashboard/dashboard_state_manager.js index a9a9372318592..aea6ba3b96426 100644 --- a/src/core_plugins/kibana/public/dashboard/dashboard_state_manager.js +++ b/src/core_plugins/kibana/public/dashboard/dashboard_state_manager.js @@ -17,6 +17,7 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; import _ from 'lodash'; import moment from 'moment'; @@ -550,7 +551,9 @@ export class DashboardStateManager { */ syncTimefilterWithDashboard(timeFilter, quickTimeRanges) { if (!this.getIsTimeSavedWithDashboard()) { - throw new Error('The time is not saved with this dashboard so should not be synced.'); + throw new Error(i18n.translate('kbn.dashboard.stateManager.timeNotSavedWithDashboardErrorMessage', { + defaultMessage: 'The time is not saved with this dashboard so should not be synced.', + })); } let mode; diff --git a/src/core_plugins/kibana/public/dashboard/dashboard_strings.js b/src/core_plugins/kibana/public/dashboard/dashboard_strings.js index 7dffc94c9e97d..a0d2af3e9c00a 100644 --- a/src/core_plugins/kibana/public/dashboard/dashboard_strings.js +++ b/src/core_plugins/kibana/public/dashboard/dashboard_strings.js @@ -17,6 +17,7 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; import { DashboardViewMode } from './dashboard_view_mode'; /** @@ -28,10 +29,21 @@ import { DashboardViewMode } from './dashboard_view_mode'; */ export function getDashboardTitle(title, viewMode, isDirty) { const isEditMode = viewMode === DashboardViewMode.EDIT; - const unsavedSuffix = isEditMode && isDirty - ? ' (unsaved)' - : ''; + let displayTitle; - const displayTitle = `${title}${unsavedSuffix}`; - return isEditMode ? 'Editing ' + displayTitle : displayTitle; + if (isEditMode && isDirty) { + displayTitle = i18n.translate('kbn.dashboard.strings.dashboardUnsavedEditTitle', { + defaultMessage: 'Editing {title} (unsaved)', + values: { title }, + }); + } else if (isEditMode) { + displayTitle = i18n.translate('kbn.dashboard.strings.dashboardEditTitle', { + defaultMessage: 'Editing {title}', + values: { title }, + }); + } else { + displayTitle = title; + } + + return displayTitle; } diff --git a/src/core_plugins/kibana/public/dashboard/grid/__snapshots__/dashboard_grid.test.js.snap b/src/core_plugins/kibana/public/dashboard/grid/__snapshots__/dashboard_grid.test.js.snap index a098c22af2363..806e11c557a02 100644 --- a/src/core_plugins/kibana/public/dashboard/grid/__snapshots__/dashboard_grid.test.js.snap +++ b/src/core_plugins/kibana/public/dashboard/grid/__snapshots__/dashboard_grid.test.js.snap @@ -33,7 +33,7 @@ exports[`renders DashboardGrid 1`] = ` } } > - - { }); test('renders DashboardGrid', () => { - const component = shallow(); + const component = shallowWithIntl(); expect(component).toMatchSnapshot(); - const panelElements = component.find('Connect(DashboardPanel)'); + const panelElements = component.find('Connect(InjectIntl(DashboardPanelUi))'); expect(panelElements.length).toBe(2); }); test('renders DashboardGrid with no visualizations', () => { - const component = shallow(); + const component = shallowWithIntl(); expect(component).toMatchSnapshot(); }); test('adjusts z-index of focused panel to be higher than siblings', () => { - const component = shallow(); - const panelElements = component.find('Connect(DashboardPanel)'); + const component = shallowWithIntl(); + const panelElements = component.find('Connect(InjectIntl(DashboardPanelUi))'); panelElements.first().prop('onPanelFocused')('1'); const [gridItem1, gridItem2] = component.update().findWhere(el => el.key() === '1' || el.key() === '2'); expect(gridItem1.props.style.zIndex).toEqual('2'); diff --git a/src/core_plugins/kibana/public/dashboard/grid/dashboard_grid_container.test.js b/src/core_plugins/kibana/public/dashboard/grid/dashboard_grid_container.test.js index 1a7e3db048683..12d5d42c811ff 100644 --- a/src/core_plugins/kibana/public/dashboard/grid/dashboard_grid_container.test.js +++ b/src/core_plugins/kibana/public/dashboard/grid/dashboard_grid_container.test.js @@ -18,7 +18,7 @@ */ import React from 'react'; -import { mount } from 'enzyme'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { Provider } from 'react-redux'; import _ from 'lodash'; import sizeMe from 'react-sizeme'; @@ -94,7 +94,7 @@ test('loads old panel data in the right order', () => { store.dispatch(updatePanels(panelData)); store.dispatch(updateUseMargins(false)); - const grid = mount(); + const grid = mountWithIntl(); const panels = store.getState().dashboard.panels; expect(Object.keys(panels).length).toBe(16); @@ -130,7 +130,7 @@ test('loads old panel data in the right order with margins', () => { store.dispatch(updatePanels(panelData)); store.dispatch(updateUseMargins(true)); - const grid = mount(); + const grid = mountWithIntl(); const panels = store.getState().dashboard.panels; expect(Object.keys(panels).length).toBe(16); diff --git a/src/core_plugins/kibana/public/dashboard/index.js b/src/core_plugins/kibana/public/dashboard/index.js index 3c302ec41bb67..72280b7bc9d71 100644 --- a/src/core_plugins/kibana/public/dashboard/index.js +++ b/src/core_plugins/kibana/public/dashboard/index.js @@ -17,10 +17,12 @@ * under the License. */ +import { injectI18nProvider } from '@kbn/i18n/react'; import './dashboard_app'; import './saved_dashboard/saved_dashboards'; import './dashboard_config'; import uiRoutes from 'ui/routes'; +import chrome from 'ui/chrome'; import { toastNotifications } from 'ui/notify'; import dashboardTemplate from './dashboard_app.html'; @@ -34,7 +36,6 @@ import { recentlyAccessed } from 'ui/persisted_log'; import { SavedObjectRegistryProvider } from 'ui/saved_objects/saved_object_registry'; import { DashboardListing, EMPTY_FILTER } from './listing/dashboard_listing'; import { uiModules } from 'ui/modules'; -import { i18n } from '@kbn/i18n'; const app = uiModules.get('app/dashboard', [ 'ngRoute', @@ -42,16 +43,22 @@ const app = uiModules.get('app/dashboard', [ ]); app.directive('dashboardListing', function (reactDirective) { - return reactDirective(DashboardListing); + return reactDirective(injectI18nProvider(DashboardListing)); }); +function createNewDashboardCtrl($scope, i18n) { + $scope.visitVisualizeAppLinkText = i18n('kbn.dashboard.visitVisualizeAppLinkText', { + defaultMessage: 'visit the Visualize app', + }); +} + uiRoutes .defaults(/dashboard/, { requireDefaultIndex: true }) .when(DashboardConstants.LANDING_PAGE_PATH, { template: dashboardListingTemplate, - controller($injector, $location, $scope, Private, config, breadcrumbState) { + controller($injector, $location, $scope, Private, config, i18n) { const services = Private(SavedObjectRegistryProvider).byLoaderPropertiesName; const dashboardConfig = $injector.get('dashboardConfig'); @@ -64,8 +71,8 @@ uiRoutes }; $scope.hideWriteControls = dashboardConfig.getHideWriteControls(); $scope.initialFilter = ($location.search()).filter || EMPTY_FILTER; - breadcrumbState.set([{ - text: i18n.translate('kbn.dashboard.dashboardBreadcrumbsTitle', { + chrome.breadcrumbs.set([{ + text: i18n('kbn.dashboard.dashboardBreadcrumbsTitle', { defaultMessage: 'Dashboards', }), }]); @@ -98,6 +105,7 @@ uiRoutes }) .when(DashboardConstants.CREATE_NEW_DASHBOARD_URL, { template: dashboardTemplate, + controller: createNewDashboardCtrl, resolve: { dash: function (savedDashboards, redirectWhenMissing) { return savedDashboards.get() @@ -109,8 +117,9 @@ uiRoutes }) .when(createDashboardEditUrl(':id'), { template: dashboardTemplate, + controller: createNewDashboardCtrl, resolve: { - dash: function (savedDashboards, Notifier, $route, $location, redirectWhenMissing, kbnUrl, AppState) { + dash: function (savedDashboards, Notifier, $route, $location, redirectWhenMissing, kbnUrl, AppState, i18n) { const id = $route.current.params.id; return savedDashboards.get(id) @@ -131,7 +140,9 @@ uiRoutes if (error instanceof SavedObjectNotFound && id === 'create') { // Note "new AppState" is necessary so the state in the url is preserved through the redirect. kbnUrl.redirect(DashboardConstants.CREATE_NEW_DASHBOARD_URL, {}, new AppState()); - toastNotifications.addWarning('The url "dashboard/create" was removed in 6.0. Please update your bookmarks.'); + toastNotifications.addWarning(i18n('kbn.dashboard.urlWasRemovedInSixZeroWarningMessage', + { defaultMessage: 'The url "dashboard/create" was removed in 6.0. Please update your bookmarks.' } + )); } else { throw error; } @@ -143,11 +154,15 @@ uiRoutes } }); -FeatureCatalogueRegistryProvider.register(() => { +FeatureCatalogueRegistryProvider.register((i18n) => { return { id: 'dashboard', - title: 'Dashboard', - description: 'Display and share a collection of visualizations and saved searches.', + title: i18n('kbn.dashboard.featureCatalogue.dashboardTitle', { + defaultMessage: 'Dashboard', + }), + description: i18n('kbn.dashboard.featureCatalogue.dashboardDescription', { + defaultMessage: 'Display and share a collection of visualizations and saved searches.', + }), icon: 'dashboardApp', path: `/app/kibana#${DashboardConstants.LANDING_PAGE_PATH}`, showOnHomePage: true, diff --git a/src/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap b/src/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap index cd45b2ae64b10..e350b43c4028f 100644 --- a/src/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap +++ b/src/core_plugins/kibana/public/dashboard/listing/__snapshots__/dashboard_listing.test.js.snap @@ -22,7 +22,11 @@ exports[`after fetch hideWriteControls 1`] = ` color="subdued" component="span" > - Looks like you don't have any dashboards. + @@ -64,7 +68,11 @@ exports[`after fetch initialFilter 1`] = ` textTransform="none" >

- Dashboards +

@@ -80,7 +88,11 @@ exports[`after fetch initialFilter 1`] = ` iconSide="left" type="button" > - Create new dashboard + @@ -108,7 +120,7 @@ exports[`after fetch initialFilter 1`] = ` incremental={false} isLoading={false} onChange={[Function]} - placeholder="Search..." + placeholder="Search…" value="my dashboard" /> @@ -157,7 +169,13 @@ exports[`after fetch initialFilter 1`] = ` ] } loading={false} - noItemsMessage="No dashboards matched your search." + noItemsMessage={ + + } onChange={[Function]} pagination={ Object { @@ -210,24 +228,42 @@ exports[`after fetch renders call to action when no dashboards exist 1`] = ` iconType="plusInCircle" type="button" > - Create new dashboard + } body={

- You can combine data views from any Kibana app into one dashboard and see everything in one place. +

- New to Kibana? - - Install some sample data - - to take a test drive. + + + , + } + } + />

} @@ -235,7 +271,11 @@ exports[`after fetch renders call to action when no dashboards exist 1`] = ` iconType="dashboardApp" title={

- Create your first dashboard +

} /> @@ -278,7 +318,11 @@ exports[`after fetch renders table rows 1`] = ` textTransform="none" >

- Dashboards +

@@ -294,7 +338,11 @@ exports[`after fetch renders table rows 1`] = ` iconSide="left" type="button" > - Create new dashboard + @@ -322,7 +370,7 @@ exports[`after fetch renders table rows 1`] = ` incremental={false} isLoading={false} onChange={[Function]} - placeholder="Search..." + placeholder="Search…" value="" /> @@ -371,7 +419,13 @@ exports[`after fetch renders table rows 1`] = ` ] } loading={false} - noItemsMessage="No dashboards matched your search." + noItemsMessage={ + + } onChange={[Function]} pagination={ Object { @@ -432,7 +486,11 @@ exports[`after fetch renders warning when listingLimit is exceeded 1`] = ` textTransform="none" >

- Dashboards +

@@ -448,7 +506,11 @@ exports[`after fetch renders warning when listingLimit is exceeded 1`] = ` iconSide="left" type="button" > - Create new dashboard + @@ -460,26 +522,39 @@ exports[`after fetch renders warning when listingLimit is exceeded 1`] = ` color="warning" iconType="help" size="m" - title="Listing limit exceeded" + title={ + + } >

- You have - 2 - dashboards, but your - - listingLimit - - setting prevents the table below from displaying more than - 1 - . You can change this setting under - - Advanced Settings - - . + + + , + "listingLimitText": + listingLimit + , + "listingLimitValue": 1, + "totalDashboards": 2, + } + } + />

@@ -556,7 +631,13 @@ exports[`after fetch renders warning when listingLimit is exceeded 1`] = ` ] } loading={false} - noItemsMessage="No dashboards matched your search." + noItemsMessage={ + + } onChange={[Function]} pagination={ Object { diff --git a/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js b/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js index 2a2aef320880a..b7af6a616ba26 100644 --- a/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js +++ b/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.js @@ -19,6 +19,7 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; +import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import _ from 'lodash'; import { toastNotifications } from 'ui/notify'; import { @@ -49,7 +50,7 @@ export const EMPTY_FILTER = ''; // and not supporting server-side paging. // This component does not try to tackle these problems (yet) and is just feature matching the legacy component // TODO support server side sorting/paging once title and description are sortable on the server. -export class DashboardListing extends React.Component { +class DashboardListingUi extends React.Component { constructor(props) { super(props); @@ -111,7 +112,12 @@ export class DashboardListing extends React.Component { await this.props.delete(this.state.selectedIds); } catch (error) { toastNotifications.addDanger({ - title: `Unable to delete dashboard(s)`, + title: ( + + ), text: `${error}`, }); } @@ -194,14 +200,34 @@ export class DashboardListing extends React.Component { return ( + } onCancel={this.closeDeleteModal} onConfirm={this.deleteSelectedItems} - cancelButtonText="Cancel" - confirmButtonText="Delete" + cancelButtonText={ + + } + confirmButtonText={ + + } defaultFocusedButton="cancel" > -

{`You can't recover deleted dashboards.`}

+

+ +

); @@ -212,14 +238,38 @@ export class DashboardListing extends React.Component { return ( + } color="warning" iconType="help" >

- You have {this.state.totalDashboards} dashboards, - but your listingLimit setting prevents the table below from displaying more than {this.props.listingLimit}. - You can change this setting under Advanced Settings. + + listingLimit + + ), + advancedSettingsLink: ( + + + + ) + }} + />

@@ -233,7 +283,12 @@ export class DashboardListing extends React.Component { return ''; } - return 'No dashboards matched your search.'; + return ( + + ); } renderNoItemsMessage() { @@ -243,7 +298,10 @@ export class DashboardListing extends React.Component {

- {`Looks like you don't have any dashboards.`} +

@@ -254,14 +312,37 @@ export class DashboardListing extends React.Component {
Create your first dashboard} + title={ +

+ +

+ } body={

- You can combine data views from any Kibana app into one dashboard and see everything in one place. +

- New to Kibana? Install some sample data to take a test drive. + + + + ), + }} + />

} @@ -272,7 +353,10 @@ export class DashboardListing extends React.Component { iconType="plusInCircle" data-test-subj="createDashboardPromptButton" > - Create new dashboard + } /> @@ -282,6 +366,7 @@ export class DashboardListing extends React.Component { } renderSearchBar() { + const { intl } = this.props; let deleteBtn; if (this.state.selectedIds.length > 0) { deleteBtn = ( @@ -292,7 +377,10 @@ export class DashboardListing extends React.Component { data-test-subj="deleteSelectedDashboards" key="delete" > - Delete selected + ); @@ -303,8 +391,14 @@ export class DashboardListing extends React.Component { {deleteBtn} { @@ -320,10 +414,14 @@ export class DashboardListing extends React.Component { } renderTable() { + const { intl } = this.props; const tableColumns = [ { field: 'title', - name: 'Title', + name: intl.formatMessage({ + id: 'kbn.dashboard.listing.table.titleColumnName', + defaultMessage: 'Title', + }), sortable: true, render: (field, record) => ( { @@ -351,7 +455,10 @@ export class DashboardListing extends React.Component { - Edit + ); } @@ -413,7 +520,10 @@ export class DashboardListing extends React.Component { href={`#${DashboardConstants.CREATE_NEW_DASHBOARD_URL}`} data-test-subj="newDashboardLink" > - Create new dashboard + ); @@ -426,7 +536,10 @@ export class DashboardListing extends React.Component {

- Dashboards +

@@ -471,7 +584,7 @@ export class DashboardListing extends React.Component { } } -DashboardListing.propTypes = { +DashboardListingUi.propTypes = { find: PropTypes.func.isRequired, delete: PropTypes.func.isRequired, listingLimit: PropTypes.number.isRequired, @@ -479,6 +592,8 @@ DashboardListing.propTypes = { initialFilter: PropTypes.string, }; -DashboardListing.defaultProps = { +DashboardListingUi.defaultProps = { initialFilter: EMPTY_FILTER, }; + +export const DashboardListing = injectI18n(DashboardListingUi); diff --git a/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.test.js b/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.test.js index e1a95b65982a6..9fcb0c6bab90b 100644 --- a/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.test.js +++ b/src/core_plugins/kibana/public/dashboard/listing/dashboard_listing.test.js @@ -36,7 +36,7 @@ jest.mock('lodash', }), { virtual: true }); import React from 'react'; -import { shallow } from 'enzyme'; +import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { DashboardListing, @@ -58,7 +58,7 @@ const find = (num) => { }; test('renders empty page in before initial fetch to avoid flickering', () => { - const component = shallow( {}} listingLimit={1000} @@ -69,7 +69,7 @@ test('renders empty page in before initial fetch to avoid flickering', () => { describe('after fetch', () => { test('initialFilter', async () => { - const component = shallow( {}} listingLimit={1000} @@ -86,7 +86,7 @@ describe('after fetch', () => { }); test('renders table rows', async () => { - const component = shallow( {}} listingLimit={1000} @@ -102,7 +102,7 @@ describe('after fetch', () => { }); test('renders call to action when no dashboards exist', async () => { - const component = shallow( {}} listingLimit={1} @@ -118,7 +118,7 @@ describe('after fetch', () => { }); test('hideWriteControls', async () => { - const component = shallow( {}} listingLimit={1} @@ -134,7 +134,7 @@ describe('after fetch', () => { }); test('renders warning when listingLimit is exceeded', async () => { - const component = shallow( {}} listingLimit={1} diff --git a/src/core_plugins/kibana/public/dashboard/panel/_dashboard_panel.scss b/src/core_plugins/kibana/public/dashboard/panel/_dashboard_panel.scss index 6efb9d4f25b0d..70a4b0e76a669 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/_dashboard_panel.scss +++ b/src/core_plugins/kibana/public/dashboard/panel/_dashboard_panel.scss @@ -35,7 +35,7 @@ @include euiScrollBar; /* 3 */ } - .visualization .vis-container { + .visualization .visChart__container { overflow: visible; /* 2 */ } } diff --git a/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.js b/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.js index 684fe6c54a223..1ec58d1971541 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.js +++ b/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.js @@ -19,6 +19,7 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import classNames from 'classnames'; import _ from 'lodash'; @@ -29,11 +30,14 @@ import { EuiPanel, } from '@elastic/eui'; -export class DashboardPanel extends React.Component { +class DashboardPanelUi extends React.Component { constructor(props) { super(props); this.state = { - error: props.embeddableFactory ? null : `No factory found for embeddable`, + error: props.embeddableFactory ? null : props.intl.formatMessage({ + id: 'kbn.dashboard.panel.noEmbeddableFactoryErrorMessage', + defaultMessage: 'No factory found for embeddable', + }), }; this.mounted = false; @@ -100,7 +104,10 @@ export class DashboardPanel extends React.Component { className="panel-content" ref={panelElement => this.panelElement = panelElement} > - {!this.props.initialized && 'loading...'} + {!this.props.initialized && }
); } @@ -151,7 +158,7 @@ export class DashboardPanel extends React.Component { } } -DashboardPanel.propTypes = { +DashboardPanelUi.propTypes = { viewOnlyMode: PropTypes.bool.isRequired, onPanelFocused: PropTypes.func, onPanelBlurred: PropTypes.func, @@ -179,3 +186,5 @@ DashboardPanel.propTypes = { panelIndex: PropTypes.string, }).isRequired, }; + +export const DashboardPanel = injectI18n(DashboardPanelUi); diff --git a/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.test.js b/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.test.js index 5f142a8b1e338..eb8258f43833b 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.test.js +++ b/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel.test.js @@ -19,7 +19,7 @@ import React from 'react'; import _ from 'lodash'; -import { mount } from 'enzyme'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { DashboardPanel } from './dashboard_panel'; import { DashboardViewMode } from '../dashboard_view_mode'; import { PanelError } from '../panel/panel_error'; @@ -62,7 +62,7 @@ beforeAll(() => { }); test('DashboardPanel matches snapshot', () => { - const component = mount(); + const component = mountWithIntl(); expect(takeMountedSnapshot(component)).toMatchSnapshot(); }); @@ -71,7 +71,7 @@ test('renders an error when error prop is passed', () => { error: 'Simulated error' }); - const component = mount(); + const component = mountWithIntl(); const panelError = component.find(PanelError); expect(panelError.length).toBe(1); }); diff --git a/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel_container.js b/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel_container.js index 1b62a93f2cfb8..556c81d32f8d2 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel_container.js +++ b/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel_container.js @@ -19,6 +19,7 @@ import { connect } from 'react-redux'; import PropTypes from 'prop-types'; +import { i18n } from '@kbn/i18n'; import { DashboardPanel } from './dashboard_panel'; import { DashboardViewMode } from '../dashboard_view_mode'; @@ -40,7 +41,10 @@ const mapStateToProps = ({ dashboard }, { embeddableFactory, panelId }) => { let error = null; if (!embeddableFactory) { const panelType = getPanelType(dashboard, panelId); - error = `No embeddable factory found for panel type ${panelType}`; + error = i18n.translate('kbn.dashboard.panel.noFoundEmbeddableFactoryErrorMessage', { + defaultMessage: 'No embeddable factory found for panel type {panelType}', + values: { panelType }, + }); } else { error = (embeddable && getEmbeddableError(dashboard, panelId)) || ''; } diff --git a/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel_container.test.js b/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel_container.test.js index 2af88510b7152..cb532f1a48e38 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel_container.test.js +++ b/src/core_plugins/kibana/public/dashboard/panel/dashboard_panel_container.test.js @@ -19,7 +19,7 @@ import React from 'react'; import _ from 'lodash'; -import { mount } from 'enzyme'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { DashboardPanelContainer } from './dashboard_panel_container'; import { DashboardViewMode } from '../dashboard_view_mode'; import { PanelError } from '../panel/panel_error'; @@ -52,7 +52,7 @@ test('renders an error when embeddableFactory.create throws an error', (done) => throw new Error('simulated error'); }); }; - const component = mount(); + const component = mountWithIntl(); setTimeout(() => { component.update(); const panelError = component.find(PanelError); diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_customize_panel_action.tsx b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_customize_panel_action.tsx index f7cd97666b5c3..dd059c47475c3 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_customize_panel_action.tsx +++ b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_customize_panel_action.tsx @@ -18,6 +18,7 @@ */ import { EuiIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import React from 'react'; import { ContextMenuAction, ContextMenuPanel } from 'ui/embeddable'; import { DashboardViewMode } from '../../../dashboard_view_mode'; @@ -36,7 +37,9 @@ export function getCustomizePanelAction({ }): ContextMenuAction { return new ContextMenuAction( { - displayName: 'Customize panel', + displayName: i18n.translate('kbn.dashboard.panel.customizePanel.displayName', { + defaultMessage: 'Customize panel', + }), id: 'customizePanel', parentPanelId: 'mainMenu', }, @@ -44,7 +47,9 @@ export function getCustomizePanelAction({ childContextMenuPanel: new ContextMenuPanel( { id: 'panelSubOptionsMenu', - title: 'Customize panel', + title: i18n.translate('kbn.dashboard.panel.customizePanelTitle', { + defaultMessage: 'Customize panel', + }), }, { getContent: () => ( diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_edit_panel_action.tsx b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_edit_panel_action.tsx index 88bc0001a9ac1..f4413fc5539e7 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_edit_panel_action.tsx +++ b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_edit_panel_action.tsx @@ -20,6 +20,7 @@ import React from 'react'; import { EuiIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { ContextMenuAction } from 'ui/embeddable'; import { DashboardViewMode } from '../../../dashboard_view_mode'; @@ -31,7 +32,9 @@ import { DashboardViewMode } from '../../../dashboard_view_mode'; export function getEditPanelAction() { return new ContextMenuAction( { - displayName: 'Edit visualization', + displayName: i18n.translate('kbn.dashboard.panel.editPanel.displayName', { + defaultMessage: 'Edit visualization', + }), id: 'editPanel', parentPanelId: 'mainMenu', }, diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_inspector_panel_action.tsx b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_inspector_panel_action.tsx index 2739e2859a429..01f6da6421109 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_inspector_panel_action.tsx +++ b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_inspector_panel_action.tsx @@ -20,6 +20,7 @@ import React from 'react'; import { EuiIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { ContextMenuAction } from 'ui/embeddable'; import { Inspector } from 'ui/inspector'; @@ -41,7 +42,9 @@ export function getInspectorPanelAction({ return new ContextMenuAction( { id: 'openInspector', - displayName: 'Inspect', + displayName: i18n.translate('kbn.dashboard.panel.inspectorPanel.displayName', { + defaultMessage: 'Inspect', + }), parentPanelId: 'mainMenu', }, { diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_remove_panel_action.tsx b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_remove_panel_action.tsx index fce94f24b16ce..47718a5f21b31 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_remove_panel_action.tsx +++ b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_remove_panel_action.tsx @@ -18,6 +18,7 @@ */ import { EuiIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import React from 'react'; import { ContextMenuAction } from 'ui/embeddable'; @@ -31,7 +32,9 @@ import { DashboardViewMode } from '../../../dashboard_view_mode'; export function getRemovePanelAction(onDeletePanel: () => void) { return new ContextMenuAction( { - displayName: 'Delete from dashboard', + displayName: i18n.translate('kbn.dashboard.panel.removePanel.displayName', { + defaultMessage: 'Delete from dashboard', + }), id: 'deletePanel', parentPanelId: 'mainMenu', }, diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_toggle_expand_panel_action.tsx b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_toggle_expand_panel_action.tsx index 27dca29c01ba6..80d43347b308c 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_toggle_expand_panel_action.tsx +++ b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_actions/get_toggle_expand_panel_action.tsx @@ -18,6 +18,7 @@ */ import { EuiIcon } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import React from 'react'; import { ContextMenuAction } from 'ui/embeddable'; @@ -37,7 +38,13 @@ export function getToggleExpandPanelAction({ }) { return new ContextMenuAction( { - displayName: isExpanded ? 'Minimize' : 'Full screen', + displayName: isExpanded + ? i18n.translate('kbn.dashboard.panel.toggleExpandPanel.expandedDisplayName', { + defaultMessage: 'Minimize', + }) + : i18n.translate('kbn.dashboard.panel.toggleExpandPanel.notExpandedDisplayName', { + defaultMessage: 'Full screen', + }), id: 'togglePanel', parentPanelId: 'mainMenu', }, diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header.tsx b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header.tsx index c0acd5a672a65..9e9e59d79aa97 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header.tsx +++ b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header.tsx @@ -17,6 +17,7 @@ * under the License. */ +import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; import React from 'react'; import { Embeddable } from 'ui/embeddable'; import { PanelId } from '../../selectors'; @@ -30,13 +31,18 @@ export interface PanelHeaderProps { hidePanelTitles: boolean; } -export function PanelHeader({ +interface PanelHeaderUiProps extends PanelHeaderProps { + intl: InjectedIntl; +} + +function PanelHeaderUi({ title, panelId, embeddable, isViewOnlyMode, hidePanelTitles, -}: PanelHeaderProps) { + intl, +}: PanelHeaderUiProps) { if (isViewOnlyMode && (!title || hidePanelTitles)) { return (
@@ -56,7 +62,15 @@ export function PanelHeader({ data-test-subj="dashboardPanelTitle" className="dshPanel__title" title={title} - aria-label={`Dashboard panel: ${title}`} + aria-label={intl.formatMessage( + { + id: 'kbn.dashboard.panel.dashboardPanelAriaLabel', + defaultMessage: 'Dashboard panel: {title}', + }, + { + title, + } + )} > {hidePanelTitles ? '' : title} @@ -67,3 +81,5 @@ export function PanelHeader({
); } + +export const PanelHeader = injectI18n(PanelHeaderUi); diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header_container.test.tsx b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header_container.test.tsx index 998c1f6dc3ffc..f395b17207be5 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header_container.test.tsx +++ b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_header_container.test.tsx @@ -17,10 +17,11 @@ * under the License. */ -import { mount, ReactWrapper } from 'enzyme'; +import { ReactWrapper } from 'enzyme'; import _ from 'lodash'; import React from 'react'; import { Provider } from 'react-redux'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; // TODO: remove this when EUI supports types for this. // @ts-ignore: implicit any for JS file @@ -77,7 +78,7 @@ afterAll(() => { }); test('Panel header shows embeddable title when nothing is set on the panel', () => { - component = mount( + component = mountWithIntl( diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu.tsx b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu.tsx index 983a235a719f2..25efd58d059e1 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu.tsx +++ b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu.tsx @@ -17,6 +17,7 @@ * under the License. */ +import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; import React from 'react'; import { @@ -34,19 +35,27 @@ export interface PanelOptionsMenuProps { isViewMode: boolean; } -export function PanelOptionsMenu({ +interface PanelOptionsMenuUiProps extends PanelOptionsMenuProps { + intl: InjectedIntl; +} + +function PanelOptionsMenuUi({ toggleContextMenu, isPopoverOpen, closeContextMenu, panels, isViewMode, -}: PanelOptionsMenuProps) { + intl, +}: PanelOptionsMenuUiProps) { const button = ( @@ -70,3 +79,5 @@ export function PanelOptionsMenu({ ); } + +export const PanelOptionsMenu = injectI18n(PanelOptionsMenuUi); diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_container.ts b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_container.ts index 294a7dd2661ce..478c59847fe3a 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_container.ts +++ b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_container.ts @@ -18,6 +18,7 @@ */ import { EuiContextMenuPanelDescriptor } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { connect } from 'react-redux'; import { buildEuiContextMenuPanels, @@ -167,7 +168,9 @@ const mergeProps = ( // every panel, every time any state changes. if (isPopoverOpen) { const contextMenuPanel = new ContextMenuPanel({ - title: 'Options', + title: i18n.translate('kbn.dashboard.panel.optionsMenu.optionsContextMenuTitle', { + defaultMessage: 'Options', + }), id: 'mainMenu', }); diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_form.tsx b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_form.tsx index 4b7b4a3a1341c..c80ab00a46b77 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_form.tsx +++ b/src/core_plugins/kibana/public/dashboard/panel/panel_header/panel_options_menu_form.tsx @@ -20,6 +20,7 @@ import React, { ChangeEvent, KeyboardEvent } from 'react'; import { EuiButtonEmpty, EuiFieldText, EuiFormRow, keyCodes } from '@elastic/eui'; +import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; export interface PanelOptionsMenuFormProps { title?: string; @@ -28,12 +29,17 @@ export interface PanelOptionsMenuFormProps { onClose: () => void; } -export function PanelOptionsMenuForm({ +interface PanelOptionsMenuFormUiProps extends PanelOptionsMenuFormProps { + intl: InjectedIntl; +} + +function PanelOptionsMenuFormUi({ title, onReset, onUpdatePanelTitle, onClose, -}: PanelOptionsMenuFormProps) { + intl, +}: PanelOptionsMenuFormUiProps) { function onInputChange(event: ChangeEvent) { onUpdatePanelTitle(event.target.value); } @@ -46,7 +52,12 @@ export function PanelOptionsMenuForm({ return (
- + - Reset title +
); } + +export const PanelOptionsMenuForm = injectI18n(PanelOptionsMenuFormUi); diff --git a/src/core_plugins/kibana/public/dashboard/panel/panel_utils.js b/src/core_plugins/kibana/public/dashboard/panel/panel_utils.js index c5b60f1018bb3..aec24998f17b7 100644 --- a/src/core_plugins/kibana/public/dashboard/panel/panel_utils.js +++ b/src/core_plugins/kibana/public/dashboard/panel/panel_utils.js @@ -18,6 +18,7 @@ */ import _ from 'lodash'; +import { i18n } from '@kbn/i18n'; import { DEFAULT_PANEL_WIDTH, DEFAULT_PANEL_HEIGHT } from '../dashboard_constants'; import chrome from 'ui/chrome'; @@ -31,7 +32,10 @@ export class PanelUtils { static convertPanelDataPre_6_1(panel) { // eslint-disable-line camelcase ['col', 'row'].forEach(key => { if (!_.has(panel, key)) { - throw new Error(`Unable to migrate panel data for "6.1.0" backwards compatibility, panel does not contain expected field: ${key}`); + throw new Error(i18n.translate('kbn.dashboard.panel.unableToMigratePanelDataForSixOneZeroErrorMessage', { + defaultMessage: 'Unable to migrate panel data for "6.1.0" backwards compatibility, panel does not contain expected field: {key}', + values: { key }, + })); } }); @@ -59,7 +63,10 @@ export class PanelUtils { static convertPanelDataPre_6_3(panel, useMargins) { // eslint-disable-line camelcase ['w', 'x', 'h', 'y'].forEach(key => { if (!_.has(panel.gridData, key)) { - throw new Error(`Unable to migrate panel data for "6.3.0" backwards compatibility, panel does not contain expected field: ${key}`); + throw new Error(i18n.translate('kbn.dashboard.panel.unableToMigratePanelDataForSixThreeZeroErrorMessage', { + defaultMessage: 'Unable to migrate panel data for "6.3.0" backwards compatibility, panel does not contain expected field: {key}', + values: { key }, + })); } }); @@ -78,7 +85,13 @@ export class PanelUtils { static parseVersion(version = '6.0.0') { const versionSplit = version.split('.'); if (versionSplit.length < 3) { - throw new Error(`Invalid version, ${version}, expected ..`); + throw new Error(i18n.translate('kbn.dashboard.panel.invalidVersionErrorMessage', { + defaultMessage: 'Invalid version, {version}, expected {semver}', + values: { + version, + semver: '..', + }, + })); } return { major: parseInt(versionSplit[0], 10), diff --git a/src/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.js b/src/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.js index cf82b8274b633..04773be6e428b 100644 --- a/src/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.js +++ b/src/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboard.js @@ -26,7 +26,7 @@ import { SavedObjectProvider } from 'ui/courier'; const module = uiModules.get('app/dashboard'); // Used only by the savedDashboards service, usually no reason to change this -module.factory('SavedDashboard', function (Private, config) { +module.factory('SavedDashboard', function (Private, config, i18n) { // SavedDashboard constructor. Usually you'd interact with an instance of this. // ID is option, without it one will be generated on save. const SavedObject = Private(SavedObjectProvider); @@ -43,7 +43,7 @@ module.factory('SavedDashboard', function (Private, config) { // default values that will get assigned if the doc is new defaults: { - title: 'New Dashboard', + title: i18n('kbn.dashboard.savedDashboard.newDashboardTitle', { defaultMessage: 'New Dashboard' }), hits: 0, description: '', panelsJSON: '[]', diff --git a/src/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboards.js b/src/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboards.js index a4563b71a26fa..c4ab8b9a96e37 100644 --- a/src/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboards.js +++ b/src/core_plugins/kibana/public/dashboard/saved_dashboard/saved_dashboards.js @@ -17,6 +17,7 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; import './saved_dashboard'; import { uiModules } from 'ui/modules'; import { SavedObjectLoader } from 'ui/courier/saved_object/saved_object_loader'; @@ -29,7 +30,9 @@ const module = uiModules.get('app/dashboard'); // edited by the object editor. savedObjectManagementRegistry.register({ service: 'savedDashboards', - title: 'dashboards' + title: i18n.translate('kbn.dashboard.savedDashboardsTitle', { + defaultMessage: 'dashboards', + }), }); // This is the only thing that gets injected into controllers diff --git a/src/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/add_panel.test.js.snap b/src/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/add_panel.test.js.snap index d976372dd2bf3..4f96414b427ce 100644 --- a/src/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/add_panel.test.js.snap +++ b/src/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/add_panel.test.js.snap @@ -16,7 +16,11 @@ exports[`render 1`] = ` textTransform="none" >

- Add Panels +

- Add new Visualization + } key="visSavedObjectFinder" diff --git a/src/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/clone_modal.test.js.snap b/src/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/clone_modal.test.js.snap index 80fb7a7ed275c..92e8f07ea0da4 100644 --- a/src/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/clone_modal.test.js.snap +++ b/src/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/clone_modal.test.js.snap @@ -10,7 +10,11 @@ exports[`renders DashboardCloneModal 1`] = ` > - Clone Dashboard + @@ -19,7 +23,11 @@ exports[`renders DashboardCloneModal 1`] = ` size="m" >

- Please enter a new name for your dashboard. +

- Cancel + - Confirm Clone + diff --git a/src/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/save_modal.test.js.snap b/src/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/save_modal.test.js.snap index 2112aee0763cb..aae42c0b98ce0 100644 --- a/src/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/save_modal.test.js.snap +++ b/src/core_plugins/kibana/public/dashboard/top_nav/__snapshots__/save_modal.test.js.snap @@ -11,7 +11,13 @@ exports[`renders DashboardSaveModal 1`] = ` describedByIds={Array []} fullWidth={false} hasEmptyLabelSpace={false} - label="Description" + label={ + + } > + } + label={ + + } > - Add new Visualization + ); const tabs = [{ id: VIS_TAB_ID, - name: 'Visualization', + name: props.intl.formatMessage({ + id: 'kbn.dashboard.topNav.addPanel.visualizationTabName', + defaultMessage: 'Visualization', + }), dataTestSubj: 'addVisualizationTab', toastDataTestSubj: 'addVisualizationToDashboardSuccess', savedObjectFinder: ( @@ -59,20 +66,29 @@ export class DashboardAddPanel extends React.Component { callToActionButton={addNewVisBtn} onChoose={this.onAddPanel} visTypes={this.props.visTypes} - noItemsMessage="No matching visualizations found." + noItemsMessage={props.intl.formatMessage({ + id: 'kbn.dashboard.topNav.addPanel.visSavedObjectFinder.noMatchingVisualizationsMessage', + defaultMessage: 'No matching visualizations found.', + })} savedObjectType="visualization" /> ) }, { id: SAVED_SEARCH_TAB_ID, - name: 'Saved Search', + name: props.intl.formatMessage({ + id: 'kbn.dashboard.topNav.addPanel.savedSearchTabName', + defaultMessage: 'Saved Search', + }), dataTestSubj: 'addSavedSearchTab', toastDataTestSubj: 'addSavedSearchToDashboardSuccess', savedObjectFinder: ( ) @@ -115,7 +131,12 @@ export class DashboardAddPanel extends React.Component { } this.lastToast = toastNotifications.addSuccess({ - title: `${this.state.selectedTab.name} was added to your dashboard`, + title: this.props.intl.formatMessage({ + id: 'kbn.dashboard.topNav.addPanel.selectedTabAddedToDashboardSuccessMessageTitle', + defaultMessage: '{selectedTabName} was added to your dashboard', + }, { + selectedTabName: this.state.selectedTab.name, + }), 'data-test-subj': this.state.selectedTab.toastDataTestSubj, }); } @@ -131,7 +152,12 @@ export class DashboardAddPanel extends React.Component { -

Add Panels

+

+ +

@@ -148,9 +174,11 @@ export class DashboardAddPanel extends React.Component { } } -DashboardAddPanel.propTypes = { +DashboardAddPanelUi.propTypes = { onClose: PropTypes.func.isRequired, visTypes: PropTypes.object.isRequired, addNewPanel: PropTypes.func.isRequired, addNewVis: PropTypes.func.isRequired, }; + +export const DashboardAddPanel = injectI18n(DashboardAddPanelUi); diff --git a/src/core_plugins/kibana/public/dashboard/top_nav/add_panel.test.js b/src/core_plugins/kibana/public/dashboard/top_nav/add_panel.test.js index 9c17980f7b9cd..3f233eed6b100 100644 --- a/src/core_plugins/kibana/public/dashboard/top_nav/add_panel.test.js +++ b/src/core_plugins/kibana/public/dashboard/top_nav/add_panel.test.js @@ -19,7 +19,7 @@ import React from 'react'; import sinon from 'sinon'; -import { shallow } from 'enzyme'; +import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { DashboardAddPanel, @@ -38,7 +38,7 @@ beforeEach(() => { }); test('render', () => { - const component = shallow( {}} diff --git a/src/core_plugins/kibana/public/dashboard/top_nav/clone_modal.js b/src/core_plugins/kibana/public/dashboard/top_nav/clone_modal.js index 5bab97387a14f..507eb2b6db34e 100644 --- a/src/core_plugins/kibana/public/dashboard/top_nav/clone_modal.js +++ b/src/core_plugins/kibana/public/dashboard/top_nav/clone_modal.js @@ -19,6 +19,7 @@ import React, { Fragment } from 'react'; import PropTypes from 'prop-types'; +import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; import { EuiButton, @@ -34,7 +35,7 @@ import { EuiCallOut, } from '@elastic/eui'; -export class DashboardCloneModal extends React.Component { +class DashboardCloneModalUi extends React.Component { constructor(props) { super(props); @@ -90,12 +91,30 @@ export class DashboardCloneModal extends React.Component { return (

- Click Confirm Clone to clone the dashboard with the duplicate title. + + + + ), + }} + />

@@ -113,14 +132,20 @@ export class DashboardCloneModal extends React.Component { > - Clone Dashboard +

- Please enter a new name for your dashboard. +

@@ -143,7 +168,10 @@ export class DashboardCloneModal extends React.Component { data-test-subj="cloneCancelButton" onClick={this.props.onClose} > - Cancel + - Confirm Clone + @@ -161,8 +192,10 @@ export class DashboardCloneModal extends React.Component { } } -DashboardCloneModal.propTypes = { +DashboardCloneModalUi.propTypes = { onClone: PropTypes.func, onClose: PropTypes.func, title: PropTypes.string }; + +export const DashboardCloneModal = injectI18n(DashboardCloneModalUi); diff --git a/src/core_plugins/kibana/public/dashboard/top_nav/clone_modal.test.js b/src/core_plugins/kibana/public/dashboard/top_nav/clone_modal.test.js index c8154fe3bdd6b..3dfaa26b79826 100644 --- a/src/core_plugins/kibana/public/dashboard/top_nav/clone_modal.test.js +++ b/src/core_plugins/kibana/public/dashboard/top_nav/clone_modal.test.js @@ -19,7 +19,7 @@ import React from 'react'; import sinon from 'sinon'; -import { mount, shallow } from 'enzyme'; +import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; import { findTestSubject, } from '@elastic/eui/lib/test'; @@ -37,9 +37,9 @@ beforeEach(() => { onClose = sinon.spy(); }); -function createComponent(creationMethod = mount) { +function createComponent(creationMethod = mountWithIntl) { component = creationMethod( - { - createComponent(shallow); + createComponent(shallowWithIntl); expect(component).toMatchSnapshot(); // eslint-disable-line }); diff --git a/src/core_plugins/kibana/public/dashboard/top_nav/get_top_nav_config.js b/src/core_plugins/kibana/public/dashboard/top_nav/get_top_nav_config.js index c0ac1cb2702b2..969ff3f631de5 100644 --- a/src/core_plugins/kibana/public/dashboard/top_nav/get_top_nav_config.js +++ b/src/core_plugins/kibana/public/dashboard/top_nav/get_top_nav_config.js @@ -17,6 +17,7 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; import { DashboardViewMode } from '../dashboard_view_mode'; import { TopNavIds } from './top_nav_ids'; @@ -57,8 +58,12 @@ export function getTopNavConfig(dashboardMode, actions, hideWriteControls) { function getFullScreenConfig(action) { return { - key: 'full screen', - description: 'Full Screen Mode', + key: i18n.translate('kbn.dashboard.topNave.fullScreenButtonAriaLabel', { + defaultMessage: 'full screen', + }), + description: i18n.translate('kbn.dashboard.topNave.fullScreenConfigDescription', { + defaultMessage: 'Full Screen Mode', + }), testId: 'dashboardFullScreenMode', run: action }; @@ -69,8 +74,12 @@ function getFullScreenConfig(action) { */ function getEditConfig(action) { return { - key: 'edit', - description: 'Switch to edit mode', + key: i18n.translate('kbn.dashboard.topNave.editButtonAriaLabel', { + defaultMessage: 'edit', + }), + description: i18n.translate('kbn.dashboard.topNave.editConfigDescription', { + defaultMessage: 'Switch to edit mode', + }), testId: 'dashboardEditMode', run: action }; @@ -81,8 +90,12 @@ function getEditConfig(action) { */ function getSaveConfig(action) { return { - key: TopNavIds.SAVE, - description: 'Save your dashboard', + key: i18n.translate('kbn.dashboard.topNave.saveButtonAriaLabel', { + defaultMessage: 'save', + }), + description: i18n.translate('kbn.dashboard.topNave.saveConfigDescription', { + defaultMessage: 'Save your dashboard', + }), testId: 'dashboardSaveMenuItem', run: action }; @@ -93,8 +106,12 @@ function getSaveConfig(action) { */ function getViewConfig(action) { return { - key: 'cancel', - description: 'Cancel editing and switch to view-only mode', + key: i18n.translate('kbn.dashboard.topNave.cancelButtonAriaLabel', { + defaultMessage: 'cancel', + }), + description: i18n.translate('kbn.dashboard.topNave.viewConfigDescription', { + defaultMessage: 'Cancel editing and switch to view-only mode', + }), testId: 'dashboardViewOnlyMode', run: action }; @@ -105,8 +122,12 @@ function getViewConfig(action) { */ function getCloneConfig(action) { return { - key: TopNavIds.CLONE, - description: 'Create a copy of your dashboard', + key: i18n.translate('kbn.dashboard.topNave.cloneButtonAriaLabel', { + defaultMessage: 'clone', + }), + description: i18n.translate('kbn.dashboard.topNave.cloneConfigDescription', { + defaultMessage: 'Create a copy of your dashboard', + }), testId: 'dashboardClone', run: action }; @@ -117,8 +138,12 @@ function getCloneConfig(action) { */ function getAddConfig(action) { return { - key: TopNavIds.ADD, - description: 'Add a panel to the dashboard', + key: i18n.translate('kbn.dashboard.topNave.addButtonAriaLabel', { + defaultMessage: 'add', + }), + description: i18n.translate('kbn.dashboard.topNave.addConfigDescription', { + defaultMessage: 'Add a panel to the dashboard', + }), testId: 'dashboardAddPanelButton', run: action }; @@ -129,8 +154,12 @@ function getAddConfig(action) { */ function getShareConfig(action) { return { - key: TopNavIds.SHARE, - description: 'Share Dashboard', + key: i18n.translate('kbn.dashboard.topNave.shareButtonAriaLabel', { + defaultMessage: 'share', + }), + description: i18n.translate('kbn.dashboard.topNave.shareConfigDescription', { + defaultMessage: 'Share Dashboard', + }), testId: 'shareTopNavButton', run: action, }; @@ -141,8 +170,12 @@ function getShareConfig(action) { */ function getOptionsConfig(action) { return { - key: TopNavIds.OPTIONS, - description: 'Options', + key: i18n.translate('kbn.dashboard.topNave.optionsButtonAriaLabel', { + defaultMessage: 'options', + }), + description: i18n.translate('kbn.dashboard.topNave.optionsConfigDescription', { + defaultMessage: 'Options', + }), testId: 'dashboardOptionsButton', run: action, }; diff --git a/src/core_plugins/kibana/public/dashboard/top_nav/options.js b/src/core_plugins/kibana/public/dashboard/top_nav/options.js index 6c42c2d26a25e..62c3b65374db4 100644 --- a/src/core_plugins/kibana/public/dashboard/top_nav/options.js +++ b/src/core_plugins/kibana/public/dashboard/top_nav/options.js @@ -19,6 +19,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; +import { injectI18n } from '@kbn/i18n/react'; import { EuiForm, @@ -26,7 +27,7 @@ import { EuiSwitch, } from '@elastic/eui'; -export class OptionsMenu extends Component { +class OptionsMenuUi extends Component { state = { darkTheme: this.props.darkTheme, @@ -60,7 +61,10 @@ export class OptionsMenu extends Component { } > } + helpText={} > { - const component = shallow( {}} onClose={() => {}} title="dash title" diff --git a/src/core_plugins/kibana/public/dashboard/top_nav/show_add_panel.js b/src/core_plugins/kibana/public/dashboard/top_nav/show_add_panel.js index 00719a850aca4..d4d8b1a0e4040 100644 --- a/src/core_plugins/kibana/public/dashboard/top_nav/show_add_panel.js +++ b/src/core_plugins/kibana/public/dashboard/top_nav/show_add_panel.js @@ -17,6 +17,7 @@ * under the License. */ +import { I18nProvider } from '@kbn/i18n/react'; import { DashboardAddPanel } from './add_panel'; import React from 'react'; import ReactDOM from 'react-dom'; @@ -43,12 +44,14 @@ export function showAddPanel(addNewPanel, addNewVis, visTypes) { document.body.appendChild(container); const element = ( - + + + ); ReactDOM.render(element, container); } diff --git a/src/core_plugins/kibana/public/dashboard/top_nav/show_clone_modal.js b/src/core_plugins/kibana/public/dashboard/top_nav/show_clone_modal.js index d75c3da660e9f..08163a9a35956 100644 --- a/src/core_plugins/kibana/public/dashboard/top_nav/show_clone_modal.js +++ b/src/core_plugins/kibana/public/dashboard/top_nav/show_clone_modal.js @@ -17,9 +17,11 @@ * under the License. */ +import { I18nProvider } from '@kbn/i18n/react'; import { DashboardCloneModal } from './clone_modal'; import React from 'react'; import ReactDOM from 'react-dom'; +import { i18n } from '@kbn/i18n'; export function showCloneModal(onClone, title) { const container = document.createElement('div'); @@ -37,7 +39,16 @@ export function showCloneModal(onClone, title) { }; document.body.appendChild(container); const element = ( - + + + ); ReactDOM.render(element, container); } diff --git a/src/core_plugins/kibana/public/dashboard/top_nav/show_options_popover.js b/src/core_plugins/kibana/public/dashboard/top_nav/show_options_popover.js index 2d2dd83b4c34e..cf6fba6316e76 100644 --- a/src/core_plugins/kibana/public/dashboard/top_nav/show_options_popover.js +++ b/src/core_plugins/kibana/public/dashboard/top_nav/show_options_popover.js @@ -19,6 +19,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; +import { I18nProvider } from '@kbn/i18n/react'; import { OptionsMenu } from './options'; @@ -53,22 +54,24 @@ export function showOptionsPopover({ document.body.appendChild(container); const element = ( - - - + + + + + ); ReactDOM.render(element, container); } diff --git a/src/core_plugins/kibana/public/dashboard/viewport/dashboard_viewport_provider.js b/src/core_plugins/kibana/public/dashboard/viewport/dashboard_viewport_provider.js index a219f763dac28..95b66cc1c4c59 100644 --- a/src/core_plugins/kibana/public/dashboard/viewport/dashboard_viewport_provider.js +++ b/src/core_plugins/kibana/public/dashboard/viewport/dashboard_viewport_provider.js @@ -19,6 +19,7 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { I18nProvider } from '@kbn/i18n/react'; import { store } from '../../store'; import { Provider } from 'react-redux'; import { DashboardViewportContainer } from './dashboard_viewport_container'; @@ -26,7 +27,9 @@ import { DashboardViewportContainer } from './dashboard_viewport_container'; export function DashboardViewportProvider(props) { return ( - + + + ); } diff --git a/src/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.js b/src/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.js index 745693178d966..52dd71325a2a0 100644 --- a/src/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.js +++ b/src/core_plugins/kibana/public/discover/components/fetch_error/fetch_error.js @@ -21,6 +21,7 @@ import 'ngreact'; import React, { Fragment } from 'react'; import { uiModules } from 'ui/modules'; import chrome from 'ui/chrome'; +import { FormattedMessage, injectI18nProvider } from '@kbn/i18n/react'; import { EuiFlexGroup, @@ -43,9 +44,26 @@ const DiscoverFetchError = ({ fetchError }) => { body = (

- You can address this error by editing the ‘{fetchError.script}’ field - in Management > Index Patterns, - under the “Scripted fields” tab. + , + managementLink: ( + + + + ) + }} + />

); } @@ -77,4 +95,4 @@ const DiscoverFetchError = ({ fetchError }) => { const app = uiModules.get('apps/discover', ['react']); -app.directive('discoverFetchError', reactDirective => reactDirective(DiscoverFetchError)); +app.directive('discoverFetchError', reactDirective => reactDirective(injectI18nProvider(DiscoverFetchError))); diff --git a/src/core_plugins/kibana/public/discover/components/field_chooser/discover_field.html b/src/core_plugins/kibana/public/discover/components/field_chooser/discover_field.html index 76d7bec8e454c..206239f6ae3c4 100644 --- a/src/core_plugins/kibana/public/discover/components/field_chooser/discover_field.html +++ b/src/core_plugins/kibana/public/discover/components/field_chooser/discover_field.html @@ -17,7 +17,7 @@ ng-if="field.name !== '_source'" ng-click="toggleDisplay(field)" ng-class="::field.display ? 'kuiButton--danger' : 'kuiButton--primary'" - ng-bind="::field.display ? 'remove' : 'add'" + ng-bind="::addRemoveButtonLabel" class="dscSidebarItem__action kuiButton kuiButton--small" data-test-subj="fieldToggle-{{::field.name}}" > diff --git a/src/core_plugins/kibana/public/discover/components/field_chooser/discover_field.js b/src/core_plugins/kibana/public/discover/components/field_chooser/discover_field.js index 4627dfbdcf9e8..8ff6e3a5751f6 100644 --- a/src/core_plugins/kibana/public/discover/components/field_chooser/discover_field.js +++ b/src/core_plugins/kibana/public/discover/components/field_chooser/discover_field.js @@ -26,7 +26,7 @@ import detailsHtml from './lib/detail_views/string.html'; import { uiModules } from 'ui/modules'; const app = uiModules.get('apps/discover'); -app.directive('discoverField', function ($compile) { +app.directive('discoverField', function ($compile, i18n) { return { restrict: 'E', template: html, @@ -42,11 +42,18 @@ app.directive('discoverField', function ($compile) { let detailsElem; let detailScope; - const init = function () { if ($scope.field.details) { $scope.toggleDetails($scope.field, true); } + + $scope.addRemoveButtonLabel = $scope.field.display + ? i18n('kbn.discover.fieldChooser.discoverField.removeButtonLabel', { + defaultMessage: 'remove', + }) + : i18n('kbn.discover.fieldChooser.discoverField.addButtonLabel', { + defaultMessage: 'add', + }); }; const getWarnings = function (field) { @@ -92,6 +99,18 @@ app.directive('discoverField', function ($compile) { $scope.onShowDetails(field, recompute); detailScope = $scope.$new(); detailScope.warnings = getWarnings(field); + detailScope.getBucketAriaLabel = (bucket) => { + return i18n('kbn.discover.fieldChooser.discoverField.bucketAriaLabel', { + defaultMessage: 'Value: {value}', + values: { + value: bucket.display === '' + ? i18n('kbn.discover.fieldChooser.discoverField.emptyStringText', { + defaultMessage: 'Empty string', + }) + : bucket.display, + }, + }); + }; detailsElem = $(detailsHtml); $compile(detailsElem)(detailScope); diff --git a/src/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.html b/src/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.html index 2e64db6656bca..73442a4ec74b8 100644 --- a/src/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.html +++ b/src/core_plugins/kibana/public/discover/components/field_chooser/field_chooser.html @@ -1,4 +1,4 @@ -
diff --git a/src/core_plugins/kibana/public/discover/index.js b/src/core_plugins/kibana/public/discover/index.js index 3347777f1a649..5b23bbd4c01c6 100644 --- a/src/core_plugins/kibana/public/discover/index.js +++ b/src/core_plugins/kibana/public/discover/index.js @@ -26,11 +26,15 @@ import 'ui/doc_table/components/table_row'; import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; -FeatureCatalogueRegistryProvider.register(() => { +FeatureCatalogueRegistryProvider.register(i18n => { return { id: 'discover', - title: 'Discover', - description: 'Interactively explore your data by querying and filtering raw documents.', + title: i18n('kbn.discover.discoverTitle', { + defaultMessage: 'Discover', + }), + description: i18n('kbn.discover.discoverDescription', { + defaultMessage: 'Interactively explore your data by querying and filtering raw documents.', + }), icon: 'discoverApp', path: '/app/kibana#/discover', showOnHomePage: true, diff --git a/src/core_plugins/kibana/public/discover/saved_searches/_saved_search.js b/src/core_plugins/kibana/public/discover/saved_searches/_saved_search.js index 5a15c5a08c6be..89df75c753520 100644 --- a/src/core_plugins/kibana/public/discover/saved_searches/_saved_search.js +++ b/src/core_plugins/kibana/public/discover/saved_searches/_saved_search.js @@ -27,7 +27,7 @@ const module = uiModules.get('discover/saved_searches', [ 'kibana/courier' ]); -module.factory('SavedSearch', function (Private) { +module.factory('SavedSearch', function (Private, i18n) { const SavedObject = Private(SavedObjectProvider); createLegacyClass(SavedSearch).inherits(SavedObject); function SavedSearch(id) { @@ -38,7 +38,9 @@ module.factory('SavedSearch', function (Private) { id: id, defaults: { - title: 'New Saved Search', + title: i18n('kbn.discover.savedSearch.newSavedSearchTitle', { + defaultMessage: 'New Saved Search', + }), description: '', columns: [], hits: 0, diff --git a/src/core_plugins/kibana/public/discover/top_nav/__snapshots__/open_search_panel.test.js.snap b/src/core_plugins/kibana/public/discover/top_nav/__snapshots__/open_search_panel.test.js.snap index 4ddf453c5a819..05b8fae9ab1dd 100644 --- a/src/core_plugins/kibana/public/discover/top_nav/__snapshots__/open_search_panel.test.js.snap +++ b/src/core_plugins/kibana/public/discover/top_nav/__snapshots__/open_search_panel.test.js.snap @@ -16,7 +16,11 @@ exports[`render 1`] = ` textTransform="none" >

- Open Search +

- Manage searches + } makeUrl={[Function]} - noItemsMessage="No matching searches found." + noItemsMessage={ + + } onChoose={[Function]} savedObjectType="search" /> diff --git a/src/core_plugins/kibana/public/discover/top_nav/open_search_panel.js b/src/core_plugins/kibana/public/discover/top_nav/open_search_panel.js index f06dcf5d9c3d6..1235272588fcb 100644 --- a/src/core_plugins/kibana/public/discover/top_nav/open_search_panel.js +++ b/src/core_plugins/kibana/public/discover/top_nav/open_search_panel.js @@ -21,6 +21,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { SavedObjectFinder } from 'ui/saved_objects/components/saved_object_finder'; import rison from 'rison-node'; +import { FormattedMessage } from '@kbn/i18n/react'; import { EuiSpacer, @@ -40,7 +41,10 @@ export class OpenSearchPanel extends React.Component { onClick={this.props.onClose} href={`#/management/kibana/objects?_a=${rison.encode({ tab: SEARCH_OBJECT_TYPE })}`} > - Manage searches + ); } @@ -55,13 +59,23 @@ export class OpenSearchPanel extends React.Component { -

Open Search

+

+ +

+ } savedObjectType={SEARCH_OBJECT_TYPE} makeUrl={this.props.makeUrl} onChoose={this.props.onClose} diff --git a/src/core_plugins/kibana/public/discover/top_nav/show_open_search_panel.js b/src/core_plugins/kibana/public/discover/top_nav/show_open_search_panel.js index 5b76f9ebc6652..febfd55514910 100644 --- a/src/core_plugins/kibana/public/discover/top_nav/show_open_search_panel.js +++ b/src/core_plugins/kibana/public/discover/top_nav/show_open_search_panel.js @@ -20,6 +20,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import { OpenSearchPanel } from './open_search_panel'; +import { I18nProvider } from '@kbn/i18n/react'; let isOpen = false; @@ -38,10 +39,12 @@ export function showOpenSearchPanel({ makeUrl }) { document.body.appendChild(container); const element = ( - + + + ); ReactDOM.render(element, container); } diff --git a/src/core_plugins/kibana/public/index.scss b/src/core_plugins/kibana/public/index.scss index 390694b0a52c2..b415106ede3bf 100644 --- a/src/core_plugins/kibana/public/index.scss +++ b/src/core_plugins/kibana/public/index.scss @@ -1,6 +1,7 @@ @import 'ui/public/styles/styling_constants'; -@import 'ui/public/query_bar/index'; +// Public UI styles +@import 'ui/public/index'; // Context styles @import './context/index'; @@ -16,6 +17,8 @@ // Visualize styles @import './visualize/index'; +// Has to come after visualize because of some +// bad cascading in the Editor layout @import 'ui/public/vis/index'; // Management styles diff --git a/src/core_plugins/kibana/public/visualize/editor/editor.html b/src/core_plugins/kibana/public/visualize/editor/editor.html index c73805e4eb678..06c058d37da5a 100644 --- a/src/core_plugins/kibana/public/visualize/editor/editor.html +++ b/src/core_plugins/kibana/public/visualize/editor/editor.html @@ -73,6 +73,7 @@ saved-obj="savedVis" ui-state="uiState" time-range="timeRange" + filters="globalFilters" class="visEditor__content" /> diff --git a/src/core_plugins/kibana/public/visualize/editor/editor.js b/src/core_plugins/kibana/public/visualize/editor/editor.js index 5e4ec07ff5e83..b501d84f10ac4 100644 --- a/src/core_plugins/kibana/public/visualize/editor/editor.js +++ b/src/core_plugins/kibana/public/visualize/editor/editor.js @@ -128,18 +128,6 @@ function VisEditor( // SearchSource is a promise-based stream of search results that can inherit from other search sources. const { vis, searchSource } = savedVis; - // adds top level search source to the stack to which global filters are applied - const getTopLevelSearchSource = (searchSource) => { - if (searchSource.getParent()) return getTopLevelSearchSource(searchSource.getParent()); - return searchSource; - }; - - const topLevelSearchSource = getTopLevelSearchSource(searchSource); - const globalFiltersSearchSource = searchSource.create(); - globalFiltersSearchSource.setField('index', searchSource.getField('index')); - topLevelSearchSource.setParent(globalFiltersSearchSource); - - $scope.vis = vis; const $appStatus = this.appStatus = { @@ -338,10 +326,9 @@ function VisEditor( // update the searchSource when query updates $scope.fetch = function () { $state.save(); - const globalFilters = queryFilter.getGlobalFilters(); savedVis.searchSource.setField('query', $state.query); savedVis.searchSource.setField('filter', $state.filters); - globalFiltersSearchSource.setField('filter', globalFilters); + $scope.globalFilters = queryFilter.getGlobalFilters(); $scope.vis.forceReload(); }; @@ -436,6 +423,7 @@ function VisEditor( const searchSourceGrandparent = searchSourceParent.getParent(); delete savedVis.savedSearchId; + delete vis.savedSearchId; searchSourceParent.setField('filter', _.union(searchSource.getOwnField('filter'), searchSourceParent.getOwnField('filter'))); $state.query = searchSourceParent.getField('query'); diff --git a/src/core_plugins/kibana/public/visualize/editor/visualization_editor.js b/src/core_plugins/kibana/public/visualize/editor/visualization_editor.js index 1f8426386613c..a2ed44df2f5b0 100644 --- a/src/core_plugins/kibana/public/visualize/editor/visualization_editor.js +++ b/src/core_plugins/kibana/public/visualize/editor/visualization_editor.js @@ -17,6 +17,7 @@ * under the License. */ +import { debounce } from 'lodash'; import { uiModules } from 'ui/modules'; import 'angular-sanitize'; import { VisEditorTypesRegistryProvider } from 'ui/registry/vis_editor_types'; @@ -31,7 +32,8 @@ uiModules scope: { savedObj: '=', uiState: '=?', - timeRange: '=' + timeRange: '=', + filters: '=', }, link: function ($scope, element) { const editorType = $scope.savedObj.vis.type.editor; @@ -43,6 +45,7 @@ uiModules editor.render({ uiState: $scope.uiState, timeRange: $scope.timeRange, + filters: $scope.filters, appState: getAppState(), }); }; @@ -56,8 +59,9 @@ uiModules editor.destroy(); }); - $scope.$watch('timeRange', $scope.renderFunction); - + $scope.$watchGroup(['timeRange', 'filters'], debounce(() => { + $scope.renderFunction(); + }, 100)); } }; }); diff --git a/src/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.js b/src/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.js index 97a45005bcb1a..3bbc21beee682 100644 --- a/src/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.js +++ b/src/core_plugins/kibana/public/visualize/embeddable/visualize_embeddable_factory.js @@ -53,7 +53,7 @@ export class VisualizeEmbeddableFactory extends EmbeddableFactory { .then(([loader, savedObject]) => { const isLabsEnabled = this._config.get('visualize:enableLabs'); - if (!isLabsEnabled && savedObject.vis.type.stage === 'lab') { + if (!isLabsEnabled && savedObject.vis.type.stage === 'experimental') { return new Embeddable({ metadata: { title: savedObject.title, diff --git a/src/core_plugins/kibana/public/visualize/index.js b/src/core_plugins/kibana/public/visualize/index.js index a4b220c19760d..274e1bbf33837 100644 --- a/src/core_plugins/kibana/public/visualize/index.js +++ b/src/core_plugins/kibana/public/visualize/index.js @@ -40,6 +40,21 @@ uiRoutes template: visualizeListingTemplate, controller: VisualizeListingController, controllerAs: 'listingController', + resolve: { + createNewVis: () => false, + }, + }) + .when(VisualizeConstants.WIZARD_STEP_1_PAGE_PATH, { + template: visualizeListingTemplate, + controller: VisualizeListingController, + controllerAs: 'listingController', + resolve: { + createNewVis: () => true, + }, + }) + // Old path, will be removed in 7.0 + .when('/visualize/step/1', { + redirectTo: VisualizeConstants.WIZARD_STEP_1_PAGE_PATH, }); FeatureCatalogueRegistryProvider.register(() => { diff --git a/src/core_plugins/kibana/public/visualize/listing/visualize_listing.html b/src/core_plugins/kibana/public/visualize/listing/visualize_listing.html index 60d4622862e02..a81f6036b401c 100644 --- a/src/core_plugins/kibana/public/visualize/listing/visualize_listing.html +++ b/src/core_plugins/kibana/public/visualize/listing/visualize_listing.html @@ -33,6 +33,13 @@ + + diff --git a/src/core_plugins/kibana/public/visualize/listing/visualize_listing.js b/src/core_plugins/kibana/public/visualize/listing/visualize_listing.js index a6f839a8481c1..b8202cbe17662 100644 --- a/src/core_plugins/kibana/public/visualize/listing/visualize_listing.js +++ b/src/core_plugins/kibana/public/visualize/listing/visualize_listing.js @@ -22,24 +22,44 @@ import 'ui/pager_control'; import 'ui/pager'; import { uiModules } from 'ui/modules'; import { timefilter } from 'ui/timefilter'; +import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; import { i18n } from '@kbn/i18n'; +import chrome from 'ui/chrome'; import { VisualizeListingTable } from './visualize_listing_table'; +import { NewVisModal } from '../wizard/new_vis_modal'; + +import { injectI18nProvider } from '@kbn/i18n/react'; const app = uiModules.get('app/visualize', ['ngRoute', 'react']); -app.directive('visualizeListingTable', function (reactDirective) { - return reactDirective(VisualizeListingTable); -}); +app.directive('visualizeListingTable', reactDirective => reactDirective(VisualizeListingTable)); +app.directive('newVisModal', reactDirective => reactDirective(injectI18nProvider(NewVisModal))); -export function VisualizeListingController($injector) { +export function VisualizeListingController($injector, createNewVis) { const Notifier = $injector.get('Notifier'); const Private = $injector.get('Private'); const config = $injector.get('config'); - const breadcrumbState = $injector.get('breadcrumbState'); + + this.visTypeRegistry = Private(VisTypesRegistryProvider); timefilter.disableAutoRefreshSelector(); timefilter.disableTimeRangeSelector(); + this.showNewVisModal = false; + + this.createNewVis = () => { + this.showNewVisModal = true; + }; + + this.closeNewVisModal = () => { + this.showNewVisModal = false; + }; + + if (createNewVis) { + // In case the user navigated to the page via the /visualize/new URL we start the dialog immediately + this.createNewVis(); + } + // TODO: Extract this into an external service. const services = Private(SavedObjectRegistryProvider).byLoaderPropertiesName; const visualizationService = services.visualizations; @@ -52,7 +72,7 @@ export function VisualizeListingController($injector) { this.totalItems = result.total; this.showLimitError = result.total > config.get('savedObjects:listingLimit'); this.listingLimit = config.get('savedObjects:listingLimit'); - return result.hits.filter(result => (isLabsEnabled || result.type.stage !== 'lab')); + return result.hits.filter(result => (isLabsEnabled || result.type.stage !== 'experimental')); }); }; @@ -61,7 +81,7 @@ export function VisualizeListingController($injector) { .catch(error => notify.error(error)); }; - breadcrumbState.set([{ + chrome.breadcrumbs.set([{ text: i18n.translate('kbn.visualize.visualizeListingBreadcrumbsTitle', { defaultMessage: 'Visualize', }) diff --git a/src/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js b/src/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js index e1765c58484f7..8eb79f99a2af2 100644 --- a/src/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js +++ b/src/core_plugins/kibana/public/visualize/listing/visualize_listing_table.js @@ -260,14 +260,14 @@ export class VisualizeListingTable extends Component { this.setState({ selectedRowIds: newSelectedIds }); }; - onCreate() { - window.location = '#/visualize/new'; + onCreate = () => { + this.props.onCreateVis(); } renderToolBarActions() { return this.state.selectedRowIds.length > 0 ? : - ; + ; } renderPager() { @@ -323,4 +323,5 @@ export class VisualizeListingTable extends Component { VisualizeListingTable.propTypes = { deleteSelectedItems: PropTypes.func, fetchItems: PropTypes.func, + onCreateVis: PropTypes.func.isRequired, }; diff --git a/src/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.js b/src/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.js index 8909fabb97b33..0e31ccf313863 100644 --- a/src/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.js +++ b/src/core_plugins/kibana/public/visualize/saved_visualizations/_saved_vis.js @@ -141,6 +141,8 @@ uiModules self.visState ); + self.vis.savedSearchId = self.savedSearchId; + return self.vis; }; @@ -150,6 +152,7 @@ uiModules self.vis.indexPattern = self.searchSource.getField('index'); self.visState.title = self.title; self.vis.setState(self.visState); + self.vis.savedSearchId = self.savedSearchId; }; return SavedVis; diff --git a/src/core_plugins/kibana/public/visualize/visualize_constants.js b/src/core_plugins/kibana/public/visualize/visualize_constants.ts similarity index 100% rename from src/core_plugins/kibana/public/visualize/visualize_constants.js rename to src/core_plugins/kibana/public/visualize/visualize_constants.ts diff --git a/src/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap b/src/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap new file mode 100644 index 0000000000000..ab45dcb850704 --- /dev/null +++ b/src/core_plugins/kibana/public/visualize/wizard/__snapshots__/new_vis_modal.test.tsx.snap @@ -0,0 +1,768 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`NewVisModal should render as expected 1`] = ` + + + + + + +
+
+ + + +
+ + +
+ +
+ + New Visualization + +
+
+
+
+
+ +
+ +
+ +
+ +
+ + +
+
+ + + + +
+ + + + + +
+
+
+
+
+
+
+
+ +
+ +
+ + Vis Type 1 + + } + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + > + + + + Vis with search + + } + onBlur={[Function]} + onClick={[Function]} + onFocus={[Function]} + onMouseEnter={[Function]} + onMouseLeave={[Function]} + > + + +
+
+
+
+
+
+
+
+ +
+ +

+ + Select a visualization type + +

+
+ +
+ + + +
+

+ + Start creating your visualization by selecting a type for that visualization. + +

+
+
+
+
+ +
+ +
+ +
+
+
+ + + + + + +`; diff --git a/src/core_plugins/kibana/public/visualize/wizard/_dialog.scss b/src/core_plugins/kibana/public/visualize/wizard/_dialog.scss new file mode 100644 index 0000000000000..6a95123c2f7cc --- /dev/null +++ b/src/core_plugins/kibana/public/visualize/wizard/_dialog.scss @@ -0,0 +1,99 @@ +.visNewVisDialog { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='166' height='157' viewBox='0 0 166 157'%3E%3Cdefs%3E%3ClinearGradient id='untitled-2-a' x1='0%25' y1='0%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%23FFF' stop-opacity='.2'/%3E%3Cstop offset='100%25' stop-opacity='0'/%3E%3C/linearGradient%3E%3CradialGradient id='untitled-2-b' cx='0%25' cy='0%25' r='127.787%25' fx='0%25' fy='0%25' gradientTransform='matrix(.681 .68098 -.63326 .7323 0 0)'%3E%3Cstop offset='0%25' stop-color='%23BBB' stop-opacity='.1'/%3E%3Cstop offset='100%25' stop-opacity='.5'/%3E%3C/radialGradient%3E%3ClinearGradient id='untitled-2-c' x1='0%25' y1='0%25' y2='100%25'%3E%3Cstop offset='0%25' stop-color='%23FFF' stop-opacity='.4'/%3E%3Cstop offset='100%25' stop-opacity='0'/%3E%3C/linearGradient%3E%3CradialGradient id='untitled-2-d' cx='0%25' cy='0%25' r='148.851%25' fx='0%25' fy='0%25' gradientTransform='matrix(.6718 .67182 -.74072 .60932 0 0)'%3E%3Cstop offset='0%25' stop-color='%23FFF' stop-opacity='.101'/%3E%3Cstop offset='100%25' stop-opacity='.15'/%3E%3C/radialGradient%3E%3CradialGradient id='untitled-2-e' cx='0%25' cy='0%25' r='127.349%25' fx='0%25' fy='0%25' gradientTransform='matrix(.68331 .68332 -.73013 .63951 0 0)'%3E%3Cstop offset='0%25' stop-color='%23BBB' stop-opacity='.1'/%3E%3Cstop offset='100%25' stop-opacity='.5'/%3E%3C/radialGradient%3E%3C/defs%3E%3Cg opacity='.5' fill='none' fill-rule='evenodd' transform='matrix(-1 0 0 1 166 0)'%3E%3Cg opacity='.65' transform='matrix(0 -1 -1 0 146 157)'%3E%3Cpolygon fill='%23DD0A73' points='0 0 157 146 0 146' opacity='.418'/%3E%3Cpolygon fill='url(%23untitled-2-a)' points='0 0 157 146 0 146' style='mix-blend-mode:overlay'/%3E%3Cpolygon fill='url(%23untitled-2-b)' points='0 0 157 146 0 146' opacity='.618' style='mix-blend-mode:overlay'/%3E%3C/g%3E%3Cg opacity='.65' transform='translate(88 71)'%3E%3Cpath fill='%23017F75' d='M0,86 L78,86 C74.2038079,48.730962 43.6293886,16.7871605 0,0 L0,86 Z' opacity='.409'/%3E%3Cpath fill='url(%23untitled-2-c)' d='M0,86 L78,86 C74.2038079,48.730962 43.6293886,16.7871605 0,0 L0,86 Z' style='mix-blend-mode:overlay'/%3E%3Cpath fill='url(%23untitled-2-d)' d='M0,86 L78,86 C74.2038079,48.730962 43.6293886,16.7871605 0,0 L0,86 Z' opacity='.663' style='mix-blend-mode:overlay'/%3E%3C/g%3E%3Cg opacity='.15' transform='translate(73 79)'%3E%3Cpolygon fill='%23353535' points='0 0 73 78 0 78' opacity='.38'/%3E%3Cpolygon fill='url(%23untitled-2-a)' points='0 0 73 78 0 78' style='mix-blend-mode:overlay'/%3E%3Cpolygon fill='url(%23untitled-2-e)' points='0 0 73 78 0 78' style='mix-blend-mode:overlay'/%3E%3C/g%3E%3C/g%3E%3C/svg%3E%0A"); + background-repeat: no-repeat; + background-position: calc(100% + 1px) calc(100% + 1px); + background-size: 37%; +} + +.visNewVisDialog__body { + display: flex; + padding: $euiSizeM $euiSizeL 0; +} + +.visNewVisDialog__searchWrapper { + flex-shrink: 0; +} + +.visNewVisDialog__typesWrapper { + max-width: $euiSizeXXL * 10; + padding-top: 2px; // Account for search field dropshadow + padding-bottom: $euiSize; + // Add overflow shadows via pseudo elements + position: relative; + &::before, + &::after { + content: ""; + display: block; + position: absolute; + height: $euiSizeXXL; + left: 0; + right: 0; + pointer-events: none; + } + + &::before { + top: -$euiSizeXXL + 2px; // Account for search field dropshadow + @include euiOverflowShadowBottom; + } + + &::after { + bottom: -$euiSizeL; + @include euiOverflowShadowTop; + } +} + +.visNewVisDialog__types { + @include euiScrollBar; + // EUITODO: allow for more (calculated) widths of `EuiKeyPadMenu` + width: auto; + overflow-y: auto; + padding-top: $euiSize; + justify-content: center; + padding-bottom: $euiSize; +} + +.visNewVisDialog__description { + width: $euiSizeXL * 10; +} + +.visNewVisDialog__type:disabled { + opacity: 0.2; + pointer-events: none; +} + +.visNewVisDialog__typeLegacyIcon { + font-size: $euiSizeL; + color: $euiColorSecondary; +} + +.visNewVisDialog__typeImage { + @include size($euiSizeL); +} + +@include euiBreakpoint('xs', 's') { + .visNewVisDialog { + background-image: none; + } + + .visNewVisDialog__typesWrapper { + max-width: none; + } + + .visNewVisDialog__types { + justify-content: flex-start; + } + + .visNewVisDialog__description { + display: none; + } +} + +@include internetExplorerOnly { + .visNewVisDialog { + width: 820px; + } + + .visNewVisDialog__body { + flex-basis: 800px; + } +} diff --git a/src/core_plugins/kibana/public/visualize/wizard/_index.scss b/src/core_plugins/kibana/public/visualize/wizard/_index.scss index 957f378640ea6..b12194ed5c365 100644 --- a/src/core_plugins/kibana/public/visualize/wizard/_index.scss +++ b/src/core_plugins/kibana/public/visualize/wizard/_index.scss @@ -1 +1,2 @@ @import './wizard'; +@import './dialog'; diff --git a/src/core_plugins/kibana/public/visualize/wizard/_wizard.scss b/src/core_plugins/kibana/public/visualize/wizard/_wizard.scss index 823a588d61aa1..0e811b500a869 100644 --- a/src/core_plugins/kibana/public/visualize/wizard/_wizard.scss +++ b/src/core_plugins/kibana/public/visualize/wizard/_wizard.scss @@ -30,19 +30,3 @@ .visWizard__savedObjectFinder { padding: $euiSizeS; /* 1 */ } - -/** - * This preserves backwards-compatibility with any plugins that are still specifying an `icon` - * class. - * - * 1. Size icon correctly to match xxLarge EuiIcon - */ - .visWizard__visTypeIcon { - color: $euiColorFullShade; - font-size: $euiSizeXXL; /* 1 */ -} - -// SASSTODO: Remove img element selector when `.kuiGalleryItem__image img` selector is removed -img.visWizard__visTypeImage { // Use element selector to override base .kuiGalleryItem__image styles - @include size($euiSizeXXL); /* 1 */ -} diff --git a/src/core_plugins/kibana/public/visualize/wizard/index.ts b/src/core_plugins/kibana/public/visualize/wizard/index.ts new file mode 100644 index 0000000000000..7a3fc63af5259 --- /dev/null +++ b/src/core_plugins/kibana/public/visualize/wizard/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { NewVisModal } from './new_vis_modal'; +export { showNewVisModal } from './show_new_vis'; diff --git a/src/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx b/src/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx new file mode 100644 index 0000000000000..e4b3b7d3b1adb --- /dev/null +++ b/src/core_plugins/kibana/public/visualize/wizard/new_vis_modal.test.tsx @@ -0,0 +1,128 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { I18nProvider } from '@kbn/i18n/react'; +import React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; + +const settingsGet = jest.fn(); + +jest.mock('ui/chrome', () => ({ + getUiSettingsClient: () => ({ + get: settingsGet, + }), +})); + +import { NewVisModal } from './new_vis_modal'; + +import { VisType } from 'ui/vis'; + +describe('NewVisModal', () => { + const defaultVisTypeParams = { + hidden: false, + visualization: class Controller { + public render = jest.fn(); + public destroy = jest.fn(); + }, + requiresSearch: false, + requestHandler: 'none', + responseHandler: 'none', + }; + const visTypes: VisType[] = [ + { name: 'vis', title: 'Vis Type 1', stage: 'production', ...defaultVisTypeParams }, + { name: 'visExp', title: 'Experimental Vis', stage: 'experimental', ...defaultVisTypeParams }, + { + name: 'visWithSearch', + title: 'Vis with search', + stage: 'production', + ...defaultVisTypeParams, + }, + ]; + + it('should render as expected', () => { + const wrapper = mountWithIntl( + + null} visTypesRegistry={visTypes} /> + + ); + expect(wrapper).toMatchSnapshot(); + }); + + it('should show a button for regular visualizations', () => { + const wrapper = mountWithIntl( + + null} visTypesRegistry={visTypes} /> + + ); + expect(wrapper.find('[data-test-subj="visType-vis"]').exists()).toBe(true); + }); + + describe('open editor', () => { + it('should open the editor for visualizations without search', () => { + window.location.assign = jest.fn(); + const wrapper = mountWithIntl( + + null} visTypesRegistry={visTypes} /> + + ); + const visButton = wrapper.find('button[data-test-subj="visType-vis"]'); + visButton.simulate('click'); + expect(window.location.assign).toBeCalledWith('#/visualize/create?type=vis'); + }); + + it('passes through editor params to the editor URL', () => { + window.location.assign = jest.fn(); + const wrapper = mountWithIntl( + + null} + visTypesRegistry={visTypes} + editorParams={['foo=true', 'bar=42']} + /> + + ); + const visButton = wrapper.find('button[data-test-subj="visType-vis"]'); + visButton.simulate('click'); + expect(window.location.assign).toBeCalledWith('#/visualize/create?type=vis&foo=true&bar=42'); + }); + }); + + describe('experimental visualizations', () => { + it('should not show experimental visualizations if visualize:enableLabs is false', () => { + settingsGet.mockReturnValue(false); + const wrapper = mountWithIntl( + + null} visTypesRegistry={visTypes} /> + + ); + expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(false); + }); + + it('should show experimental visualizations if visualize:enableLabs is true', () => { + settingsGet.mockReturnValue(true); + const wrapper = mountWithIntl( + + null} visTypesRegistry={visTypes} /> + + ); + expect(wrapper.find('[data-test-subj="visType-visExp"]').exists()).toBe(true); + }); + }); +}); diff --git a/src/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx b/src/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx new file mode 100644 index 0000000000000..bc206a70fa361 --- /dev/null +++ b/src/core_plugins/kibana/public/visualize/wizard/new_vis_modal.tsx @@ -0,0 +1,79 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; + +import { EuiModal, EuiOverlayMask } from '@elastic/eui'; + +import { VisualizeConstants } from '../visualize_constants'; + +import { TypeSelection } from './type_selection'; + +import chrome from 'ui/chrome'; +import { VisType } from 'ui/vis'; + +interface TypeSelectionProps { + isOpen: boolean; + onClose: () => void; + visTypesRegistry: VisType[]; + editorParams?: string[]; +} + +class NewVisModal extends React.Component { + public static defaultProps = { + editorParams: [], + }; + + private readonly isLabsEnabled: boolean; + + constructor(props: TypeSelectionProps) { + super(props); + this.isLabsEnabled = chrome.getUiSettingsClient().get('visualize:enableLabs'); + } + + public render() { + if (!this.props.isOpen) { + return null; + } + + return ( + + + + + + ); + } + + private onVisTypeSelected = (visType: VisType) => { + const baseUrl = + visType.requiresSearch && visType.options.showIndexSelection + ? `#${VisualizeConstants.WIZARD_STEP_2_PAGE_PATH}?` + : `#${VisualizeConstants.CREATE_PATH}?`; + const params = [`type=${encodeURIComponent(visType.name)}`, ...this.props.editorParams!]; + this.props.onClose(); + location.assign(`${baseUrl}${params.join('&')}`); + }; +} + +export { NewVisModal }; diff --git a/src/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx b/src/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx new file mode 100644 index 0000000000000..fe4d28900c11e --- /dev/null +++ b/src/core_plugins/kibana/public/visualize/wizard/show_new_vis.tsx @@ -0,0 +1,53 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import ReactDOM from 'react-dom'; + +import { I18nProvider } from '@kbn/i18n/react'; +import { VisType } from 'ui/vis'; +import { NewVisModal } from './new_vis_modal'; + +interface ShowNewVisModalParams { + editorParams?: string[]; +} + +export function showNewVisModal( + visTypeRegistry: VisType[], + { editorParams = [] }: ShowNewVisModalParams = {} +) { + const container = document.createElement('div'); + const onClose = () => { + ReactDOM.unmountComponentAtNode(container); + document.body.removeChild(container); + }; + + document.body.appendChild(container); + const element = ( + + + + ); + ReactDOM.render(element, container); +} diff --git a/src/core_plugins/kibana/public/visualize/wizard/step_1.html b/src/core_plugins/kibana/public/visualize/wizard/step_1.html deleted file mode 100644 index ff13e9a755559..0000000000000 --- a/src/core_plugins/kibana/public/visualize/wizard/step_1.html +++ /dev/null @@ -1,102 +0,0 @@ - - - -
- - -
-
- -
-
- -
-

- Select visualization type -

-
- - - - -
-
diff --git a/src/core_plugins/kibana/public/visualize/wizard/type_selection/index.ts b/src/core_plugins/kibana/public/visualize/wizard/type_selection/index.ts new file mode 100644 index 0000000000000..c4093b4dec3e8 --- /dev/null +++ b/src/core_plugins/kibana/public/visualize/wizard/type_selection/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { TypeSelection } from './type_selection'; diff --git a/src/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.tsx b/src/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.tsx new file mode 100644 index 0000000000000..5a68be8623686 --- /dev/null +++ b/src/core_plugins/kibana/public/visualize/wizard/type_selection/new_vis_help.tsx @@ -0,0 +1,34 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { FormattedMessage } from '@kbn/i18n/react'; +import React from 'react'; + +import { EuiText } from '@elastic/eui'; + +export const NewVisHelp = () => ( + +

+ +

+
+); diff --git a/src/core_plugins/kibana/public/visualize/wizard/type_selection/type_selection.tsx b/src/core_plugins/kibana/public/visualize/wizard/type_selection/type_selection.tsx new file mode 100644 index 0000000000000..55554ed784512 --- /dev/null +++ b/src/core_plugins/kibana/public/visualize/wizard/type_selection/type_selection.tsx @@ -0,0 +1,203 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { sortByOrder } from 'lodash'; +import React, { ChangeEvent } from 'react'; + +import { + EuiFieldSearch, + EuiFlexGroup, + EuiFlexItem, + EuiKeyPadMenu, + EuiKeyPadMenuItemButton, + EuiModalHeader, + EuiModalHeaderTitle, + EuiSpacer, + EuiTitle, +} from '@elastic/eui'; +import { NewVisHelp } from './new_vis_help'; +import { VisHelpText } from './vis_help_text'; +import { VisTypeIcon } from './vis_type_icon'; + +import { memoizeLast } from 'ui/utils/memoize'; +import { VisType } from 'ui/vis'; + +interface VisTypeListEntry extends VisType { + highlighted: boolean; +} + +interface TypeSelectionProps { + onVisTypeSelected: (visType: VisType) => void; + visTypesRegistry: VisType[]; + showExperimental: boolean; +} + +interface TypeSelectionState { + highlightedType: VisType | null; + query: string; +} + +class TypeSelection extends React.Component { + public state = { + highlightedType: null, + query: '', + }; + + private readonly getFilteredVisTypes = memoizeLast(this.filteredVisTypes); + + public render() { + const { query, highlightedType } = this.state; + const visTypes = this.getFilteredVisTypes(this.props.visTypesRegistry, query); + return ( + + + + + + +
+ + + + + + + + + {visTypes.map(this.renderVisType)} + + + + + + {highlightedType ? ( + + ) : ( + + +

+ +

+
+ + +
+ )} +
+
+
+
+ ); + } + + private filteredVisTypes(visTypes: VisType[], query: string): VisTypeListEntry[] { + const types = visTypes.filter(type => { + // Filter out all lab visualizations if lab mode is not enabled + if (!this.props.showExperimental && type.stage === 'experimental') { + return false; + } + + // Filter out hidden visualizations + if (type.hidden) { + return false; + } + + return true; + }); + + let entries: VisTypeListEntry[]; + if (!query) { + entries = types.map(type => ({ ...type, highlighted: false })); + } else { + const q = query.toLowerCase(); + entries = types.map(type => { + const matchesQuery = + type.name.toLowerCase().includes(q) || + type.title.toLowerCase().includes(q) || + (typeof type.description === 'string' && type.description.toLowerCase().includes(q)); + return { ...type, highlighted: matchesQuery }; + }); + } + + return sortByOrder(entries, ['highlighted', 'title'], ['desc', 'asc']); + } + + private renderVisType = (visType: VisTypeListEntry) => { + let stage = {}; + if (visType.stage === 'experimental') { + stage = { + betaBadgeLabel: i18n.translate('kbn.visualize.newVisWizard.experimentalTitle', { + defaultMessage: 'Experimental', + }), + betaBadgeTooltipContent: i18n.translate('kbn.visualize.newVisWizard.experimentalTooltip', { + defaultMessage: 'This visualization is experimental.', + }), + }; + } + const isDisabled = this.state.query !== '' && !visType.highlighted; + return ( + {visType.title}} + onClick={() => this.props.onVisTypeSelected(visType)} + onFocus={() => this.highlightType(visType)} + onMouseEnter={() => this.highlightType(visType)} + onMouseLeave={() => this.highlightType(null)} + onBlur={() => this.highlightType(null)} + className="visNewVisDialog__type" + data-test-subj={`visType-${visType.name}`} + data-vis-stage={visType.stage} + disabled={isDisabled} + {...stage} + > + + + ); + }; + + private highlightType(visType: VisType | null) { + this.setState({ + highlightedType: visType, + }); + } + + private onQueryChange = (ev: ChangeEvent) => { + this.setState({ + query: ev.target.value, + }); + }; +} + +export { TypeSelection }; diff --git a/src/core_plugins/kibana/public/visualize/wizard/type_selection/vis_help_text.tsx b/src/core_plugins/kibana/public/visualize/wizard/type_selection/vis_help_text.tsx new file mode 100644 index 0000000000000..249931733dc22 --- /dev/null +++ b/src/core_plugins/kibana/public/visualize/wizard/type_selection/vis_help_text.tsx @@ -0,0 +1,55 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { FormattedMessage } from '@kbn/i18n/react'; +import React from 'react'; + +import { EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; + +import { VisType } from 'ui/vis'; + +interface VisHelpTextProps { + visType: VisType; +} + +export const VisHelpText = ({ visType }: VisHelpTextProps) => { + return ( + + +

{visType.title}

+
+ + {visType.stage === 'experimental' && ( + + + + + + + + + )} + {visType.description} +
+ ); +}; diff --git a/src/core_plugins/kibana/public/visualize/wizard/type_selection/vis_type_icon.tsx b/src/core_plugins/kibana/public/visualize/wizard/type_selection/vis_type_icon.tsx new file mode 100644 index 0000000000000..44c4f4835d5aa --- /dev/null +++ b/src/core_plugins/kibana/public/visualize/wizard/type_selection/vis_type_icon.tsx @@ -0,0 +1,54 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { EuiIcon } from '@elastic/eui'; +import classnames from 'classnames'; +import React from 'react'; +import { VisType } from 'ui/vis'; + +interface VisTypeIconProps { + visType: VisType; +} + +/** + * This renders the icon for a specific visualization type. + * This currently checks the following: + * - If visType.image is set, use that as the `src` of an image + * - If legacyIcon is set, use that as a classname for a span with kuiIcon (to be removed in 7.0) + * - Otherwise use the visType.icon as an EuiIcon or the 'empty' icon if that's not set + */ +export const VisTypeIcon = ({ visType }: VisTypeIconProps) => { + const legacyIconClass = classnames( + 'kuiIcon', + 'visNewVisDialog__typeLegacyIcon', + visType.legacyIcon + ); + return ( + + {visType.image && ( + + )} + {!visType.image && visType.legacyIcon && } + {!visType.image && + !visType.legacyIcon && ( + + ); +}; diff --git a/src/core_plugins/kibana/public/visualize/wizard/wizard.js b/src/core_plugins/kibana/public/visualize/wizard/wizard.js index 218561a7003a6..899c64fb8047a 100644 --- a/src/core_plugins/kibana/public/visualize/wizard/wizard.js +++ b/src/core_plugins/kibana/public/visualize/wizard/wizard.js @@ -22,177 +22,16 @@ import 'ui/directives/saved_object_finder'; import 'ui/directives/paginated_selectable_list'; import '../../discover/saved_searches/saved_searches'; -import _ from 'lodash'; -import { CATEGORY, CATEGORY_DISPLAY_NAMES } from 'ui/vis/vis_category'; import { DashboardConstants } from '../../dashboard/dashboard_constants'; import { VisualizeConstants } from '../visualize_constants'; import routes from 'ui/routes'; -import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; import { uiModules } from 'ui/modules'; -import visualizeWizardStep1Template from './step_1.html'; import visualizeWizardStep2Template from './step_2.html'; import { SavedObjectsClientProvider } from 'ui/saved_objects'; import { timefilter } from 'ui/timefilter'; const module = uiModules.get('app/visualize', ['kibana/courier']); -/******** -/** Wizard Step 1 -/********/ - -// Redirect old route to new route. -routes.when('/visualize/step/1', { - redirectTo: VisualizeConstants.WIZARD_STEP_1_PAGE_PATH, -}); - -routes.when(VisualizeConstants.WIZARD_STEP_1_PAGE_PATH, { - template: visualizeWizardStep1Template, - controller: 'VisualizeWizardStep1', -}); - -module.controller('VisualizeWizardStep1', function ($scope, $route, kbnUrl, Private, config) { - timefilter.disableAutoRefreshSelector(); - timefilter.disableTimeRangeSelector(); - - const addToDashMode = $route.current.params[DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM]; - kbnUrl.removeParam(DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM); - - const visTypes = Private(VisTypesRegistryProvider); - const isLabsEnabled = config.get('visualize:enableLabs'); - $scope.toggleLabView = () => { - $route.current.params.lab = !$route.current.params.lab; - $route.updateParams($route.current.params); - $route.reload(); - }; - - const categoryToVisTypesMap = {}; - - visTypes.forEach(visType => { - - let categoryName = visType.category; - - if (categoryName === CATEGORY.HIDDEN) { - return; - } - - if (!isLabsEnabled && visType.stage === 'lab') { - return; - } - - // If the specified category doesn't have a value in our display names - // mapping (most likely because the vis specified a random category, not using - // CATEGORY values), just move it to the OTHER category. - if (!CATEGORY_DISPLAY_NAMES[categoryName]) { - categoryName = CATEGORY.OTHER; - } - - // Create category object if it doesn't exist yet. - if (!categoryToVisTypesMap[categoryName]) { - categoryToVisTypesMap[categoryName] = { - label: CATEGORY_DISPLAY_NAMES[categoryName], - list: [], - }; - } - - const categoryVisTypes = categoryToVisTypesMap[categoryName]; - - // Add the visType to the list and sort them by their title. - categoryVisTypes.list = _.sortBy( - categoryVisTypes.list.concat(visType), - type => type.title - ); - }); - - // Sort the categories alphabetically. - const sortedVisTypeCategories = Object.values(categoryToVisTypesMap).sort((a, b) => { - const other = CATEGORY.OTHER.toLowerCase(); - - // Put "other" category at the end of the list. - const labelA = a.label.toLowerCase(); - if (labelA === other) return 1; - - const labelB = b.label.toLowerCase(); - if (labelB === other) return -1; - - if (labelA < labelB) return -1; - if (labelA > labelB) return 1; - return 0; - }); - - $scope.searchTerm = ''; - - $scope.filteredVisTypeCategories = []; - - $scope.$watch('searchTerm', () => { - function getVisTypeCategories() { - const normalizedSearchTerm = $scope.searchTerm.toLowerCase().trim(); - - const filteredVisTypeCategories = sortedVisTypeCategories.map(category => { - // Include entire category if the category matches the search term. - if (category.label.toLowerCase().includes(normalizedSearchTerm)) { - return category; - } - - // Otherwise, return just the vis types in the category which match. - const filteredVisTypes = category.list.filter(visType => { - return visType.title.toLowerCase().includes(normalizedSearchTerm); - }); - - return { - label: category.label, - list: filteredVisTypes, - }; - }); - - return filteredVisTypeCategories.filter(category => category.list.length); - } - - $scope.filteredVisTypeCategories = getVisTypeCategories(); - }); - - $scope.getVisTypeId = type => { - return _.camelCase(type.name); - }; - - $scope.getVisTypeTooltip = type => { - //to not clutter the tooltip, just only notify if labs or experimental. - //labs is more important in this regard. - let prefix = ''; - if (type.stage === 'lab') { - prefix = '(Lab)'; - } else if (type.stage === 'experimental') { - prefix = '(Experimental)'; - } - return `${prefix} ${type.description}`; - }; - - $scope.getVisTypeTooltipPosition = index => { - // Tooltips should appear on the bottom by default, unless they're on the last row. This is a - // cheap workaround to automatically positioning the tooltip so that it won't disappear off - // the edge of the screen. - if (index === $scope.filteredVisTypeCategories.length - 1) { - return 'top'; - } - - return 'bottom'; - }; - - $scope.getVisTypeUrl = function (visType) { - const baseUrl = - visType.requiresSearch && visType.options.showIndexSelection - ? `#${VisualizeConstants.WIZARD_STEP_2_PAGE_PATH}?` - : `#${VisualizeConstants.CREATE_PATH}?`; - - const params = [`type=${encodeURIComponent(visType.name)}`]; - - if (addToDashMode) { - params.push(DashboardConstants.ADD_VISUALIZATION_TO_DASHBOARD_MODE_PARAM); - } - - return baseUrl + params.join('&'); - }; -}); - /******** /** Wizard Step 2 /********/ diff --git a/src/core_plugins/kibana/server/tutorials/apm/saved_objects/index_pattern.json b/src/core_plugins/kibana/server/tutorials/apm/saved_objects/index_pattern.json index 1a071768ab78c..066e425e866fa 100644 --- a/src/core_plugins/kibana/server/tutorials/apm/saved_objects/index_pattern.json +++ b/src/core_plugins/kibana/server/tutorials/apm/saved_objects/index_pattern.json @@ -1,7 +1,7 @@ { "attributes": { "fieldFormatMap": "{\"@timestamp\":{\"id\":\"date\"},\"context.service.name\":{\"id\":\"url\",\"params\":{\"labelTemplate\":\"{{value}}\",\"openLinkInCurrentTab\":true,\"urlTemplate\":\"../app/kibana#/dashboard/41b5d920-7821-11e7-8c47-65b845b5cfb3?_a=(query:(language:lucene,query:'context.service.name:\\\"{{value}}\\\"'))\"}},\"error id icon\":{\"id\":\"url\",\"params\":{\"labelTemplate\":\"-\"}},\"error.grouping_key\":{\"id\":\"url\",\"params\":{\"labelTemplate\":\"View Error Details\",\"openLinkInCurrentTab\":true,\"urlTemplate\":\"../app/kibana#/dashboard/5f08a870-7c6a-11e7-aa55-3b0d52c71c60?_a=(query:(language:lucene,query:'error.grouping_key:{{value}}'))\"}},\"span.duration.us\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"microseconds\",\"outputFormat\":\"asMilliseconds\",\"outputPrecision\":0}},\"system.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.memory.actual.free\":{\"id\":\"bytes\"},\"system.memory.total\":{\"id\":\"bytes\"},\"system.process.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.process.memory.rss.bytes\":{\"id\":\"bytes\"},\"system.process.memory.size\":{\"id\":\"bytes\"},\"transaction.duration.us\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"microseconds\",\"outputFormat\":\"asMilliseconds\",\"outputPrecision\":0}},\"transaction.id\":{\"id\":\"url\",\"params\":{\"labelTemplate\":\"View Spans\",\"openLinkInCurrentTab\":true,\"urlTemplate\":\"../app/kibana#/dashboard/3e3de700-7de0-11e7-b115-df9c90da2df1?_a=(query:(language:lucene,query:'transaction.id:{{value}}'))\"}},\"view errors\":{\"id\":\"url\",\"params\":{\"labelTemplate\":\"View Errors\",\"openLinkInCurrentTab\":true,\"urlTemplate\":\"../app/kibana#/dashboard/37f6fac0-7c6a-11e7-aa55-3b0d52c71c60?_a=(query:(language:lucene,query:'context.service.name:\\\"{{value}}\\\"'))\"}},\"view spans\":{\"id\":\"url\",\"params\":{\"labelTemplate\":\"View Spans\"}}}", - "fields": "[{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"beat.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"beat.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"beat.timezone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"beat.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"@timestamp\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"fields\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"meta.cloud.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"meta.cloud.instance_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"meta.cloud.instance_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"meta.cloud.machine_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"meta.cloud.availability_zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"meta.cloud.project_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"meta.cloud.region\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"docker.container.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"docker.container.image\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"docker.container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"docker.container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.ip\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.namespace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.annotations\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.image\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"listening\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.event\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.tags\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.user.username\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.user.ip\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.user.user-agent\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.request.url.raw\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.request.url.protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.request.url.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.request.url.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.request.url.port\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.request.url.pathname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.request.url.search\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.request.url.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.request.http_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.request.method\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.response.status_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.response.finished\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.system.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.system.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.system.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.system.ip\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.process.pid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.process.ppid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.process.title\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.service.environment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.service.language.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.service.language.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.service.runtime.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.service.runtime.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.service.framework.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.service.framework.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.service.agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.service.agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":true,\"lang\":\"painless\",\"name\":\"view errors\",\"script\":\"doc['context.service.name'].value\",\"scripted\":true,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":true,\"lang\":\"painless\",\"name\":\"error id icon\",\"script\":\"doc['error.grouping_key'].value\",\"scripted\":true,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.culprit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.grouping_key\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.handled\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.logger_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.param_message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.total\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.actual.free\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.rss.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.bundle_filepath\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"view spans\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.start.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.parent\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name.keyword\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.result\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks.navigationTiming\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.sampled\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.span_count.dropped.total\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_id\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_index\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_score\",\"scripted\":false,\"searchable\":false,\"type\":\"number\"}]", + "fields": "[{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"trace.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"parent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.hex_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.sync\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"beat.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"beat.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"beat.timezone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"beat.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"@timestamp\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"timestamp.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"fields\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"meta.cloud.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"meta.cloud.instance_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"meta.cloud.instance_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"meta.cloud.machine_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"meta.cloud.availability_zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"meta.cloud.project_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"meta.cloud.region\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"docker.container.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"docker.container.image\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"docker.container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"docker.container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.ip\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.pod.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.namespace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.annotations\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.container.image\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"listening\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"processor.event\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.tags\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.user.username\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.user.ip\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.user.user-agent\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.request.url.raw\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.request.url.protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.request.url.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.request.url.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.request.url.port\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.request.url.pathname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.request.url.search\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.request.url.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.request.http_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.http.status_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.request.method\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.response.status_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.response.finished\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.system.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.system.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.system.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.system.ip\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.process.pid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.process.ppid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.process.title\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.service.environment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.service.language.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.service.language.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.service.runtime.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.service.runtime.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.service.framework.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.service.framework.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.service.agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"context.service.agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":true,\"lang\":\"painless\",\"name\":\"view errors\",\"script\":\"doc['context.service.name'].value\",\"scripted\":true,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":true,\"lang\":\"painless\",\"name\":\"error id icon\",\"script\":\"doc['error.grouping_key'].value\",\"scripted\":true,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.culprit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.grouping_key\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":4,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.exception.handled\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.logger_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":2,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.log.param_message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.total\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.memory.actual.free\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.cpu.total.norm.pct\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.memory.rss.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"sourcemap.bundle_filepath\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"view spans\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.start.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"span.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.parent\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name.keyword\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.result\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.marks.navigationTiming\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.sampled\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.span_count.dropped.total\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_id\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_index\",\"scripted\":false,\"searchable\":false,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"indexed\":false,\"name\":\"_score\",\"scripted\":false,\"searchable\":false,\"type\":\"number\"}]", "sourceFilters": "[{\"value\":\"sourcemap.sourcemap\"}]", "timeFieldName": "@timestamp" }, diff --git a/src/core_plugins/kibana/ui_setting_defaults.js b/src/core_plugins/kibana/ui_setting_defaults.js index f86477a89e46d..76b916656e554 100644 --- a/src/core_plugins/kibana/ui_setting_defaults.js +++ b/src/core_plugins/kibana/ui_setting_defaults.js @@ -24,9 +24,11 @@ import { IS_KIBANA_RELEASE } from '../../utils'; export function getUiSettingDefaults() { const weekdays = moment.weekdays().slice(); const [defaultWeekday] = weekdays; - const numeralLanguageIds = numeralLanguages.map(function (numeralLanguage) { + // We add the `en` key manually here, since that's not a real numeral locale, but the + // default fallback in case the locale is not found. + const numeralLanguageIds = ['en', ...numeralLanguages.map(function (numeralLanguage) { return numeralLanguage.id; - }); + })]; // wrapped in provider so that a new instance is given to each app/test return { @@ -218,9 +220,10 @@ export function getUiSettingDefaults() { description: `Never show more than this many bars in date histograms, scale values if needed`, }, 'visualize:enableLabs': { - name: 'Enable labs', + name: 'Enable experimental visualizations', value: true, - description: `Enable lab visualizations in Visualize.`, + description: `Allows users to create, view, and edit experimental visualizations. If disabled, + only visualizations that are considered production-ready are available to the user.`, category: ['visualization'], }, 'visualization:tileMap:maxPrecision': { diff --git a/src/core_plugins/markdown_vis/public/markdown_vis.js b/src/core_plugins/markdown_vis/public/markdown_vis.js index 6c762aa148636..80cb0b6633a24 100644 --- a/src/core_plugins/markdown_vis/public/markdown_vis.js +++ b/src/core_plugins/markdown_vis/public/markdown_vis.js @@ -19,7 +19,6 @@ import { MarkdownVisWrapper } from './markdown_vis_controller'; import { VisFactoryProvider } from 'ui/vis/vis_factory'; -import { CATEGORY } from 'ui/vis/vis_category'; import markdownVisParamsTemplate from './markdown_vis_params.html'; import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; import { DefaultEditorSize } from 'ui/vis/editor_size'; @@ -41,7 +40,6 @@ function MarkdownVisProvider(Private, i18n) { isAccessible: true, icon: 'visText', description: i18n('markdownVis.markdownDescription', { defaultMessage: 'Create a document using markdown syntax' }), - category: CATEGORY.OTHER, visConfig: { component: MarkdownVisWrapper, defaults: { diff --git a/src/core_plugins/metric_vis/public/metric_vis.js b/src/core_plugins/metric_vis/public/metric_vis.js index 119f28296f904..f8d8bac235d18 100644 --- a/src/core_plugins/metric_vis/public/metric_vis.js +++ b/src/core_plugins/metric_vis/public/metric_vis.js @@ -19,7 +19,6 @@ import './metric_vis_params'; import { VisFactoryProvider } from 'ui/vis/vis_factory'; -import { CATEGORY } from 'ui/vis/vis_category'; import { Schemas } from 'ui/vis/editors/default/schemas'; import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; import { vislibColorMaps } from 'ui/vislib/components/color/colormaps'; @@ -41,7 +40,6 @@ function MetricVisProvider(Private, i18n) { title: i18n('metricVis.metricTitle', { defaultMessage: 'Metric' }), icon: 'visMetric', description: i18n('metricVis.metricDescription', { defaultMessage: 'Display a calculation as a single number' }), - category: CATEGORY.DATA, visConfig: { component: MetricVisComponent, defaults: { diff --git a/src/core_plugins/metrics/common/agg_lookup.js b/src/core_plugins/metrics/common/agg_lookup.js index b738442ff6c1f..2de9a1aee206c 100644 --- a/src/core_plugins/metrics/common/agg_lookup.js +++ b/src/core_plugins/metrics/common/agg_lookup.js @@ -18,37 +18,39 @@ */ import _ from 'lodash'; +import { i18n } from '@kbn/i18n'; + const lookup = { - count: 'Count', - calculation: 'Calculation', - std_deviation: 'Std. Deviation', - variance: 'Variance', - sum_of_squares: 'Sum of Sq.', - avg: 'Average', - max: 'Max', - min: 'Min', - sum: 'Sum', - percentile: 'Percentile', - percentile_rank: 'Percentile Rank', - cardinality: 'Cardinality', - value_count: 'Value Count', - derivative: 'Derivative', - cumulative_sum: 'Cumulative Sum', - moving_average: 'Moving Average', - avg_bucket: 'Overall Average', - min_bucket: 'Overall Min', - max_bucket: 'Overall Max', - sum_bucket: 'Overall Sum', - variance_bucket: 'Overall Variance', - sum_of_squares_bucket: 'Overall Sum of Sq.', - std_deviation_bucket: 'Overall Std. Deviation', - series_agg: 'Series Agg', - math: 'Math', - serial_diff: 'Serial Difference', - filter_ratio: 'Filter Ratio', - positive_only: 'Positive Only', - static: 'Static Value', - top_hit: 'Top Hit', + count: i18n.translate('tsvb.aggLookup.countLabel', { defaultMessage: 'Count' }), + calculation: i18n.translate('tsvb.aggLookup.calculationLabel', { defaultMessage: 'Calculation' }), + std_deviation: i18n.translate('tsvb.aggLookup.deviationLabel', { defaultMessage: 'Std. Deviation' }), + variance: i18n.translate('tsvb.aggLookup.varianceLabel', { defaultMessage: 'Variance' }), + sum_of_squares: i18n.translate('tsvb.aggLookup.sumOfSqLabel', { defaultMessage: 'Sum of Sq.' }), + avg: i18n.translate('tsvb.aggLookup.averageLabel', { defaultMessage: 'Average' }), + max: i18n.translate('tsvb.aggLookup.maxLabel', { defaultMessage: 'Max' }), + min: i18n.translate('tsvb.aggLookup.minLabel', { defaultMessage: 'Min' }), + sum: i18n.translate('tsvb.aggLookup.sumLabel', { defaultMessage: 'Sum' }), + percentile: i18n.translate('tsvb.aggLookup.percentileLabel', { defaultMessage: 'Percentile' }), + percentile_rank: i18n.translate('tsvb.aggLookup.percentileRankLabel', { defaultMessage: 'Percentile Rank' }), + cardinality: i18n.translate('tsvb.aggLookup.cardinalityLabel', { defaultMessage: 'Cardinality' }), + value_count: i18n.translate('tsvb.aggLookup.valueCountLabel', { defaultMessage: 'Value Count' }), + derivative: i18n.translate('tsvb.aggLookup.derivativeLabel', { defaultMessage: 'Derivative' }), + cumulative_sum: i18n.translate('tsvb.aggLookup.cumulativeSumLabel', { defaultMessage: 'Cumulative Sum' }), + moving_average: i18n.translate('tsvb.aggLookup.movingAverageLabel', { defaultMessage: 'Moving Average' }), + avg_bucket: i18n.translate('tsvb.aggLookup.overallAverageLabel', { defaultMessage: 'Overall Average' }), + min_bucket: i18n.translate('tsvb.aggLookup.overallMinLabel', { defaultMessage: 'Overall Min' }), + max_bucket: i18n.translate('tsvb.aggLookup.overallMaxLabel', { defaultMessage: 'Overall Max' }), + sum_bucket: i18n.translate('tsvb.aggLookup.overallSumLabel', { defaultMessage: 'Overall Sum' }), + variance_bucket: i18n.translate('tsvb.aggLookup.overallVarianceLabel', { defaultMessage: 'Overall Variance' }), + sum_of_squares_bucket: i18n.translate('tsvb.aggLookup.overallSumOfSqLabel', { defaultMessage: 'Overall Sum of Sq.' }), + std_deviation_bucket: i18n.translate('tsvb.aggLookup.overallStdDeviationLabel', { defaultMessage: 'Overall Std. Deviation' }), + series_agg: i18n.translate('tsvb.aggLookup.seriesAggLabel', { defaultMessage: 'Series Agg' }), + math: i18n.translate('tsvb.aggLookup.mathLabel', { defaultMessage: 'Math' }), + serial_diff: i18n.translate('tsvb.aggLookup.serialDifferenceLabel', { defaultMessage: 'Serial Difference' }), + filter_ratio: i18n.translate('tsvb.aggLookup.filterRatioLabel', { defaultMessage: 'Filter Ratio' }), + positive_only: i18n.translate('tsvb.aggLookup.positiveOnlyLabel', { defaultMessage: 'Positive Only' }), + static: i18n.translate('tsvb.aggLookup.staticValueLabel', { defaultMessage: 'Static Value' }), + top_hit: i18n.translate('tsvb.aggLookup.topHitLabel', { defaultMessage: 'Top Hit' }), }; const pipeline = [ @@ -98,7 +100,10 @@ export function createOptions(type = '_all', siblings = []) { const disabled = _.includes(pipeline, value) ? !enablePipelines : false; return { label: disabled - ? `${label} (use the "+" button to add this pipeline agg)` + ? i18n.translate('tsvb.aggLookup.addPipelineAggDescription', { + defaultMessage: '{label} (use the "+" button to add this pipeline agg)', + values: { label } + }) : label, value, disabled, diff --git a/src/core_plugins/metrics/common/calculate_label.js b/src/core_plugins/metrics/common/calculate_label.js index b6e8dd3dce1b4..8bcb9e2277509 100644 --- a/src/core_plugins/metrics/common/calculate_label.js +++ b/src/core_plugins/metrics/common/calculate_label.js @@ -19,6 +19,8 @@ import { includes, startsWith } from 'lodash'; import lookup from './agg_lookup'; +import { i18n } from '@kbn/i18n'; + const paths = [ 'cumulative_sum', 'derivative', @@ -34,22 +36,34 @@ const paths = [ 'positive_only', ]; export default function calculateLabel(metric, metrics) { - if (!metric) return 'Unknown'; + if (!metric) return i18n.translate('tsvb.calculateLabel.unknownLabel', { defaultMessage: 'Unknown' }); if (metric.alias) return metric.alias; - if (metric.type === 'count') return 'Count'; - if (metric.type === 'calculation') return 'Bucket Script'; - if (metric.type === 'math') return 'Math'; - if (metric.type === 'series_agg') return `Series Agg (${metric.function})`; - if (metric.type === 'filter_ratio') return 'Filter Ratio'; - if (metric.type === 'static') return `Static Value of ${metric.value}`; + if (metric.type === 'count') return i18n.translate('tsvb.calculateLabel.countLabel', { defaultMessage: 'Count' }); + if (metric.type === 'calculation') { + return i18n.translate('tsvb.calculateLabel.bucketScriptsLabel', { defaultMessage: 'Bucket Script' }); + } + if (metric.type === 'math') return i18n.translate('tsvb.calculateLabel.mathLabel', { defaultMessage: 'Math' }); + if (metric.type === 'series_agg') { + return i18n.translate('tsvb.calculateLabel.seriesAggLabel', + { defaultMessage: 'Series Agg ({metricFunction})', values: { metricFunction: metric.function } } + ); + } + if (metric.type === 'filter_ratio') return i18n.translate('tsvb.calculateLabel.filterRatioLabel', { defaultMessage: 'Filter Ratio' }); + if (metric.type === 'static') { + return i18n.translate('tsvb.calculateLabel.staticValueLabel', + { defaultMessage: 'Static Value of {metricValue}', values: { metricValue: metric.value } } + ); + } if (metric.type === 'percentile_rank') { - return `${lookup[metric.type]} (${metric.value}) of ${metric.field}`; + return i18n.translate('tsvb.calculateLabel.percentileRankLabel', { + defaultMessage: '{lookupMetricType} ({metricValue}) of {metricField}', + values: { lookupMetricType: lookup[metric.type], metricValue: metric.value, metricField: metric.field } + }); } if (includes(paths, metric.type)) { - let additionalLabel = ''; const targetMetric = metrics.find(m => startsWith(metric.field, m.id)); const targetLabel = calculateLabel(targetMetric, metrics); // For percentiles we need to parse the field id to extract the percentile @@ -59,11 +73,20 @@ export default function calculateLabel(metric, metrics) { const percentileValueMatch = /\[([0-9\.]+)\]$/; const matches = metric.field.match(percentileValueMatch); if (matches) { - additionalLabel += ` (${matches[1]})`; + return i18n.translate('tsvb.calculateLabel.lookupMetricTypeOfTargetWithAdditionalLabel', { + defaultMessage: '{lookupMetricType} of {targetLabel} ({additionalLabel})', + values: { lookupMetricType: lookup[metric.type], targetLabel, additionalLabel: matches[1] } + }); } } - return `${lookup[metric.type]} of ${targetLabel}${additionalLabel}`; + return i18n.translate('tsvb.calculateLabel.lookupMetricTypeOfTargetLabel', { + defaultMessage: '{lookupMetricType} of {targetLabel}', + values: { lookupMetricType: lookup[metric.type], targetLabel } + }); } - return `${lookup[metric.type]} of ${metric.field}`; + return i18n.translate('tsvb.calculateLabel.lookupMetricTypeOfMetricFieldRankLabel', { + defaultMessage: '{lookupMetricType} of {metricField}', + values: { lookupMetricType: lookup[metric.type], metricField: metric.field } + }); } diff --git a/src/core_plugins/metrics/public/components/add_delete_buttons.js b/src/core_plugins/metrics/public/components/add_delete_buttons.js index 222ffa1af0f5e..d6bc91a1320e8 100644 --- a/src/core_plugins/metrics/public/components/add_delete_buttons.js +++ b/src/core_plugins/metrics/public/components/add_delete_buttons.js @@ -20,6 +20,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import { EuiToolTip, EuiButtonIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; function AddDeleteButtons(props) { const { testSubj } = props; @@ -86,9 +87,9 @@ function AddDeleteButtons(props) { AddDeleteButtons.defaultProps = { testSubj: 'Add', - addTooltip: 'Add', - deleteTooltip: 'Delete', - cloneTooltip: 'Clone' + addTooltip: i18n.translate('tsvb.addDeleteButtons.addButtonDefaultTooltip', { defaultMessage: 'Add' }), + deleteTooltip: i18n.translate('tsvb.addDeleteButtons.deleteButtonDefaultTooltip', { defaultMessage: 'Delete' }), + cloneTooltip: i18n.translate('tsvb.addDeleteButtons.cloneButtonDefaultTooltip', { defaultMessage: 'Clone' }) }; AddDeleteButtons.propTypes = { diff --git a/src/core_plugins/metrics/public/components/add_delete_buttons.test.js b/src/core_plugins/metrics/public/components/add_delete_buttons.test.js index de24bbff55846..71492c5ee1f84 100644 --- a/src/core_plugins/metrics/public/components/add_delete_buttons.test.js +++ b/src/core_plugins/metrics/public/components/add_delete_buttons.test.js @@ -19,14 +19,14 @@ import React from 'react'; import { expect } from 'chai'; -import { shallow } from 'enzyme'; +import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import sinon from 'sinon'; import AddDeleteButtons from './add_delete_buttons'; describe('AddDeleteButtons', () => { it('calls onAdd={handleAdd}', () => { const handleAdd = sinon.spy(); - const wrapper = shallow( + const wrapper = shallowWithIntl( ); wrapper.find('EuiButtonIcon').at(0).simulate('click'); @@ -35,7 +35,7 @@ describe('AddDeleteButtons', () => { it('calls onDelete={handleDelete}', () => { const handleDelete = sinon.spy(); - const wrapper = shallow( + const wrapper = shallowWithIntl( ); wrapper.find('EuiButtonIcon').at(1).simulate('click'); @@ -44,7 +44,7 @@ describe('AddDeleteButtons', () => { it('calls onClone={handleClone}', () => { const handleClone = sinon.spy(); - const wrapper = shallow( + const wrapper = shallowWithIntl( ); wrapper.find('EuiButtonIcon').at(0).simulate('click'); @@ -52,21 +52,21 @@ describe('AddDeleteButtons', () => { }); it('disableDelete={true}', () => { - const wrapper = shallow( + const wrapper = shallowWithIntl( ); expect(wrapper.find({ text: 'Delete' })).to.have.length(0); }); it('disableAdd={true}', () => { - const wrapper = shallow( + const wrapper = shallowWithIntl( ); expect(wrapper.find({ text: 'Add' })).to.have.length(0); }); it('should not display clone by default', () => { - const wrapper = shallow( + const wrapper = shallowWithIntl( ); expect(wrapper.find({ text: 'Clone' })).to.have.length(0); @@ -74,7 +74,7 @@ describe('AddDeleteButtons', () => { it('should not display clone when disableAdd={true}', () => { const fn = sinon.spy(); - const wrapper = shallow( + const wrapper = shallowWithIntl( ); expect(wrapper.find({ text: 'Clone' })).to.have.length(0); diff --git a/src/core_plugins/metrics/public/components/aggs/agg_row.js b/src/core_plugins/metrics/public/components/aggs/agg_row.js index 756e7b7eb3fd0..e778dcb6b4469 100644 --- a/src/core_plugins/metrics/public/components/aggs/agg_row.js +++ b/src/core_plugins/metrics/public/components/aggs/agg_row.js @@ -22,11 +22,14 @@ import React from 'react'; import _ from 'lodash'; import AddDeleteButtons from '../add_delete_buttons'; import { EuiToolTip, EuiButtonIcon, EuiIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; -function AggRow(props) { +function AggRowUi(props) { let iconType = 'eyeClosed'; let iconColor = 'subdued'; const last = _.last(props.siblings); + const { intl } = props; + if (last.id === props.model.id) { iconType = 'eye'; iconColor = 'text'; @@ -36,8 +39,17 @@ function AggRow(props) { if (!props.disableDelete) { dragHandle = ( - - + )} + > + ); @@ -56,8 +68,8 @@ function AggRow(props) { { return value === option.value; }); @@ -98,21 +186,23 @@ function AggSelect(props) { } else { options = [ { - label: 'Metric Aggregations', + label: intl.formatMessage({ id: 'tsvb.aggSelect.aggGroups.metricAggLabel', defaultMessage: 'Metric Aggregations' }), options: metricAggs, }, { - label: 'Parent Pipeline Aggregations', + label: intl.formatMessage({ + id: 'tsvb.aggSelect.aggGroups.parentPipelineAggLabel', defaultMessage: 'Parent Pipeline Aggregations' }), options: pipelineAggs .filter(filterByPanelType(panelType)) - .map(agg => ({ ...agg, disabled: !enablePipelines })) + .map(agg => ({ ...agg, disabled: !enablePipelines })), }, { - label: 'Sibling Pipeline Aggregations', + label: intl.formatMessage({ + id: 'tsvb.aggSelect.aggGroups.siblingPipelineAggLabel', defaultMessage: 'Sibling Pipeline Aggregations' }), options: siblingAggs.map(agg => ({ ...agg, disabled: !enablePipelines })), }, { - label: 'Special Aggregations', + label: intl.formatMessage({ id: 'tsvb.aggSelect.aggGroups.specialAggLabel', defaultMessage: 'Special Aggregations' }), options: specialAggs.map(agg => ({ ...agg, disabled: !enablePipelines })), }, ]; @@ -127,7 +217,7 @@ function AggSelect(props) {
- Aggregation + + + - Variables + + + )} fullWidth helpText={
- Variables are keys on the params object, i.e. params.<name>. - To access the bucket interval (in milliseconds) use params._interval. + params), + paramsName: (params.<name>), + paramsInterval: (params._interval) + }} + />
} > diff --git a/src/core_plugins/metrics/public/components/aggs/cumulative_sum.js b/src/core_plugins/metrics/public/components/aggs/cumulative_sum.js index 5bb10f6982c56..09dad215d335e 100644 --- a/src/core_plugins/metrics/public/components/aggs/cumulative_sum.js +++ b/src/core_plugins/metrics/public/components/aggs/cumulative_sum.js @@ -24,6 +24,7 @@ import AggSelect from './agg_select'; import MetricSelect from './metric_select'; import createChangeHandler from '../lib/create_change_handler'; import createSelectHandler from '../lib/create_select_handler'; +import { FormattedMessage } from '@kbn/i18n/react'; import { htmlIdGenerator, EuiFlexGroup, EuiFlexItem, EuiFormLabel, EuiFormRow } from '@elastic/eui'; function CumulativeSumAgg(props) { @@ -41,7 +42,12 @@ function CumulativeSumAgg(props) { > - Aggregation + + + )} > { const { siblings } = props; @@ -56,7 +57,12 @@ export const DerivativeAgg = props => { > - Aggregation + + + { )} fullWidth > { )} fullWidth > { const { @@ -69,7 +70,12 @@ export const FilterRatioAgg = props => { - Aggregation + + + { - + )} + > { - + )} + > { - Metric Aggregation + + + { { model.metric_agg !== 'count' ? ( - + )} + > - Aggregation + + + - Variables + + + )} fullWidth - helpText={ -
- This field uses basic math expressions (see{' '} - - TinyMath - ) - Variables are keys on the params object, - i.e. params.<name> To access all the data use - params._all.<name>.values for an array of the - values and params._all.<name>.timestamps - for an array of the timestamps. params._timestamp - is available for the current bucket's timestamp, - params._index is available for the current - bucket's index, and params._intervals - available for the interval in milliseconds. -
- } + helpText={( + + ), + params: (params), + paramsName: (params.<name>), + paramsValues: (params._all.<name>.values), + paramsTimestamps: (params._all.<name>.timestamps), + paramsTimestamp: (params._timestamp), + paramsIndex: (params._index), + paramsInterval: (params._interval) + }} + />)} > { @@ -58,8 +59,8 @@ export function filterRows(includeSiblings) { }; } -function MetricSelect(props) { - const { additionalOptions, restrict, metric, metrics, onChange, value, exclude, includeSiblings, clearable, ...rest } = props; +function MetricSelectUi(props) { + const { additionalOptions, restrict, metric, metrics, onChange, value, exclude, includeSiblings, clearable, intl, ...rest } = props; const calculatedMetrics = metrics.filter(createTypeFilter(restrict, exclude)); @@ -97,7 +98,7 @@ function MetricSelect(props) { return ( { - const { siblings } = props; +const MovingAverageAggUi = props => { + const { siblings, intl } = props; const defaults = { settings: '', minimize: 0, @@ -52,11 +53,27 @@ export const MovingAverageAgg = props => { const handleTextChange = createTextHandler(handleChange); const handleNumberChange = createNumberHandler(handleChange); const modelOptions = [ - { label: 'Simple', value: 'simple' }, - { label: 'Linear', value: 'linear' }, - { label: 'Exponentially Weighted', value: 'ewma' }, - { label: 'Holt-Linear', value: 'holt' }, - { label: 'Holt-Winters', value: 'holt_winters' } + { + label: intl.formatMessage({ id: 'tsvb.movingAverage.modelOptions.simpleLabel', defaultMessage: 'Simple' }), + value: 'simple' + }, + { + label: intl.formatMessage({ id: 'tsvb.movingAverage.modelOptions.linearLabel', defaultMessage: 'Linear' }), + value: 'linear' + }, + { + label: intl.formatMessage({ + id: 'tsvb.movingAverage.modelOptions.exponentiallyWeightedLabel', defaultMessage: 'Exponentially Weighted' }), + value: 'ewma' + }, + { + label: intl.formatMessage({ id: 'tsvb.movingAverage.modelOptions.holtLinearLabel', defaultMessage: 'Holt-Linear' }), + value: 'holt' + }, + { + label: intl.formatMessage({ id: 'tsvb.movingAverage.modelOptions.holtWintersLabel', defaultMessage: 'Holt-Winters' }), + value: 'holt_winters' + } ]; const minimizeOptions = [ { label: 'True', value: 1 }, @@ -80,7 +97,12 @@ export const MovingAverageAgg = props => { > - Aggregation + + + { )} > { )} > { - + )} + > {/* EUITODO: The following input couldn't be converted to EUI because of type mis-match. Should it be text or number? @@ -137,9 +171,15 @@ export const MovingAverageAgg = props => { - + )} + > { - + )} + > {/* EUITODO: The following input couldn't be converted to EUI because of type mis-match. Should it be text or number? @@ -169,9 +215,18 @@ export const MovingAverageAgg = props => { )} helpText={ - Key=Value space-delimited + + Key=Value) }} + /> + } > { ); }; -MovingAverageAgg.propTypes = { +MovingAverageAggUi.propTypes = { disableDelete: PropTypes.bool, fields: PropTypes.object, model: PropTypes.object, @@ -196,3 +251,5 @@ MovingAverageAgg.propTypes = { series: PropTypes.object, siblings: PropTypes.array, }; + +export const MovingAverageAgg = injectI18n(MovingAverageAggUi); diff --git a/src/core_plugins/metrics/public/components/aggs/percentile.js b/src/core_plugins/metrics/public/components/aggs/percentile.js index 7a1e89db80830..4bf29f681df9e 100644 --- a/src/core_plugins/metrics/public/components/aggs/percentile.js +++ b/src/core_plugins/metrics/public/components/aggs/percentile.js @@ -38,12 +38,13 @@ import { EuiFieldNumber, EuiFormRow, } from '@elastic/eui'; +import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; const newPercentile = (opts) => { return _.assign({ id: uuid.v1(), mode: 'line', shade: 0.2 }, opts); }; -class Percentiles extends Component { +class PercentilesUi extends Component { constructor(props) { super(props); @@ -64,9 +65,16 @@ class Percentiles extends Component { const model = { ...defaults, ...row }; const handleAdd = collectionActions.handleAdd.bind(null, this.props, newPercentile); const handleDelete = collectionActions.handleDelete.bind(null, this.props, model); + const { intl } = this.props; const modeOptions = [ - { label: 'Line', value: 'line' }, - { label: 'Band', value: 'band' } + { + label: intl.formatMessage({ id: 'tsvb.percentile.modeOptions.lineLabel', defaultMessage: 'Line' }), + value: 'line' + }, + { + label: intl.formatMessage({ id: 'tsvb.percentile.modeOptions.bandLabel', defaultMessage: 'Band' }), + value: 'band' + } ]; const optionsStyle = {}; if (model.mode === 'line') { @@ -82,8 +90,8 @@ class Percentiles extends Component { - Mode: + + + - Fill to: + + + - Shade (0 to 1): + + + - Aggregation + + + - + )} + > { const { series, panel, fields } = props; @@ -56,7 +57,12 @@ export const PercentileRankAgg = props => { > - Aggregation + + + { /> - + )} + > { - + )} + > { const { siblings } = props; @@ -46,7 +47,12 @@ export const PositiveOnlyAgg = props => { > - Aggregation + + + { )} > { const { siblings } = props; @@ -48,7 +49,12 @@ export const SerialDiffAgg = props => { > - Aggregation + + + { )} > { - + )} + > {/* EUITODO: The following input couldn't be converted to EUI because of type mis-match. Should it be text or number? diff --git a/src/core_plugins/metrics/public/components/aggs/series_agg.js b/src/core_plugins/metrics/public/components/aggs/series_agg.js index 6fd85372bf921..3dd14aefcaa4e 100644 --- a/src/core_plugins/metrics/public/components/aggs/series_agg.js +++ b/src/core_plugins/metrics/public/components/aggs/series_agg.js @@ -32,9 +32,10 @@ import { EuiTitle, EuiFormRow, } from '@elastic/eui'; +import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; -function SeriesAgg(props) { - const { panel, model } = props; +function SeriesAggUi(props) { + const { panel, model, intl } = props; const handleChange = createChangeHandler(props.onChange, model); const handleSelectChange = createSelectHandler(handleChange); @@ -42,15 +43,42 @@ function SeriesAgg(props) { const htmlId = htmlIdGenerator(); const functionOptions = [ - { label: 'Sum', value: 'sum' }, - { label: 'Max', value: 'max' }, - { label: 'Min', value: 'min' }, - { label: 'Avg', value: 'mean' }, - { label: 'Overall Sum', value: 'overall_sum' }, - { label: 'Overall Max', value: 'overall_max' }, - { label: 'Overall Min', value: 'overall_min' }, - { label: 'Overall Avg', value: 'overall_avg' }, - { label: 'Cumulative Sum', value: 'cumulative_sum' }, + { + label: intl.formatMessage({ id: 'tsvb.seriesAgg.functionOptions.sumLabel', defaultMessage: 'Sum' }), + value: 'sum' + }, + { + label: intl.formatMessage({ id: 'tsvb.seriesAgg.functionOptions.maxLabel', defaultMessage: 'Max' }), + value: 'max' + }, + { + label: intl.formatMessage({ id: 'tsvb.seriesAgg.functionOptions.minLabel', defaultMessage: 'Min' }), + value: 'min' + }, + { + label: intl.formatMessage({ id: 'tsvb.seriesAgg.functionOptions.avgLabel', defaultMessage: 'Avg' }), + value: 'mean' + }, + { + label: intl.formatMessage({ id: 'tsvb.seriesAgg.functionOptions.overallSumLabel', defaultMessage: 'Overall Sum' }), + value: 'overall_sum' + }, + { + label: intl.formatMessage({ id: 'tsvb.seriesAgg.functionOptions.overallMaxLabel', defaultMessage: 'Overall Max' }), + value: 'overall_max' + }, + { + label: intl.formatMessage({ id: 'tsvb.seriesAgg.functionOptions.overallMinLabel', defaultMessage: 'Overall Min' }), + value: 'overall_min' + }, + { + label: intl.formatMessage({ id: 'tsvb.seriesAgg.functionOptions.overallAvgLabel', defaultMessage: 'Overall Avg' }), + value: 'overall_avg' + }, + { + label: intl.formatMessage({ id: 'tsvb.seriesAgg.functionOptions.cumulativeSumLabel', defaultMessage: 'Cumulative Sum' }), + value: 'cumulative_sum' + }, ]; const selectedFunctionOption = functionOptions.find(option => { return model.function === option.value; @@ -66,7 +94,12 @@ function SeriesAgg(props) { siblings={props.siblings} > - Series Agg is not compatible with the table visualization. + + + ); @@ -82,7 +115,12 @@ function SeriesAgg(props) { > - Aggregation + + + - + )} + > { const handleChange = createChangeHandler(props.onChange, props.model); @@ -57,7 +58,12 @@ export const Static = props => { > - Aggregation + + + { /> - + )} + > - Aggregation + + + - + )} + fullWidth + > { - const { series, panel, fields } = props; +const StandardDeviationAggUi = props => { + const { series, panel, fields, intl } = props; const defaults = { sigma: '' }; const model = { ...defaults, ...props.model }; const modeOptions = [ - { label: 'Raw', value: 'raw' }, - { label: 'Upper Bound', value: 'upper' }, - { label: 'Lower Bound', value: 'lower' }, + { + label: intl.formatMessage({ id: 'tsvb.stdDeviation.modeOptions.rawLabel', defaultMessage: 'Raw' }), + value: 'raw' + }, + { + label: intl.formatMessage({ id: 'tsvb.stdDeviation.modeOptions.upperBoundLabel', defaultMessage: 'Upper Bound' }), + value: 'upper' + }, + { + дabel: intl.formatMessage({ id: 'tsvb.stdDeviation.modeOptions.lowerBoundLabel', defaultMessage: 'Lower Bound' }), + value: 'lower' + }, ]; if (panel.type !== 'table') { - modeOptions.push({ label: 'Bounds Band', value: 'band' }); + modeOptions.push({ + label: intl.formatMessage({ id: 'tsvb.stdDeviation.modeOptions.boundsBandLabel', defaultMessage: 'Bounds Band' }), + value: 'band' + }); } const handleChange = createChangeHandler(props.onChange, model); @@ -70,7 +83,12 @@ export const StandardDeviationAgg = props => { > - Aggregation + + + { /> - + )} + > { - + )} + > { - + )} + > { ); }; -StandardDeviationAgg.propTypes = { +StandardDeviationAggUi.propTypes = { disableDelete: PropTypes.bool, fields: PropTypes.object, model: PropTypes.object, @@ -125,3 +161,5 @@ StandardDeviationAgg.propTypes = { series: PropTypes.object, siblings: PropTypes.array, }; + +export const StandardDeviationAgg = injectI18n(StandardDeviationAggUi); diff --git a/src/core_plugins/metrics/public/components/aggs/std_sibling.js b/src/core_plugins/metrics/public/components/aggs/std_sibling.js index 30232933ffc71..26fd7d8e4a1fe 100644 --- a/src/core_plugins/metrics/public/components/aggs/std_sibling.js +++ b/src/core_plugins/metrics/public/components/aggs/std_sibling.js @@ -34,9 +34,10 @@ import { EuiFormLabel, EuiFormRow, } from '@elastic/eui'; +import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; -export const StandardSiblingAgg = props => { - const { siblings } = props; +const StandardSiblingAggUi = props => { + const { siblings, intl } = props; const defaults = { sigma: '' }; const model = { ...defaults, ...props.model }; const htmlId = htmlIdGenerator(); @@ -49,7 +50,13 @@ export const StandardSiblingAgg = props => { if (model.type === 'std_deviation_bucket') { stdDev.sigma = ( - + )} + > { ); const modeOptions = [ - { label: 'Raw', value: 'raw' }, - { label: 'Upper Bound', value: 'upper' }, - { label: 'Lower Bound', value: 'lower' }, - { label: 'Bounds Band', value: 'band' } + { + label: intl.formatMessage({ id: 'tsvb.stdSibling.modeOptions.rawLabel', defaultMessage: 'Raw' }), + value: 'raw' + }, + { + label: intl.formatMessage({ id: 'tsvb.stdSibling.modeOptions.upperBoundLabel', defaultMessage: 'Upper Bound' }), + value: 'upper' + }, + { + label: intl.formatMessage({ id: 'tsvb.stdSibling.modeOptions.lowerBoundLabel', defaultMessage: 'Lower Bound' }), + value: 'lower' + }, + { + label: intl.formatMessage({ id: 'tsvb.stdSibling.modeOptions.boundsBandLabel', defaultMessage: 'Bounds Band' }), + value: 'band' + } ]; const selectedModeOption = modeOptions.find(option => { return model.mode === option.value; @@ -70,7 +89,13 @@ export const StandardSiblingAgg = props => { stdDev.mode = ( - + )} + > { > - Aggregation + + + { )} > { ); }; -StandardSiblingAgg.propTypes = { +StandardSiblingAggUi.propTypes = { disableDelete: PropTypes.bool, fields: PropTypes.object, model: PropTypes.object, @@ -133,3 +166,5 @@ StandardSiblingAgg.propTypes = { series: PropTypes.object, siblings: PropTypes.array, }; + +export const StandardSiblingAgg = injectI18n(StandardSiblingAggUi); diff --git a/src/core_plugins/metrics/public/components/aggs/top_hit.js b/src/core_plugins/metrics/public/components/aggs/top_hit.js index 06abcfad6039f..46e0b9d14b0ae 100644 --- a/src/core_plugins/metrics/public/components/aggs/top_hit.js +++ b/src/core_plugins/metrics/public/components/aggs/top_hit.js @@ -33,9 +33,10 @@ import { EuiSpacer, EuiFormRow, } from '@elastic/eui'; +import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; -export const TopHitAgg = props => { - const { fields, series, panel } = props; +const TopHitAggUi = props => { + const { fields, series, panel, intl } = props; const defaults = { agg_with: 'avg', size: 1, @@ -51,15 +52,33 @@ export const TopHitAgg = props => { const handleTextChange = createTextHandler(handleChange); const aggWithOptions = [ - { label: 'Avg', value: 'avg' }, - { label: 'Max', value: 'max' }, - { label: 'Min', value: 'min' }, - { label: 'Sum', value: 'sum' }, + { + label: intl.formatMessage({ id: 'tsvb.topHit.aggWithOptions.averageLabel', defaultMessage: 'Avg' }), + value: 'avg', + }, + { + label: intl.formatMessage({ id: 'tsvb.topHit.aggWithOptions.maxLabel', defaultMessage: 'Max' }), + value: 'max' + }, + { + label: intl.formatMessage({ id: 'tsvb.topHit.aggWithOptions.minLabel', defaultMessage: 'Min' }), + value: 'min' + }, + { + label: intl.formatMessage({ id: 'tsvb.topHit.aggWithOptions.sumLabel', defaultMessage: 'Sum' }), + value: 'sum' + }, ]; const orderOptions = [ - { label: 'Asc', value: 'asc' }, - { label: 'Desc', value: 'desc' }, + { + label: intl.formatMessage({ id: 'tsvb.topHit.orderOptions.ascLabel', defaultMessage: 'Asc' }), + value: 'asc' + }, + { + label: intl.formatMessage({ id: 'tsvb.topHit.orderOptions.descLabel', defaultMessage: 'Desc' }), + value: 'desc' + }, ]; const htmlId = htmlIdGenerator(); @@ -79,7 +98,12 @@ export const TopHitAgg = props => { > - Aggregation + + + { /> - + )} + > { - + )} + > {/* EUITODO: The following input couldn't be converted to EUI because of type mis-match. Should it be text or number? @@ -119,10 +155,16 @@ export const TopHitAgg = props => { - + )} + > { - + )} + > { - + )} + > { ); }; + +export const TopHitAgg = injectI18n(TopHitAggUi); diff --git a/src/core_plugins/metrics/public/components/aggs/unsupported_agg.js b/src/core_plugins/metrics/public/components/aggs/unsupported_agg.js index 3c24bc1b5266f..6637538c5ea67 100644 --- a/src/core_plugins/metrics/public/components/aggs/unsupported_agg.js +++ b/src/core_plugins/metrics/public/components/aggs/unsupported_agg.js @@ -20,6 +20,7 @@ import AggRow from './agg_row'; import React from 'react'; import { EuiCode, EuiTitle } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; export function UnsupportedAgg(props) { return ( @@ -31,7 +32,13 @@ export function UnsupportedAgg(props) { siblings={props.siblings} > - The {props.model.type} aggregation is no longer supported. + + {props.model.type}) }} + /> + ); diff --git a/src/core_plugins/metrics/public/components/aggs/vars.js b/src/core_plugins/metrics/public/components/aggs/vars.js index 1ace6b717aee8..982f7e33ae1dd 100644 --- a/src/core_plugins/metrics/public/components/aggs/vars.js +++ b/src/core_plugins/metrics/public/components/aggs/vars.js @@ -24,8 +24,9 @@ import AddDeleteButtons from '../add_delete_buttons'; import * as collectionActions from '../lib/collection_actions'; import MetricSelect from './metric_select'; import { EuiFlexGroup, EuiFlexItem, EuiFieldText } from '@elastic/eui'; +import { injectI18n } from '@kbn/i18n/react'; -class CalculationVars extends Component { +class CalculationVarsUi extends Component { constructor(props) { super(props); @@ -44,14 +45,15 @@ class CalculationVars extends Component { renderRow(row, i, items) { const handleAdd = collectionActions.handleAdd.bind(null, this.props); const handleDelete = collectionActions.handleDelete.bind(null, this.props, row); + const { intl } = this.props; return ( @@ -91,12 +93,12 @@ class CalculationVars extends Component { } -CalculationVars.defaultProps = { +CalculationVarsUi.defaultProps = { name: 'variables', includeSiblings: false }; -CalculationVars.propTypes = { +CalculationVarsUi.propTypes = { metrics: PropTypes.array, model: PropTypes.object, name: PropTypes.string, @@ -104,4 +106,5 @@ CalculationVars.propTypes = { includeSiblings: PropTypes.bool }; +const CalculationVars = injectI18n(CalculationVarsUi); export default CalculationVars; diff --git a/src/core_plugins/metrics/public/components/annotations_editor.js b/src/core_plugins/metrics/public/components/annotations_editor.js index 88c8f2062121b..b0d095a731035 100644 --- a/src/core_plugins/metrics/public/components/annotations_editor.js +++ b/src/core_plugins/metrics/public/components/annotations_editor.js @@ -41,6 +41,7 @@ import { EuiCode, EuiText, } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; function newAnnotation() { return { @@ -99,7 +100,10 @@ class AnnotationsEditor extends Component { )} fullWidth > - + )} + fullWidth + > )} fullWidth > - Ignore global filters? + + + - Ignore panel filters? + + + - + )} + > )} fullWidth > )} helpText={ - eg. {'{{field}}'} + + {'{{field}}'}) }} + /> + } fullWidth > @@ -222,8 +264,18 @@ class AnnotationsEditor extends Component { .bind(null, this.props, newAnnotation); content = ( -

Click the button below to create an annotation data source.

- Add data source +

+ +

+ + +
); } else { @@ -231,7 +283,12 @@ class AnnotationsEditor extends Component { content = (
- Data sources + + + diff --git a/src/core_plugins/metrics/public/components/color_picker.js b/src/core_plugins/metrics/public/components/color_picker.js index 3458556300079..2bedccb32cdee 100644 --- a/src/core_plugins/metrics/public/components/color_picker.js +++ b/src/core_plugins/metrics/public/components/color_picker.js @@ -24,6 +24,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { EuiIconTip, } from '@elastic/eui'; import Picker from './custom_color_picker'; +import { injectI18n } from '@kbn/i18n/react'; class ColorPicker extends Component { @@ -65,7 +66,10 @@ class ColorPicker extends Component { if (!this.props.value) { return (
); } @@ -124,4 +140,4 @@ ColorPicker.propTypes = { onChange: PropTypes.func }; -export default ColorPicker; +export default injectI18n(ColorPicker); diff --git a/src/core_plugins/metrics/public/components/color_rules.js b/src/core_plugins/metrics/public/components/color_rules.js index 9533ef642a8ae..baaae852b1111 100644 --- a/src/core_plugins/metrics/public/components/color_rules.js +++ b/src/core_plugins/metrics/public/components/color_rules.js @@ -31,6 +31,8 @@ import { EuiFlexGroup, EuiFlexItem, } from '@elastic/eui'; +import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; class ColorRules extends Component { @@ -55,11 +57,15 @@ class ColorRules extends Component { const model = { ...defaults, ...row }; const handleAdd = collectionActions.handleAdd.bind(null, this.props); const handleDelete = collectionActions.handleDelete.bind(null, this.props, model); + const { intl } = this.props; const operatorOptions = [ - { label: '> greater than', value: 'gt' }, - { label: '>= greater than or equal', value: 'gte' }, - { label: '< less than', value: 'lt' }, - { label: '<= less than or equal', value: 'lte' }, + { label: intl.formatMessage({ id: 'tsvb.colorRules.greaterThanLabel', defaultMessage: '> greater than' }), value: 'gt' }, + { + label: intl.formatMessage({ id: 'tsvb.colorRules.greaterThanOrEqualLabel', defaultMessage: '>= greater than or equal' }), + value: 'gte' + }, + { label: intl.formatMessage({ id: 'tsvb.colorRules.lessThanLabel', defaultMessage: '< less than' }), value: 'lt' }, + { label: intl.formatMessage({ id: 'tsvb.colorRules.lessThanOrEqualLabel', defaultMessage: '<= less than or equal' }), value: 'lte' }, ]; const handleColorChange = (part) => { const handleChange = collectionActions.handleChange.bind(null, this.props); @@ -77,7 +83,15 @@ class ColorRules extends Component { secondary = ( - and {this.props.secondaryName} to + + + - Set {this.props.primaryName} to + + + - if metric is + + + { return defaultValue === option.value; @@ -134,7 +137,13 @@ class DataFormatPicker extends Component {
- + )} + > - + )} + > - + )} + > this.decimals = el} @@ -171,9 +192,18 @@ class DataFormatPicker extends Component { custom = ( )} helpText={ - See Numeral.js + + Numeral.js) }} + /> + } > -
{title || 'The request for this panel failed'}
+
+ {title || + } +
{additionalInfo}
); diff --git a/src/core_plugins/metrics/public/components/icon_select.js b/src/core_plugins/metrics/public/components/icon_select.js index 576f62d2ad5dc..28a9d6bec2070 100644 --- a/src/core_plugins/metrics/public/components/icon_select.js +++ b/src/core_plugins/metrics/public/components/icon_select.js @@ -22,6 +22,7 @@ import React from 'react'; import { EuiComboBox, } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; function renderOption(option) { const icon = option.value; @@ -53,21 +54,27 @@ function IconSelect(props) { IconSelect.defaultProps = { icons: [ - { value: 'fa-asterisk', label: 'Asterisk' }, - { value: 'fa-bell', label: 'Bell' }, - { value: 'fa-bolt', label: 'Bolt' }, - { value: 'fa-bomb', label: 'Bomb' }, - { value: 'fa-bug', label: 'Bug' }, - { value: 'fa-comment', label: 'Comment' }, - { value: 'fa-exclamation-circle', label: 'Exclamation Circle' }, - { value: 'fa-exclamation-triangle', label: 'Exclamation Triangle' }, - { value: 'fa-fire', label: 'Fire' }, - { value: 'fa-flag', label: 'Flag' }, - { value: 'fa-heart', label: 'Heart' }, - { value: 'fa-map-marker', label: 'Map Marker' }, - { value: 'fa-map-pin', label: 'Map Pin' }, - { value: 'fa-star', label: 'Star' }, - { value: 'fa-tag', label: 'Tag' }, + { value: 'fa-asterisk', label: i18n.translate('tsvb.iconSelect.asteriskLabel', { defaultMessage: 'Asterisk' }) }, + { value: 'fa-bell', label: i18n.translate('tsvb.iconSelect.bellLabel', { defaultMessage: 'Bell' }) }, + { value: 'fa-bolt', label: i18n.translate('tsvb.iconSelect.boltLabel', { defaultMessage: 'Bolt' }) }, + { value: 'fa-bomb', label: i18n.translate('tsvb.iconSelect.bombLabel', { defaultMessage: 'Bomb' }) }, + { value: 'fa-bug', label: i18n.translate('tsvb.iconSelect.bugLabel', { defaultMessage: 'Bug' }) }, + { value: 'fa-comment', label: i18n.translate('tsvb.iconSelect.commentLabel', { defaultMessage: 'Comment' }) }, + { + value: 'fa-exclamation-circle', + label: i18n.translate('tsvb.iconSelect.exclamationCircleLabel', { defaultMessage: 'Exclamation Circle' }) + }, + { + value: 'fa-exclamation-triangle', + label: i18n.translate('tsvb.iconSelect.exclamationTriangleLabel', { defaultMessage: 'Exclamation Triangle' }) + }, + { value: 'fa-fire', label: i18n.translate('tsvb.iconSelect.fireLabel', { defaultMessage: 'Fire' }) }, + { value: 'fa-flag', label: i18n.translate('tsvb.iconSelect.flagLabel', { defaultMessage: 'Flag' }) }, + { value: 'fa-heart', label: i18n.translate('tsvb.iconSelect.heartLabel', { defaultMessage: 'Heart' }) }, + { value: 'fa-map-marker', label: i18n.translate('tsvb.iconSelect.mapMarkerLabel', { defaultMessage: 'Map Marker' }) }, + { value: 'fa-map-pin', label: i18n.translate('tsvb.iconSelect.mapPinLabel', { defaultMessage: 'Map Pin' }) }, + { value: 'fa-star', label: i18n.translate('tsvb.iconSelect.starLabel', { defaultMessage: 'Star' }) }, + { value: 'fa-tag', label: i18n.translate('tsvb.iconSelect.tagLabel', { defaultMessage: 'Tag' }) }, ] }; diff --git a/src/core_plugins/metrics/public/components/index_pattern.js b/src/core_plugins/metrics/public/components/index_pattern.js index 15712513f416b..dc456026acb5a 100644 --- a/src/core_plugins/metrics/public/components/index_pattern.js +++ b/src/core_plugins/metrics/public/components/index_pattern.js @@ -32,6 +32,7 @@ import { EuiFormLabel, EuiSpacer, } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; export const IndexPattern = props => { const { fields, prefix } = props; @@ -57,7 +58,10 @@ export const IndexPattern = props => { )} fullWidth > { )} fullWidth > { )} + helpText={()} > { - Drop last bucket? + + + 1) { const unitValue = Math.round(Math.abs(as)); const unitString = unitLookup[units[i]]; - return `per ${unitValue} ${unitString}`; + return i18n.translate('tsvb.axisLabelOptions.axisLabel', + { defaultMessage: 'per {unitValue} {unitString}', values: { unitValue, unitString } }); } } } diff --git a/src/core_plugins/metrics/public/components/lib/replace_vars.js b/src/core_plugins/metrics/public/components/lib/replace_vars.js index 16158e25d04f6..97d439636dd29 100644 --- a/src/core_plugins/metrics/public/components/lib/replace_vars.js +++ b/src/core_plugins/metrics/public/components/lib/replace_vars.js @@ -19,6 +19,8 @@ import _ from 'lodash'; import handlebars from 'handlebars/dist/handlebars'; +import { i18n } from '@kbn/i18n'; + export default function replaceVars(str, args = {}, vars = {}) { try { const template = handlebars.compile(str, { strict: true }); @@ -36,15 +38,17 @@ export default function replaceVars(str, args = {}, vars = {}) { const badVar = e.message.split(/"/)[1]; e.error = { caused_by: { - reason: `{{${badVar}}} is an unknown variable`, - title: 'Error processing your markdown' + reason: i18n.translate('tsvb.replaceVars.errors.unknownVarDescription', + { defaultMessage: '{badVar} is an unknown variable', values: { badVar: '{{' + badVar + '}}' } }), + title: i18n.translate('tsvb.replaceVars.errors.unknownVarTitle', { defaultMessage: 'Error processing your markdown' }) } }; } else { e.error = { caused_by: { - reason: 'Please verify you are only using markdown, known variables, and built-in Handlebars expressions', - title: 'Error processing your markdown' + reason: i18n.translate('tsvb.replaceVars.errors.markdownErrorDescription', { + defaultMessage: 'Please verify you are only using markdown, known variables, and built-in Handlebars expressions' }), + title: i18n.translate('tsvb.replaceVars.errors.markdownErrorTitle', { defaultMessage: 'Error processing your markdown' }) } }; } diff --git a/src/core_plugins/metrics/public/components/markdown_editor.js b/src/core_plugins/metrics/public/components/markdown_editor.js index d5061810b59ac..f00d7c1b80db1 100644 --- a/src/core_plugins/metrics/public/components/markdown_editor.js +++ b/src/core_plugins/metrics/public/components/markdown_editor.js @@ -38,6 +38,8 @@ import { EuiTitle, } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + class MarkdownEditor extends Component { state = { visData: null, @@ -150,18 +152,38 @@ class MarkdownEditor extends Component {

- The following variables can be used in the Markdown by using the Handlebar (mustache) syntax.{' '} - - Click here for documentation - {' '} - on the available expressions. + + + + ) + }} + />

- - + + {rows} @@ -169,7 +191,12 @@ class MarkdownEditor extends Component { {rows.length === 0 && ( - No variables available for the selected data metrics. + + + )} @@ -177,8 +204,12 @@ class MarkdownEditor extends Component {

- There is also a special variable named _all which you can use to access the entire tree. This is useful for - creating lists with data from a group by... + _all) }} + />

diff --git a/src/core_plugins/metrics/public/components/no_data.js b/src/core_plugins/metrics/public/components/no_data.js index 579d87076fe05..4a7d62294bc6c 100644 --- a/src/core_plugins/metrics/public/components/no_data.js +++ b/src/core_plugins/metrics/public/components/no_data.js @@ -18,11 +18,17 @@ */ import React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; function NoDataComponent() { return (
-
No data to display for the selected metrics
+
+ +
); } diff --git a/src/core_plugins/metrics/public/components/panel_config.js b/src/core_plugins/metrics/public/components/panel_config.js index 91c9449276083..c45c83d5644c2 100644 --- a/src/core_plugins/metrics/public/components/panel_config.js +++ b/src/core_plugins/metrics/public/components/panel_config.js @@ -25,6 +25,7 @@ import topN from './panel_config/top_n'; import table from './panel_config/table'; import gauge from './panel_config/gauge'; import markdown from './panel_config/markdown'; +import { FormattedMessage } from '@kbn/i18n/react'; const types = { timeseries, @@ -41,7 +42,14 @@ function PanelConfig(props) { if (component) { return React.createElement(component, props); } - return (
Missing panel config for “{model.type}”
); + return ( +
+ +
); } PanelConfig.propTypes = { diff --git a/src/core_plugins/metrics/public/components/panel_config/gauge.js b/src/core_plugins/metrics/public/components/panel_config/gauge.js index f275f7d1fc9dc..e1fe00d12345a 100644 --- a/src/core_plugins/metrics/public/components/panel_config/gauge.js +++ b/src/core_plugins/metrics/public/components/panel_config/gauge.js @@ -43,8 +43,9 @@ import { EuiTitle, EuiHorizontalRule, } from '@elastic/eui'; +import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; -class GaugePanelConfig extends Component { +class GaugePanelConfigUi extends Component { constructor(props) { super(props); @@ -70,6 +71,7 @@ class GaugePanelConfig extends Component { render() { const { selectedTab } = this.state; + const { intl } = this.props; const defaults = { gauge_max: '', filter: '', @@ -81,8 +83,16 @@ class GaugePanelConfig extends Component { const handleSelectChange = createSelectHandler(this.props.onChange); const handleTextChange = createTextHandler(this.props.onChange); const styleOptions = [ - { label: 'Circle', value: 'circle' }, - { label: 'Half Circle', value: 'half' } + { + label: intl.formatMessage({ + id: 'tsvb.gauge.styleOptions.circleLabel', defaultMessage: 'Circle' }), + value: 'circle' + }, + { + label: intl.formatMessage({ + id: 'tsvb.gauge.styleOptions.halfCircleLabel', defaultMessage: 'Half Circle' }), + value: 'half' + } ]; const htmlId = htmlIdGenerator(); const selectedGaugeStyleOption = styleOptions.find(option => { @@ -104,7 +114,14 @@ class GaugePanelConfig extends Component { view = (
- Data + + + + + )} fullWidth > - Ignore global filter? + + + - Style + + + + + )} > {/* EUITODO: The following input couldn't be converted to EUI because of type mis-match. @@ -169,7 +204,10 @@ class GaugePanelConfig extends Component { )} > )} > )} > - Background color: + + + - Inner color: + + + - Color rules + + + + + this.switchTab('data')} > - Data + this.switchTab('options')} > - Panel options + {view} @@ -269,10 +336,11 @@ class GaugePanelConfig extends Component { } -GaugePanelConfig.propTypes = { +GaugePanelConfigUi.propTypes = { fields: PropTypes.object, model: PropTypes.object, onChange: PropTypes.func, }; +const GaugePanelConfig = injectI18n(GaugePanelConfigUi); export default GaugePanelConfig; diff --git a/src/core_plugins/metrics/public/components/panel_config/markdown.js b/src/core_plugins/metrics/public/components/panel_config/markdown.js index e48e62fab76ea..1854a23e728db 100644 --- a/src/core_plugins/metrics/public/components/panel_config/markdown.js +++ b/src/core_plugins/metrics/public/components/panel_config/markdown.js @@ -45,8 +45,9 @@ import { EuiHorizontalRule, } from '@elastic/eui'; const lessC = less(window, { env: 'production' }); +import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; -class MarkdownPanelConfig extends Component { +class MarkdownPanelConfigUi extends Component { constructor(props) { super(props); @@ -78,13 +79,23 @@ class MarkdownPanelConfig extends Component { const { selectedTab } = this.state; const handleSelectChange = createSelectHandler(this.props.onChange); const handleTextChange = createTextHandler(this.props.onChange); + const { intl } = this.props; const htmlId = htmlIdGenerator(); const alignOptions = [ - { label: 'Top', value: 'top' }, - { label: 'Middle', value: 'middle' }, - { label: 'Bottom', value: 'bottom' } + { + label: intl.formatMessage({ id: 'tsvb.markdown.alignOptions.topLabel', defaultMessage: 'Top' }), + value: 'top' + }, + { + label: intl.formatMessage({ id: 'tsvb.markdown.alignOptions.middleLabel', defaultMessage: 'Middle' }), + value: 'middle' + }, + { + label: intl.formatMessage({ id: 'tsvb.markdown.alignOptions.bottomLabel', defaultMessage: 'Bottom' }), + value: 'bottom' + } ]; const selectedAlignOption = alignOptions.find(option => { return model.markdown_vertical_align === option.value; @@ -106,7 +117,14 @@ class MarkdownPanelConfig extends Component { view = (
- Data + + + + + )} fullWidth > - Ignore global filter? + + + - Style + + + + + - Background color: + + + - Show scrollbars? + + + - Vertical alignment: + + + - Custom CSS (supports Less) + + + + + this.switchTab('markdown')} > - Markdown + this.switchTab('data')} > - Data + this.switchTab('options')} > - Panel options + {view} @@ -231,11 +295,12 @@ class MarkdownPanelConfig extends Component { } } -MarkdownPanelConfig.propTypes = { +MarkdownPanelConfigUi.propTypes = { fields: PropTypes.object, model: PropTypes.object, onChange: PropTypes.func, dateFormat: PropTypes.string }; +const MarkdownPanelConfig = injectI18n(MarkdownPanelConfigUi); export default MarkdownPanelConfig; diff --git a/src/core_plugins/metrics/public/components/panel_config/metric.js b/src/core_plugins/metrics/public/components/panel_config/metric.js index eea7608670bff..06cf98a0ad191 100644 --- a/src/core_plugins/metrics/public/components/panel_config/metric.js +++ b/src/core_plugins/metrics/public/components/panel_config/metric.js @@ -39,6 +39,7 @@ import { EuiTitle, EuiHorizontalRule, } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; class MetricPanelConfig extends Component { @@ -82,7 +83,14 @@ class MetricPanelConfig extends Component { view = (
- Data + + + + + )} fullWidth > - Ignore global filter? + + + - Color rules + + + + + this.switchTab('data')} > - Data + this.switchTab('options')} data-test-subj="metricEditorPanelOptionsBtn" > - Panel options + {view} diff --git a/src/core_plugins/metrics/public/components/panel_config/table.js b/src/core_plugins/metrics/public/components/panel_config/table.js index fbed539877417..1c07d8ef65a1b 100644 --- a/src/core_plugins/metrics/public/components/panel_config/table.js +++ b/src/core_plugins/metrics/public/components/panel_config/table.js @@ -42,6 +42,7 @@ import { EuiCode, EuiText, } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; class TablePanelConfig extends Component { @@ -78,15 +79,23 @@ class TablePanelConfig extends Component {

- For the table visualization you need to define a field to - group by using a terms aggregation. +

- + )} + > )} fullWidth > )} > {/* EUITODO: The following input couldn't be converted to EUI because of type mis-match. @@ -143,15 +158,28 @@ class TablePanelConfig extends Component { view = (
- Data + + + + + )} helpText={ - This supports mustache templating. - {'{{key}}'} is set to the term. + {'{{key}}'}) }} + /> } > @@ -175,7 +203,10 @@ class TablePanelConfig extends Component { )} fullWidth > - Ignore global filter? + + + this.switchTab('data')} > - Columns + this.switchTab('options')} > - Panel options + {view} diff --git a/src/core_plugins/metrics/public/components/panel_config/timeseries.js b/src/core_plugins/metrics/public/components/panel_config/timeseries.js index 25281b08ef943..6cc0db1fe761f 100644 --- a/src/core_plugins/metrics/public/components/panel_config/timeseries.js +++ b/src/core_plugins/metrics/public/components/panel_config/timeseries.js @@ -41,8 +41,9 @@ import { EuiTitle, EuiHorizontalRule, } from '@elastic/eui'; +import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; -class TimeseriesPanelConfig extends Component { +class TimeseriesPanelConfigUi extends Component { constructor(props) { super(props); @@ -66,24 +67,46 @@ class TimeseriesPanelConfig extends Component { const handleSelectChange = createSelectHandler(this.props.onChange); const handleTextChange = createTextHandler(this.props.onChange); const htmlId = htmlIdGenerator(); + const { intl } = this.props; + const positionOptions = [ - { label: 'Right', value: 'right' }, - { label: 'Left', value: 'left' } + { + label: intl.formatMessage({ id: 'tsvb.timeseries.positionOptions.rightLabel', defaultMessage: 'Right' }), + value: 'right' + }, + { + label: intl.formatMessage({ id: 'tsvb.timeseries.positionOptions.leftLabel', defaultMessage: 'Left' }), + value: 'left' + } ]; const selectedPositionOption = positionOptions.find(option => { return model.axis_position === option.value; }); const scaleOptions = [ - { label: 'Normal', value: 'normal' }, - { label: 'Log', value: 'log' } + { + label: intl.formatMessage({ id: 'tsvb.timeseries.scaleOptions.normalLabel', defaultMessage: 'Normal' }), + value: 'normal' + }, + { + label: intl.formatMessage({ id: 'tsvb.timeseries.scaleOptions.logLabel', defaultMessage: 'Log' }), + value: 'log' } ]; const selectedAxisScaleOption = scaleOptions.find(option => { return model.axis_scale === option.value; }); const legendPositionOptions = [ - { label: 'Right', value: 'right' }, - { label: 'Left', value: 'left' }, - { label: 'Bottom', value: 'bottom' } + { + label: intl.formatMessage({ id: 'tsvb.timeseries.legendPositionOptions.rightLabel', defaultMessage: 'Right' }), + value: 'right' + }, + { + label: intl.formatMessage({ id: 'tsvb.timeseries.legendPositionOptions.leftLabel', defaultMessage: 'Left' }), + value: 'left' + }, + { + label: intl.formatMessage({ id: 'tsvb.timeseries.legendPositionOptions.bottomLabel', defaultMessage: 'Bottom' }), + value: 'bottom' + } ]; const selectedLegendPosOption = legendPositionOptions.find(option => { return model.legend_position === option.value; @@ -112,7 +135,14 @@ class TimeseriesPanelConfig extends Component { view = (
- Data + + + + + )} fullWidth > - Ignore global filter? + + + - Style + + + + + - + )} + > - + )} + > - + )} + > - + )} + > - Background color: + + + - Show legend? + + + - Legend position + + + - Display grid + + + this.switchTab('data')} > - Data + this.switchTab('options')} > - Panel options + this.switchTab('annotations')} > - Annotations + {view} @@ -277,10 +375,11 @@ class TimeseriesPanelConfig extends Component { } -TimeseriesPanelConfig.propTypes = { +TimeseriesPanelConfigUi.propTypes = { fields: PropTypes.object, model: PropTypes.object, onChange: PropTypes.func, }; +const TimeseriesPanelConfig = injectI18n(TimeseriesPanelConfigUi); export default TimeseriesPanelConfig; diff --git a/src/core_plugins/metrics/public/components/panel_config/top_n.js b/src/core_plugins/metrics/public/components/panel_config/top_n.js index 4721f4f42d251..2f7bbb0070538 100644 --- a/src/core_plugins/metrics/public/components/panel_config/top_n.js +++ b/src/core_plugins/metrics/public/components/panel_config/top_n.js @@ -41,6 +41,7 @@ import { EuiHorizontalRule, EuiCode, } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; class TopNPanelConfig extends Component { @@ -83,15 +84,28 @@ class TopNPanelConfig extends Component { view = (
- Data + + + + + )} helpText={ - This supports mustache templating. - {'{{key}}'} is set to the term. + {'{{key}}'}) }} + /> } > @@ -115,7 +129,10 @@ class TopNPanelConfig extends Component { )} fullWidth > - Ignore global filter? + + + - Style + + + + + - Background color: + + + - Color rules + + + + + this.switchTab('data')} > - Data + this.switchTab('options')} > - Panel options + {view} diff --git a/src/core_plugins/metrics/public/components/series.js b/src/core_plugins/metrics/public/components/series.js index 9cec7b7eace1d..fba1a69e1cc72 100644 --- a/src/core_plugins/metrics/public/components/series.js +++ b/src/core_plugins/metrics/public/components/series.js @@ -28,6 +28,7 @@ import table from './vis_types/table/series'; import gauge from './vis_types/gauge/series'; import markdown from './vis_types/markdown/series'; import { sortable } from 'react-anything-sortable'; +import { FormattedMessage } from '@kbn/i18n/react'; const lookup = { top_n: topN, @@ -98,7 +99,15 @@ class Series extends Component { }; return (); } - return (
Missing Series component for panel type: {panel.type}
); + return ( +
+ +
+ ); } } diff --git a/src/core_plugins/metrics/public/components/series_config.js b/src/core_plugins/metrics/public/components/series_config.js index ba46b589f1a04..989a59a21a4a1 100644 --- a/src/core_plugins/metrics/public/components/series_config.js +++ b/src/core_plugins/metrics/public/components/series_config.js @@ -35,6 +35,7 @@ import { EuiFormLabel, EuiSpacer, } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; export const SeriesConfig = props => { const defaults = { offset_time: '', value_template: '' }; @@ -55,7 +56,10 @@ export const SeriesConfig = props => { )} fullWidth > { eg.{'{{value}}/s'}} + label={()} + helpText={( + + {'{{value}}/s'}) }} + /> + + )} fullWidth > { )} > { - Override Index Pattern? + + + - + )} + > { const { onChange } = props; @@ -34,7 +35,13 @@ export const SplitByFilter = props => { return ( - + )} + > { - + )} + > @@ -65,8 +68,8 @@ class FilterItems extends Component { - + )} + > { @@ -46,9 +61,10 @@ function GroupBySelect(props) { } -GroupBySelect.propTypes = { +GroupBySelectUi.propTypes = { onChange: PropTypes.func, value: PropTypes.string }; +const GroupBySelect = injectI18n(GroupBySelectUi); export default GroupBySelect; diff --git a/src/core_plugins/metrics/public/components/splits/terms.js b/src/core_plugins/metrics/public/components/splits/terms.js index db5d08246a8f3..5376f48e8b76a 100644 --- a/src/core_plugins/metrics/public/components/splits/terms.js +++ b/src/core_plugins/metrics/public/components/splits/terms.js @@ -25,21 +25,34 @@ import createSelectHandler from '../lib/create_select_handler'; import FieldSelect from '../aggs/field_select'; import MetricSelect from '../aggs/metric_select'; import { htmlIdGenerator, EuiFlexGroup, EuiFlexItem, EuiFormRow, EuiFieldNumber, EuiComboBox, EuiSpacer } from '@elastic/eui'; +import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; -export const SplitByTerms = props => { +const SplitByTermsUi = props => { const htmlId = htmlIdGenerator(); const handleTextChange = createTextHandler(props.onChange); const handleSelectChange = createSelectHandler(props.onChange); - const { indexPattern } = props; + const { indexPattern, intl } = props; const defaults = { terms_direction: 'desc', terms_size: 10, terms_order_by: '_count' }; const model = { ...defaults, ...props.model }; const { metrics } = model; - const defaultCount = { value: '_count', label: 'Doc Count (default)' }; - const terms = { value: '_term', label: 'Terms' }; + const defaultCount = { + value: '_count', + label: intl.formatMessage({ id: 'tsvb.splits.terms.defaultCountLabel', defaultMessage: 'Doc Count (default)' }) + }; + const terms = { + value: '_term', + label: intl.formatMessage({ id: 'tsvb.splits.terms.termsLabel', defaultMessage: 'Terms' }) + }; const dirOptions = [ - { value: 'desc', label: 'Descending' }, - { value: 'asc', label: 'Ascending' }, + { + value: 'desc', + label: intl.formatMessage({ id: 'tsvb.splits.terms.dirOptions.descendingLabel', defaultMessage: 'Descending' }) + }, + { + value: 'asc', + label: intl.formatMessage({ id: 'tsvb.splits.terms.dirOptions.ascendingLabel', defaultMessage: 'Ascending' }) + }, ]; const selectedDirectionOption = dirOptions.find(option => { return model.terms_direction === option.value; @@ -49,7 +62,13 @@ export const SplitByTerms = props => {
- + )} + > { - + )} + > { - + )} + > - + )} + > { - + )} + > { ); }; -SplitByTerms.propTypes = { +SplitByTermsUi.propTypes = { model: PropTypes.object, onChange: PropTypes.func, indexPattern: PropTypes.string, fields: PropTypes.object }; + +export const SplitByTerms = injectI18n(SplitByTermsUi); \ No newline at end of file diff --git a/src/core_plugins/metrics/public/components/vis_editor.js b/src/core_plugins/metrics/public/components/vis_editor.js index b217446f81056..9b2cfedd3f30f 100644 --- a/src/core_plugins/metrics/public/components/vis_editor.js +++ b/src/core_plugins/metrics/public/components/vis_editor.js @@ -30,6 +30,7 @@ import { get } from 'lodash'; import { extractIndexPatterns } from '../lib/extract_index_patterns'; import { fetchFields } from '../lib/fetch_fields'; import chrome from 'ui/chrome'; +import { I18nProvider } from '@kbn/i18n/react'; class VisEditor extends Component { constructor(props) { @@ -131,17 +132,19 @@ class VisEditor extends Component { } const reversed = this.state.reversed; return ( - + + + ); } diff --git a/src/core_plugins/metrics/public/components/vis_editor_visualization.js b/src/core_plugins/metrics/public/components/vis_editor_visualization.js index 278265769b772..eac9a9e99b849 100644 --- a/src/core_plugins/metrics/public/components/vis_editor_visualization.js +++ b/src/core_plugins/metrics/public/components/vis_editor_visualization.js @@ -21,6 +21,7 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; import { keyCodes, EuiFlexGroup, EuiFlexItem, EuiButton, EuiText, EuiSwitch } from '@elastic/eui'; import { getVisualizeLoader } from 'ui/visualize/loader/visualize_loader'; +import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; const MIN_CHART_HEIGHT = 250; @@ -140,15 +141,31 @@ class VisEditorVisualization extends Component { style.userSelect = 'none'; } - let applyMessage = 'The latest changes have been applied.'; - if (dirty) applyMessage = 'The changes to this visualization have not been applied.'; - if (autoApply) applyMessage = 'The changes will be automatically applied.'; + let applyMessage = (); + if (dirty) { + applyMessage = (); + } + if (autoApply) { + applyMessage = (); + } const applyButton = ( )} checked={autoApply} onChange={this.props.onToggleAutoApply} /> @@ -164,7 +181,12 @@ class VisEditorVisualization extends Component { {!autoApply && - Apply changes + + + } @@ -189,7 +211,10 @@ class VisEditorVisualization extends Component { onMouseDown={this.handleMouseDown} onMouseUp={this.handleMouseUp} onKeyDown={this.onSizeHandleKeyDown} - aria-label="Press up/down to adjust the chart size" + aria-label={this.props.intl.formatMessage({ + id: 'tsvb.colorRules.adjustChartSizeAriaLabel', + defaultMessage: 'Press up/down to adjust the chart size' + })} > @@ -215,4 +240,4 @@ VisEditorVisualization.propTypes = { appState: PropTypes.object, }; -export default VisEditorVisualization; +export default injectI18n(VisEditorVisualization); diff --git a/src/core_plugins/metrics/public/components/vis_editor_visualization.test.js b/src/core_plugins/metrics/public/components/vis_editor_visualization.test.js index 15b6666207f73..146afeb07605d 100644 --- a/src/core_plugins/metrics/public/components/vis_editor_visualization.test.js +++ b/src/core_plugins/metrics/public/components/vis_editor_visualization.test.js @@ -20,7 +20,7 @@ jest.mock('ui/visualize/loader/visualize_loader', () => ({})); import React from 'react'; -import { mount } from 'enzyme'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; import VisEditorVisualization from './vis_editor_visualization'; describe('getVisualizeLoader', () => { @@ -45,7 +45,7 @@ describe('getVisualizeLoader', () => { }); it('should not call _handler.update until getVisualizeLoader returns _handler', async () => { - const wrapper = mount( + const wrapper = mountWithIntl( ); diff --git a/src/core_plugins/metrics/public/components/vis_picker.js b/src/core_plugins/metrics/public/components/vis_picker.js index 22579125837a3..d58fa8cfd43a2 100644 --- a/src/core_plugins/metrics/public/components/vis_picker.js +++ b/src/core_plugins/metrics/public/components/vis_picker.js @@ -20,6 +20,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import { EuiTabs, EuiTab } from '@elastic/eui'; +import { injectI18n } from '@kbn/i18n/react'; function VisPickerItem(props) { const { label, type, selected } = props; @@ -44,19 +45,19 @@ VisPickerItem.propTypes = { selected: PropTypes.bool }; -function VisPicker(props) { +const VisPicker = injectI18n(function (props) { const handleChange = (type) => { props.onChange({ type }); }; - const { model } = props; + const { model, intl } = props; const tabs = [ - { type: 'timeseries', label: 'Time Series' }, - { type: 'metric', label: 'Metric' }, - { type: 'top_n', label: 'Top N' }, - { type: 'gauge', label: 'Gauge' }, - { type: 'markdown', label: 'Markdown' }, - { type: 'table', label: 'Table' } + { type: 'timeseries', label: intl.formatMessage({ id: 'tsvb.visPicker.timeSeriesLabel', defaultMessage: 'Time Series' }) }, + { type: 'metric', label: intl.formatMessage({ id: 'tsvb.visPicker.metricLabel', defaultMessage: 'Metric' }) }, + { type: 'top_n', label: intl.formatMessage({ id: 'tsvb.visPicker.topNLabel', defaultMessage: 'Top N' }) }, + { type: 'gauge', label: intl.formatMessage({ id: 'tsvb.visPicker.gaugeLabel', defaultMessage: 'Gauge' }) }, + { type: 'markdown', label: intl.formatMessage({ id: 'tsvb.visPicker.markdownLabel', defaultMessage: 'Markdown' }) }, + { type: 'table', label: intl.formatMessage({ id: 'tsvb.visPicker.tableLabel', defaultMessage: 'Table' }) } ].map(item => { return ( ); -} +}); VisPicker.propTypes = { model: PropTypes.object, diff --git a/src/core_plugins/metrics/public/components/vis_types/gauge/series.js b/src/core_plugins/metrics/public/components/vis_types/gauge/series.js index 6c95520995fd3..0d336010fe49a 100644 --- a/src/core_plugins/metrics/public/components/vis_types/gauge/series.js +++ b/src/core_plugins/metrics/public/components/vis_types/gauge/series.js @@ -28,8 +28,9 @@ import { EuiToolTip, EuiTabs, EuiTab, EuiFlexGroup, EuiFlexItem, EuiFieldText, E import createAggRowRender from '../../lib/create_agg_row_render'; import createTextHandler from '../../lib/create_text_handler'; import { createUpDownHandler } from '../../lib/sort_keyhandler'; +import { injectI18n, FormattedMessage } from '@kbn/i18n/react'; -function GaugeSeries(props) { +function GaugeSeriesUi(props) { const { panel, fields, @@ -39,7 +40,8 @@ function GaugeSeries(props) { disableDelete, disableAdd, selectedTab, - visible + visible, + intl } = props; const defaults = { label: '' }; @@ -96,14 +98,20 @@ function GaugeSeries(props) { isSelected={selectedTab === 'metrics'} onClick={() => props.switchTab('metrics')} > - Metrics + props.switchTab('options')} > - Options + {seriesBody} @@ -124,11 +132,16 @@ function GaugeSeries(props) { if (!props.disableDelete) { dragHandle = ( - + )} + > @@ -150,7 +163,7 @@ function GaugeSeries(props) { iconType={caretIcon} color="text" onClick={props.toggleVisible} - aria-label="Toggle series editor" + aria-label={intl.formatMessage({ id: 'tsvb.gauge.editor.toggleEditorAriaLabel', defaultMessage: 'Toggle series editor' })} aria-expanded={props.visible} /> @@ -163,7 +176,7 @@ function GaugeSeries(props) { @@ -172,9 +185,9 @@ function GaugeSeries(props) { props.switchTab('metrics')} > - Metrics + props.switchTab('options')} > - Options + {seriesBody} @@ -122,7 +130,7 @@ function MarkdownSeries(props) { iconType={caretIcon} color="text" onClick={props.toggleVisible} - aria-label="Toggle series editor" + aria-label={intl.formatMessage({ id: 'tsvb.markdown.editor.toggleEditorAriaLabel', defaultMessage: 'Toggle series editor' })} aria-expanded={props.visible} /> @@ -131,7 +139,7 @@ function MarkdownSeries(props) { @@ -140,16 +148,16 @@ function MarkdownSeries(props) { props.switchTab('metrics')} > - Metrics + props.switchTab('options')} > - Options + {seriesBody} @@ -129,11 +137,16 @@ function MetricSeries(props) { if (!props.disableDelete) { dragHandle = ( - + )} + > @@ -154,7 +167,7 @@ function MetricSeries(props) { iconType={caretIcon} color="text" onClick={props.toggleVisible} - aria-label="Toggle series editor" + aria-label={intl.formatMessage({ id: 'tsvb.metric.editor.toggleEditorAriaLabel', defaultMessage: 'Toggle series editor' })} aria-expanded={props.visible} /> @@ -165,7 +178,7 @@ function MetricSeries(props) { @@ -174,9 +187,9 @@ function MetricSeries(props) { { return model.aggregate_function === option.value; @@ -86,8 +88,19 @@ class TableSeriesConfig extends Component { eg.{'{{value}}/s'}} + label={()} + helpText={ + + {'{{value}}/s'}) }} + /> + + } fullWidth > )} fullWidth > - Show trend arrows? + + + - + )} + > )} fullWidth > - Color rules + + + + + props.switchTab('metrics')} > - Metrics + props.switchTab('options')} > - Options + {seriesBody} @@ -102,11 +110,16 @@ function TopNSeries(props) { if (!props.disableDelete) { dragHandle = ( - + )} + > @@ -127,7 +140,7 @@ function TopNSeries(props) { iconType={caretIcon} color="text" onClick={props.toggleVisible} - aria-label="Toggle series editor" + aria-label={intl.formatMessage({ id: 'tsvb.table.toggleSeriesEditorAriaLabel', defaultMessage: 'Toggle series editor' })} aria-expanded={props.visible} /> @@ -135,9 +148,9 @@ function TopNSeries(props) { @@ -146,9 +159,9 @@ function TopNSeries(props) { {headerContent} + )} + > + {headerContent} + ); } @@ -176,10 +184,15 @@ class TableVis extends Component { if (_.isArray(visData.series) && visData.series.length) { rows = visData.series.map(this.renderRow); } else { - let message = 'No results available.'; - if (!model.pivot_id) { - message += ' You must choose a group by field for this visualization.'; - } + const message = model.pivot_id ? + () + : (); rows = (
NameValue + + + +
{ return model.stacked === option.value; }); const positionOptions = [ - { label: 'Right', value: 'right' }, - { label: 'Left', value: 'left' } + { label: intl.formatMessage({ id: 'tsvb.timeSeries.rightLabel', defaultMessage: 'Right' }), value: 'right' }, + { label: intl.formatMessage({ id: 'tsvb.timeSeries.leftLabel', defaultMessage: 'Left' }), value: 'left' } ]; const selectedAxisPosOption = positionOptions.find(option => { return model.axis_position === option.value; }); const chartTypeOptions = [ - { label: 'Bar', value: 'bar' }, - { label: 'Line', value: 'line' } + { label: intl.formatMessage({ id: 'tsvb.timeSeries.barLabel', defaultMessage: 'Bar' }), value: 'bar' }, + { label: intl.formatMessage({ id: 'tsvb.timeSeries.lineLabel', defaultMessage: 'Line' }), value: 'line' } ]; const selectedChartTypeOption = chartTypeOptions.find(option => { return model.chart_type === option.value; }); const splitColorOptions = [ - { label: 'Gradient', value: 'gradient' }, - { label: 'Rainbow', value: 'rainbow' } + { label: intl.formatMessage({ id: 'tsvb.timeSeries.gradientLabel', defaultMessage: 'Gradient' }), value: 'gradient' }, + { label: intl.formatMessage({ id: 'tsvb.timeSeries.rainbowLabel', defaultMessage: 'Rainbow' }), value: 'rainbow' } ]; const selectedSplitColorOption = splitColorOptions.find(option => { return model.split_color_mode === option.value; @@ -97,7 +99,10 @@ function TimeseriesConfig(props) { )} > )} > )} > )} > )} > - Steps + + + )} > )} > )} > )} > eg.{'{{value}}/s'}} + label={()} + helpText={( + + {'{{value}}/s'}) }} + /> + + )} fullWidth > )} fullWidth > )} > - Hide in legend + + + )} > - Separate axis? + + + )} > {/* EUITODO: The following input couldn't be converted to EUI because of type mis-match. @@ -345,7 +413,10 @@ function TimeseriesConfig(props) { )} > {/* EUITODO: The following input couldn't be converted to EUI because of type mis-match. @@ -362,7 +433,10 @@ function TimeseriesConfig(props) { )} > - Override Index Pattern? + + + ); -} +}); TimeseriesConfig.propTypes = { fields: PropTypes.object, diff --git a/src/core_plugins/metrics/public/components/vis_types/timeseries/series.js b/src/core_plugins/metrics/public/components/vis_types/timeseries/series.js index 23a349aaac39e..f4eaf628a3500 100644 --- a/src/core_plugins/metrics/public/components/vis_types/timeseries/series.js +++ b/src/core_plugins/metrics/public/components/vis_types/timeseries/series.js @@ -28,8 +28,9 @@ import Split from '../../split'; import createAggRowRender from '../../lib/create_agg_row_render'; import createTextHandler from '../../lib/create_text_handler'; import { createUpDownHandler } from '../../lib/sort_keyhandler'; +import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; -function TimeseriesSeries(props) { +const TimeseriesSeries = injectI18n(function (props) { const { panel, fields, @@ -39,7 +40,8 @@ function TimeseriesSeries(props) { disableAdd, selectedTab, onChange, - visible + visible, + intl } = props; const defaults = { label: '' }; @@ -96,14 +98,20 @@ function TimeseriesSeries(props) { isSelected={selectedTab === 'metrics'} onClick={() => props.switchTab('metrics')} > - Metrics + props.switchTab('options')} > - Options + {seriesBody} @@ -124,11 +132,19 @@ function TimeseriesSeries(props) { if (!props.disableDelete) { dragHandle = ( - + )} + > @@ -149,7 +165,10 @@ function TimeseriesSeries(props) { iconType={caretIcon} color="text" onClick={props.toggleVisible} - aria-label="Toggle series editor" + aria-label={intl.formatMessage({ + id: 'tsvb.timeSeries.toggleSeriesEditorAriaLabel', + defaultMessage: 'Toggle series editor' + })} aria-expanded={props.visible} /> @@ -162,7 +181,7 @@ function TimeseriesSeries(props) { @@ -171,9 +190,9 @@ function TimeseriesSeries(props) { ); -} +}); TimeseriesSeries.propTypes = { className: PropTypes.string, @@ -216,4 +235,4 @@ TimeseriesSeries.propTypes = { visible: PropTypes.bool }; -export default TimeseriesSeries; +export default injectI18n(TimeseriesSeries); diff --git a/src/core_plugins/metrics/public/components/vis_types/top_n/series.js b/src/core_plugins/metrics/public/components/vis_types/top_n/series.js index fa112a41e39c8..754becb788fbc 100644 --- a/src/core_plugins/metrics/public/components/vis_types/top_n/series.js +++ b/src/core_plugins/metrics/public/components/vis_types/top_n/series.js @@ -28,8 +28,9 @@ import { EuiToolTip, EuiTabs, EuiTab, EuiFlexGroup, EuiFlexItem, EuiFieldText, E import createTextHandler from '../../lib/create_text_handler'; import createAggRowRender from '../../lib/create_agg_row_render'; import { createUpDownHandler } from '../../lib/sort_keyhandler'; +import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; -function TopNSeries(props) { +const TopNSeries = injectI18n(function (props) { const { panel, model, @@ -40,7 +41,8 @@ function TopNSeries(props) { disableDelete, disableAdd, selectedTab, - visible + visible, + intl } = props; const handleChange = createTextHandler(onChange); @@ -94,14 +96,20 @@ function TopNSeries(props) { isSelected={selectedTab === 'metrics'} onClick={() => props.switchTab('metrics')} > - Metrics + props.switchTab('options')} > - Options + {seriesBody} @@ -122,11 +130,16 @@ function TopNSeries(props) { if (!props.disableDelete) { dragHandle = ( - + )} + > @@ -147,7 +160,7 @@ function TopNSeries(props) { iconType={caretIcon} color="text" onClick={props.toggleVisible} - aria-label="Toggle series editor" + aria-label={intl.formatMessage({ id: 'tsvb.topN.toggleSeriesEditorAriaLabel', defaultMessage: 'Toggle series editor' })} aria-expanded={props.visible} /> @@ -160,7 +173,7 @@ function TopNSeries(props) { @@ -169,9 +182,9 @@ function TopNSeries(props) { ); -} +}); TopNSeries.propTypes = { className: PropTypes.string, diff --git a/src/core_plugins/metrics/public/components/visualization.js b/src/core_plugins/metrics/public/components/visualization.js index b72e1b047558c..505809afdd800 100644 --- a/src/core_plugins/metrics/public/components/visualization.js +++ b/src/core_plugins/metrics/public/components/visualization.js @@ -63,18 +63,20 @@ function Visualization(props) { const component = types[model.type]; if (component) { - return React.createElement(component, { - dateFormat: props.dateFormat, - reversed: props.reversed, - backgroundColor: props.backgroundColor, - model: props.model, - onBrush: props.onBrush, - onChange: props.onChange, - onUiState: props.onUiState, - uiState: props.uiState, - visData: visData.type === model.type ? visData : {}, - getConfig: props.getConfig - }); + return ( + React.createElement(component, { + dateFormat: props.dateFormat, + reversed: props.reversed, + backgroundColor: props.backgroundColor, + model: props.model, + onBrush: props.onBrush, + onChange: props.onChange, + onUiState: props.onUiState, + uiState: props.uiState, + visData: visData.type === model.type ? visData : {}, + getConfig: props.getConfig + }) + ); } return
; } diff --git a/src/core_plugins/metrics/public/components/yes_no.js b/src/core_plugins/metrics/public/components/yes_no.js index 7a79ac6ca988c..beda91b8d3c04 100644 --- a/src/core_plugins/metrics/public/components/yes_no.js +++ b/src/core_plugins/metrics/public/components/yes_no.js @@ -21,6 +21,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import _ from 'lodash'; import { EuiRadio, htmlIdGenerator } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; function YesNo(props) { const { name, value } = props; @@ -37,7 +38,10 @@ function YesNo(props) {
)} className="eui-displayInlineBlock" name={inputName} checked={Boolean(value)} @@ -49,7 +53,10 @@ function YesNo(props) { )} className="eui-displayInlineBlock" name={inputName} checked={!Boolean(value)} diff --git a/src/core_plugins/metrics/public/components/yes_no.test.js b/src/core_plugins/metrics/public/components/yes_no.test.js index e931ba5ce0775..09d936fde6f1f 100644 --- a/src/core_plugins/metrics/public/components/yes_no.test.js +++ b/src/core_plugins/metrics/public/components/yes_no.test.js @@ -19,14 +19,14 @@ import React from 'react'; import { expect } from 'chai'; -import { shallow } from 'enzyme'; +import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import sinon from 'sinon'; import YesNo from './yes_no'; describe('YesNo', () => { it('call onChange={handleChange} on yes', () => { const handleChange = sinon.spy(); - const wrapper = shallow( + const wrapper = shallowWithIntl( ); wrapper.find('EuiRadio').first().simulate('change'); @@ -38,7 +38,7 @@ describe('YesNo', () => { it('call onChange={handleChange} on no', () => { const handleChange = sinon.spy(); - const wrapper = shallow( + const wrapper = shallowWithIntl( ); wrapper.find('EuiRadio').last().simulate('change'); diff --git a/src/core_plugins/metrics/public/kbn_vis_types/editor_controller.js b/src/core_plugins/metrics/public/kbn_vis_types/editor_controller.js index 83e2c4916b049..89c859fdd421c 100644 --- a/src/core_plugins/metrics/public/kbn_vis_types/editor_controller.js +++ b/src/core_plugins/metrics/public/kbn_vis_types/editor_controller.js @@ -19,6 +19,7 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; +import { I18nProvider } from '@kbn/i18n/react'; function ReactEditorControllerProvider(Private, config) { class ReactEditorController { @@ -30,15 +31,18 @@ function ReactEditorControllerProvider(Private, config) { async render(params) { const Component = this.vis.type.editorConfig.component; - render( {}} - isEditorMode={true} - appState={params.appState} - />, this.el); + render( + + {}} + isEditorMode={true} + appState={params.appState} + /> + , this.el); } resize() { diff --git a/src/core_plugins/metrics/public/kbn_vis_types/index.js b/src/core_plugins/metrics/public/kbn_vis_types/index.js index ea82789cf61b7..27793e22e6cff 100644 --- a/src/core_plugins/metrics/public/kbn_vis_types/index.js +++ b/src/core_plugins/metrics/public/kbn_vis_types/index.js @@ -20,23 +20,22 @@ import { MetricsRequestHandlerProvider } from './request_handler'; import { ReactEditorControllerProvider } from './editor_controller'; import { VisFactoryProvider } from 'ui/vis/vis_factory'; -import { CATEGORY } from 'ui/vis/vis_category'; import { defaultFeedbackMessage } from 'ui/vis/default_feedback_message'; // register the provider with the visTypes registry so that other know it exists import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; VisTypesRegistryProvider.register(MetricsVisProvider); -export default function MetricsVisProvider(Private) { +export default function MetricsVisProvider(Private, i18n) { const VisFactory = Private(VisFactoryProvider); const ReactEditorController = Private(ReactEditorControllerProvider).handler; const metricsRequestHandler = Private(MetricsRequestHandlerProvider).handler; return VisFactory.createReactVisualization({ name: 'metrics', - title: 'Visual Builder', - description: 'Build time-series using a visual pipeline interface', - category: CATEGORY.TIME, + title: i18n('tsvb.kbnVisTypes.metricsTitle', { defaultMessage: 'Visual Builder' }), + description: i18n('tsvb.kbnVisTypes.metricsDescription', + { defaultMessage: 'Build time-series using a visual pipeline interface' }), icon: 'visVisualBuilder', feedbackMessage: defaultFeedbackMessage, visConfig: { diff --git a/src/core_plugins/metrics/public/kbn_vis_types/request_handler.js b/src/core_plugins/metrics/public/kbn_vis_types/request_handler.js index 51bce4c094fbe..6fbe8cbc050b5 100644 --- a/src/core_plugins/metrics/public/kbn_vis_types/request_handler.js +++ b/src/core_plugins/metrics/public/kbn_vis_types/request_handler.js @@ -20,15 +20,15 @@ import { validateInterval } from '../lib/validate_interval'; import { timezoneProvider } from 'ui/vis/lib/timezone'; import { timefilter } from 'ui/timefilter'; -import { BuildESQueryProvider } from 'ui/courier'; +import { BuildESQueryProvider } from '@kbn/es-query'; -const MetricsRequestHandlerProvider = function (Private, Notifier, config, $http) { - const notify = new Notifier({ location: 'Metrics' }); +const MetricsRequestHandlerProvider = function (Private, Notifier, config, $http, i18n) { + const notify = new Notifier({ location: i18n('tsvb.requestHandler.notifier.locationNameTitle', { defaultMessage: 'Metrics' }) }); const buildEsQuery = Private(BuildESQueryProvider); return { name: 'metrics', - handler: function ({ aggs, uiState, timeRange, filters, query, visParams }) { + handler: function ({ uiState, timeRange, filters, query, visParams }) { const timezone = Private(timezoneProvider)(); return new Promise((resolve) => { const panel = visParams; @@ -39,7 +39,7 @@ const MetricsRequestHandlerProvider = function (Private, Notifier, config, $http if (panel && panel.id) { const params = { timerange: { timezone, ...parsedTimeRange }, - filters: [buildEsQuery(aggs.indexPattern, [query], filters)], + filters: [buildEsQuery(undefined, [query], filters)], panels: [panel], state: uiStateObj }; diff --git a/src/core_plugins/metrics/public/lib/fetch_fields.js b/src/core_plugins/metrics/public/lib/fetch_fields.js index 9a2c2d1c78e12..0d066eb60b41b 100644 --- a/src/core_plugins/metrics/public/lib/fetch_fields.js +++ b/src/core_plugins/metrics/public/lib/fetch_fields.js @@ -18,6 +18,7 @@ */ import { kfetch } from 'ui/kfetch'; import { toastNotifications } from 'ui/notify'; +import { i18n } from '@kbn/i18n'; async function fetchFields(indexPatterns = ['*']) { const patterns = Array.isArray(indexPatterns) ? indexPatterns : [indexPatterns]; @@ -40,7 +41,9 @@ async function fetchFields(indexPatterns = ['*']) { return fields; } catch(error) { toastNotifications.addDanger({ - title: 'Unable to load index_pattern fields', + title: i18n.translate('tsvb.fetchFields.loadIndexPatternFieldsErrorMessage', { + defaultMessage: 'Unable to load index_pattern fields' + }), text: error.message, }); } diff --git a/src/core_plugins/metrics/public/lib/validate_interval.js b/src/core_plugins/metrics/public/lib/validate_interval.js index 00a8c0bef2aba..8959e63fec835 100644 --- a/src/core_plugins/metrics/public/lib/validate_interval.js +++ b/src/core_plugins/metrics/public/lib/validate_interval.js @@ -19,6 +19,8 @@ import { parseInterval } from 'ui/utils/parse_interval'; import { GTE_INTERVAL_RE } from '../../common/interval_regexp'; +import { i18n } from '@kbn/i18n'; + export function validateInterval(bounds, panel, maxBuckets) { const { interval } = panel; const { min, max } = bounds; @@ -32,7 +34,10 @@ export function validateInterval(bounds, panel, maxBuckets) { const span = max.valueOf() - min.valueOf(); const buckets = Math.floor(span / duration.asMilliseconds()); if (buckets > maxBuckets) { - throw new Error(`Max buckets exceeded: ${buckets} is greater than ${maxBuckets}, try a larger time interval in the panel options.`); + throw new Error(i18n.translate('tsvb.validateInterval.notifier.maxBucketsExceededErrorMessage', { + defaultMessage: 'Max buckets exceeded: {buckets} is greater than {maxBuckets}, try a larger time interval in the panel options.', + values: { buckets, maxBuckets } + })); } } } diff --git a/src/core_plugins/metrics/public/visualizations/components/horizontal_legend.js b/src/core_plugins/metrics/public/visualizations/components/horizontal_legend.js index 8f9dccadbe60d..37fb4bab667b7 100644 --- a/src/core_plugins/metrics/public/visualizations/components/horizontal_legend.js +++ b/src/core_plugins/metrics/public/visualizations/components/horizontal_legend.js @@ -22,8 +22,9 @@ import React from 'react'; import createLegendSeries from '../lib/create_legend_series'; import reactcss from 'reactcss'; import { htmlIdGenerator, EuiButtonIcon } from '@elastic/eui'; +import { injectI18n } from '@kbn/i18n/react'; -function HorizontalLegend(props) { +const HorizontalLegend = injectI18n(function (props) { const rows = props.series.map(createLegendSeries(props)); const htmlId = htmlIdGenerator(); const styles = reactcss({ @@ -46,7 +47,7 @@ function HorizontalLegend(props) { color="text" iconSize="s" onClick={props.onClick} - aria-label="Toggle chart legend" + aria-label={props.intl.formatMessage({ id: 'tsvb.horizontalLegend.toggleChartAriaLabel', defaultMessage: 'Toggle chart legend' })} aria-expanded={!!props.showLegend} aria-controls={htmlId('legend')} /> @@ -55,7 +56,7 @@ function HorizontalLegend(props) {
); -} +}); HorizontalLegend.propTypes = { legendPosition: PropTypes.string, diff --git a/src/core_plugins/metrics/public/visualizations/components/vertical_legend.js b/src/core_plugins/metrics/public/visualizations/components/vertical_legend.js index d2183bfbca34f..cfd9fa840f9ec 100644 --- a/src/core_plugins/metrics/public/visualizations/components/vertical_legend.js +++ b/src/core_plugins/metrics/public/visualizations/components/vertical_legend.js @@ -22,8 +22,9 @@ import React from 'react'; import createLegendSeries from '../lib/create_legend_series'; import reactcss from 'reactcss'; import { htmlIdGenerator, EuiButtonIcon } from '@elastic/eui'; +import { injectI18n } from '@kbn/i18n/react'; -function VerticalLegend(props) { +const VerticalLegend = injectI18n(function (props) { const rows = props.series.map(createLegendSeries(props)); const htmlId = htmlIdGenerator(); const hideLegend = !props.showLegend; @@ -56,7 +57,7 @@ function VerticalLegend(props) { color="text" iconSize="s" onClick={props.onClick} - aria-label="Toggle chart legend" + aria-label={props.intl.formatMessage({ id: 'tsvb.verticalLegend.toggleChartAriaLabel', defaultMessage: 'Toggle chart legend' })} aria-expanded={!!props.showLegend} aria-controls={htmlId('legend')} /> @@ -67,7 +68,7 @@ function VerticalLegend(props) { ); -} +}); VerticalLegend.propTypes = { legendPosition: PropTypes.string, diff --git a/src/core_plugins/metrics/server/lib/vis_data/helpers/bucket_transform.js b/src/core_plugins/metrics/server/lib/vis_data/helpers/bucket_transform.js index ce061464cc994..c487e541beb39 100644 --- a/src/core_plugins/metrics/server/lib/vis_data/helpers/bucket_transform.js +++ b/src/core_plugins/metrics/server/lib/vis_data/helpers/bucket_transform.js @@ -21,11 +21,17 @@ import parseSettings from './parse_settings'; import getBucketsPath from './get_buckets_path'; import { parseInterval } from './parse_interval'; import { set } from 'lodash'; +import { i18n } from '@kbn/i18n'; function checkMetric(metric, fields) { fields.forEach(field => { if (!metric[field]) { - throw new Error(`Metric missing ${field}`); + throw new Error( + i18n.translate('tsvb.metricMissingErrorMessage', { + defaultMessage: 'Metric missing {field}', + values: { field } + }) + ); } }); } diff --git a/src/core_plugins/metrics/server/lib/vis_data/series/handle_response_body.js b/src/core_plugins/metrics/server/lib/vis_data/series/handle_response_body.js index 20eb2588ffde1..f732db82f45ed 100644 --- a/src/core_plugins/metrics/server/lib/vis_data/series/handle_response_body.js +++ b/src/core_plugins/metrics/server/lib/vis_data/series/handle_response_body.js @@ -20,6 +20,7 @@ import buildProcessorFunction from '../build_processor_function'; import processors from '../response_processors/series'; import { get } from 'lodash'; +import { i18n } from '@kbn/i18n'; export default function handleResponseBody(panel) { return resp => { @@ -30,12 +31,19 @@ export default function handleResponseBody(panel) { } const aggregations = get(resp, 'aggregations'); if (!aggregations) { - const message = `The aggregations key is missing from the response, - check your permissions for this request.`; + const message = i18n.translate('tsvb.series.missingAggregationKeyErrorMessage', { + defaultMessage: 'The aggregations key is missing from the response, check your permissions for this request.' + }); throw Error(message); } const keys = Object.keys(aggregations); - if (keys.length !== 1) throw Error('There should only be one series per request.'); + if (keys.length !== 1) { + throw Error( + i18n.translate('tsvb.series.shouldOneSeriesPerRequestErrorMessage', { + defaultMessage: 'There should only be one series per request.' + }) + ); + } const seriesId = keys[0]; const series = panel.series.find(s => s.id === seriesId); const processor = buildProcessorFunction(processors, resp, panel, series); diff --git a/src/core_plugins/region_map/public/region_map_vis.js b/src/core_plugins/region_map/public/region_map_vis.js index 24bf400f69df5..97f0e3f295de6 100644 --- a/src/core_plugins/region_map/public/region_map_vis.js +++ b/src/core_plugins/region_map/public/region_map_vis.js @@ -19,7 +19,6 @@ import './region_map_vis_params'; import { VisFactoryProvider } from 'ui/vis/vis_factory'; -import { CATEGORY } from 'ui/vis/vis_category'; import { Schemas } from 'ui/vis/editors/default/schemas'; import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; import { truncatedColorMaps } from 'ui/vislib/components/color/truncated_colormaps'; @@ -40,7 +39,6 @@ VisTypesRegistryProvider.register(function RegionMapProvider(Private, regionmaps title: i18n('regionMap.mapVis.regionMapTitle', { defaultMessage: 'Region Map' }), description: i18n('regionMap.mapVis.regionMapDescription', { defaultMessage: 'Show metrics on a thematic map. Use one of the \ provided base maps, or add your own. Darker colors represent higher values.' }), - category: CATEGORY.MAP, icon: 'visMapRegion', visConfig: { defaults: { diff --git a/src/core_plugins/region_map/public/tooltip.html b/src/core_plugins/region_map/public/tooltip.html index b0d07cb80e4b3..0d57120c80a98 100644 --- a/src/core_plugins/region_map/public/tooltip.html +++ b/src/core_plugins/region_map/public/tooltip.html @@ -1,8 +1,8 @@ - - + +
{{detail.label}}{{detail.value}}{{detail.label}}{{detail.value}}
diff --git a/src/core_plugins/table_vis/public/table_vis.js b/src/core_plugins/table_vis/public/table_vis.js index 0cd14d43253d3..aa6b96648bdfc 100644 --- a/src/core_plugins/table_vis/public/table_vis.js +++ b/src/core_plugins/table_vis/public/table_vis.js @@ -23,7 +23,6 @@ import './table_vis_params'; import 'ui/agg_table'; import 'ui/agg_table/agg_table_group'; import { VisFactoryProvider } from 'ui/vis/vis_factory'; -import { CATEGORY } from 'ui/vis/vis_category'; import { Schemas } from 'ui/vis/editors/default/schemas'; import tableVisTemplate from './table_vis.html'; import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; @@ -59,7 +58,6 @@ function TableVisTypeProvider(Private) { description: i18n.translate('tableVis.tableVisDescription', { defaultMessage: 'Display values in a table', }), - category: CATEGORY.DATA, visConfig: { defaults: { perPage: 10, diff --git a/src/core_plugins/tagcloud/public/tag_cloud_vis.js b/src/core_plugins/tagcloud/public/tag_cloud_vis.js index c65f8461137e5..18214965eab26 100644 --- a/src/core_plugins/tagcloud/public/tag_cloud_vis.js +++ b/src/core_plugins/tagcloud/public/tag_cloud_vis.js @@ -19,7 +19,6 @@ import './tag_cloud_vis_params'; import { VisFactoryProvider } from 'ui/vis/vis_factory'; -import { CATEGORY } from 'ui/vis/vis_category'; import { Schemas } from 'ui/vis/editors/default/schemas'; import { TagCloudVisualization } from './tag_cloud_visualization'; import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; @@ -36,7 +35,6 @@ VisTypesRegistryProvider.register(function (Private, i18n) { description: i18n('tagCloud.vis.tagCloudDescription', { defaultMessage: 'A group of words, sized according to their importance' }), - category: CATEGORY.OTHER, visConfig: { defaults: { scale: 'linear', diff --git a/src/core_plugins/tile_map/public/editors/_tooltip.html b/src/core_plugins/tile_map/public/editors/_tooltip.html index c05a261ed6e43..9df5b94a21eda 100644 --- a/src/core_plugins/tile_map/public/editors/_tooltip.html +++ b/src/core_plugins/tile_map/public/editors/_tooltip.html @@ -1,8 +1,8 @@ - - + +
{{detail.label}}{{detail.value}}{{detail.label}}{{detail.value}}
diff --git a/src/core_plugins/tile_map/public/tile_map_vis.js b/src/core_plugins/tile_map/public/tile_map_vis.js index 1a3349c879ab2..f76d823b155a5 100644 --- a/src/core_plugins/tile_map/public/tile_map_vis.js +++ b/src/core_plugins/tile_map/public/tile_map_vis.js @@ -21,7 +21,6 @@ import { i18n } from '@kbn/i18n'; import 'plugins/kbn_vislib_vis_types/controls/vislib_basic_options'; import './editors/tile_map_vis_params'; import { supports } from 'ui/utils/supports'; -import { CATEGORY } from 'ui/vis/vis_category'; import { VisFactoryProvider } from 'ui/vis/vis_factory'; import { CoordinateMapsVisualizationProvider } from './coordinate_maps_visualization'; import { Schemas } from 'ui/vis/editors/default/schemas'; @@ -44,7 +43,6 @@ VisTypesRegistryProvider.register(function TileMapVisType(Private, getAppState, description: i18n.translate('tileMap.vis.mapDescription', { defaultMessage: 'Plot latitude and longitude coordinates on a map', }), - category: CATEGORY.MAP, visConfig: { canDesaturate: !!supports.cssFilters, defaults: { diff --git a/src/core_plugins/timelion/public/app.js b/src/core_plugins/timelion/public/app.js index 036b53ad826c2..210db2191e4b3 100644 --- a/src/core_plugins/timelion/public/app.js +++ b/src/core_plugins/timelion/public/app.js @@ -114,29 +114,37 @@ app.controller('timelion', function ( const savedSheet = $route.current.locals.savedSheet; $scope.topNavMenu = [{ - key: 'new', - description: i18n('timelion.topNavMenu.newDescription', { + key: i18n('timelion.topNavMenu.newSheetButtonLabel', { + defaultMessage: 'new', + }), + description: i18n('timelion.topNavMenu.newSheetButtonAriaLabel', { defaultMessage: 'New Sheet', }), run: function () { kbnUrl.change('/'); }, testId: 'timelionNewButton', }, { - key: 'add', - description: i18n('timelion.topNavMenu.addDescription', { + key: i18n('timelion.topNavMenu.addChartButtonLabel', { + defaultMessage: 'add', + }), + description: i18n('timelion.topNavMenu.addChartButtonAriaLabel', { defaultMessage: 'Add a chart', }), run: function () { $scope.newCell(); }, testId: 'timelionAddChartButton', }, { - key: 'save', - description: i18n('timelion.topNavMenu.saveDescription', { + key: i18n('timelion.topNavMenu.saveSheetButtonLabel', { + defaultMessage: 'save', + }), + description: i18n('timelion.topNavMenu.saveSheetButtonAriaLabel', { defaultMessage: 'Save Sheet', }), template: require('plugins/timelion/partials/save_sheet.html'), testId: 'timelionSaveButton', }, { - key: 'delete', - description: i18n('timelion.topNavMenu.deleteDescription', { + key: i18n('timelion.topNavMenu.deleteSheetButtonLabel', { + defaultMessage: 'delete', + }), + description: i18n('timelion.topNavMenu.deleteSheetButtonAriaLabel', { defaultMessage: 'Delete current sheet', }), disableButton: function () { @@ -177,22 +185,28 @@ app.controller('timelion', function ( }, testId: 'timelionDeleteButton', }, { - key: 'open', - description: i18n('timelion.topNavMenu.openDescription', { + key: i18n('timelion.topNavMenu.openSheetButtonLabel', { + defaultMessage: 'open', + }), + description: i18n('timelion.topNavMenu.openSheetButtonAriaLabel', { defaultMessage: 'Open Sheet', }), template: require('plugins/timelion/partials/load_sheet.html'), testId: 'timelionOpenButton', }, { - key: 'options', - description: i18n('timelion.topNavMenu.optionsDescription', { + key: i18n('timelion.topNavMenu.optionsButtonLabel', { + defaultMessage: 'options', + }), + description: i18n('timelion.topNavMenu.optionsButtonAriaLabel', { defaultMessage: 'Options', }), template: require('plugins/timelion/partials/sheet_options.html'), testId: 'timelionOptionsButton', }, { - key: 'help', - description: i18n('timelion.topNavMenu.helpDescription', { + key: i18n('timelion.topNavMenu.helpButtonLabel', { + defaultMessage: 'help', + }), + description: i18n('timelion.topNavMenu.helpButtonAriaLabel', { defaultMessage: 'Help', }), template: '', diff --git a/src/core_plugins/timelion/public/vis/index.js b/src/core_plugins/timelion/public/vis/index.js index e3e12d64c753f..7ae3203e2b79a 100644 --- a/src/core_plugins/timelion/public/vis/index.js +++ b/src/core_plugins/timelion/public/vis/index.js @@ -18,7 +18,6 @@ */ import { VisFactoryProvider } from 'ui/vis/vis_factory'; -import { CATEGORY } from 'ui/vis/vis_category'; import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; import { TimelionRequestHandlerProvider } from './timelion_request_handler'; import { DefaultEditorSize } from 'ui/vis/editor_size'; @@ -46,7 +45,6 @@ export default function TimelionVisProvider(Private, i18n) { description: i18n('timelion.timelionDescription', { defaultMessage: 'Build time-series using functional expressions', }), - category: CATEGORY.TIME, visConfig: { defaults: { expression: '.es(*)', diff --git a/src/core_plugins/timelion/public/vis/timelion_request_handler.js b/src/core_plugins/timelion/public/vis/timelion_request_handler.js index a2aa4b8f5582b..63ce1ff44e458 100644 --- a/src/core_plugins/timelion/public/vis/timelion_request_handler.js +++ b/src/core_plugins/timelion/public/vis/timelion_request_handler.js @@ -18,7 +18,7 @@ */ import _ from 'lodash'; -import { BuildESQueryProvider } from 'ui/courier'; +import { BuildESQueryProvider } from '@kbn/es-query'; import { timezoneProvider } from 'ui/vis/lib/timezone'; const TimelionRequestHandlerProvider = function (Private, Notifier, $http) { @@ -31,7 +31,7 @@ const TimelionRequestHandlerProvider = function (Private, Notifier, $http) { return { name: 'timelion', - handler: function ({ aggs, timeRange, filters, query, visParams }) { + handler: function ({ timeRange, filters, query, visParams }) { return new Promise((resolve, reject) => { const expression = visParams.expression; @@ -41,7 +41,7 @@ const TimelionRequestHandlerProvider = function (Private, Notifier, $http) { sheet: [expression], extended: { es: { - filter: buildEsQuery(aggs.indexPattern, [query], filters) + filter: buildEsQuery(undefined, [query], filters) } }, time: _.extend(timeRange, { diff --git a/src/core_plugins/vega/public/vega_request_handler.js b/src/core_plugins/vega/public/vega_request_handler.js index 3fba8216c7236..323c94cc828de 100644 --- a/src/core_plugins/vega/public/vega_request_handler.js +++ b/src/core_plugins/vega/public/vega_request_handler.js @@ -21,7 +21,7 @@ import { VegaParser } from './data_model/vega_parser'; import { SearchCache } from './data_model/search_cache'; import { TimeCache } from './data_model/time_cache'; import { timefilter } from 'ui/timefilter'; -import { BuildESQueryProvider } from 'ui/courier'; +import { BuildESQueryProvider } from '@kbn/es-query'; export function VegaRequestHandlerProvider(Private, es, serviceSettings) { const buildEsQuery = Private(BuildESQueryProvider); @@ -33,9 +33,9 @@ export function VegaRequestHandlerProvider(Private, es, serviceSettings) { name: 'vega', - handler({ aggs, timeRange, filters, query, visParams }) { + handler({ timeRange, filters, query, visParams }) { timeCache.setTimeRange(timeRange); - const filtersDsl = buildEsQuery(aggs.indexPattern, [query], filters); + const filtersDsl = buildEsQuery(undefined, [query], filters); const vp = new VegaParser(visParams.spec, searchCache, timeCache, filtersDsl, serviceSettings); return vp.parseAsync(); } diff --git a/src/core_plugins/vega/public/vega_type.js b/src/core_plugins/vega/public/vega_type.js index e96fba000b4c0..397fca3c5ce55 100644 --- a/src/core_plugins/vega/public/vega_type.js +++ b/src/core_plugins/vega/public/vega_type.js @@ -20,7 +20,6 @@ import { i18n } from '@kbn/i18n'; import { VisTypesRegistryProvider } from 'ui/registry/vis_types'; import { VisFactoryProvider } from 'ui/vis/vis_factory'; -import { CATEGORY } from 'ui/vis/vis_category'; import { DefaultEditorSize } from 'ui/vis/editor_size'; import { Status } from 'ui/vis/update_status'; import { defaultFeedbackMessage } from 'ui/vis/default_feedback_message'; @@ -49,7 +48,6 @@ VisTypesRegistryProvider.register((Private) => { defaultMessage: 'Create custom visualizations using Vega and Vega-Lite', }), icon: 'visVega', - category: CATEGORY.OTHER, visConfig: { defaults: { spec: defaultSpec } }, editorConfig: { optionsTemplate: vegaEditorTemplate, @@ -65,7 +63,7 @@ VisTypesRegistryProvider.register((Private) => { showQueryBar: true, showFilterBar: true, }, - stage: 'lab', + stage: 'experimental', feedbackMessage: defaultFeedbackMessage, }); }); diff --git a/src/core_plugins/vega/public/vega_view/vega_base_view.js b/src/core_plugins/vega/public/vega_view/vega_base_view.js index d37304b28837a..99eb6aeb948eb 100644 --- a/src/core_plugins/vega/public/vega_view/vega_base_view.js +++ b/src/core_plugins/vega/public/vega_view/vega_base_view.js @@ -26,7 +26,7 @@ import { Utils } from '../data_model/utils'; import { VISUALIZATION_COLORS } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { TooltipHandler } from './vega_tooltip'; -import { buildQueryFilter } from 'ui/filter_manager/lib'; +import { buildQueryFilter } from '@kbn/es-query'; vega.scheme('elastic', VISUALIZATION_COLORS); diff --git a/src/dev/build/lib/index.js b/src/dev/build/lib/index.js index 952519773d6ba..7e318567316a5 100644 --- a/src/dev/build/lib/index.js +++ b/src/dev/build/lib/index.js @@ -32,3 +32,4 @@ export { deleteAll, } from './fs'; export { scanDelete } from './scan_delete'; +export { scanCopy } from './scan_copy'; diff --git a/src/dev/build/lib/scan_copy.test.ts b/src/dev/build/lib/scan_copy.test.ts new file mode 100644 index 0000000000000..cda009f9137f8 --- /dev/null +++ b/src/dev/build/lib/scan_copy.test.ts @@ -0,0 +1,133 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { chmodSync, statSync } from 'fs'; +import { resolve } from 'path'; + +import del from 'del'; + +// @ts-ignore +import { getChildPaths, mkdirp, write } from './fs'; +import { scanCopy } from './scan_copy'; + +const IS_WINDOWS = process.platform === 'win32'; +const FIXTURES = resolve(__dirname, '__tests__/fixtures'); +const WORLD_EXECUTABLE = resolve(FIXTURES, 'bin/world_executable'); +const TMP = resolve(__dirname, '__tests__/__tmp__'); + +const getCommonMode = (path: string) => + statSync(path) + .mode.toString(8) + .slice(-3); + +// ensure WORLD_EXECUTABLE is actually executable by all +beforeAll(async () => { + chmodSync(WORLD_EXECUTABLE, 0o777); +}); + +// cleanup TMP directory +afterEach(async () => { + await del(TMP); +}); + +it('rejects if source path is not absolute', async () => { + await expect( + scanCopy({ + source: 'foo/bar', + destination: __dirname, + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Please use absolute paths to keep things explicit. You probably want to use \`build.resolvePath()\` or \`config.resolveFromRepo()\`."` + ); +}); + +it('rejects if destination path is not absolute', async () => { + await expect( + scanCopy({ + source: __dirname, + destination: 'foo/bar', + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Please use absolute paths to keep things explicit. You probably want to use \`build.resolvePath()\` or \`config.resolveFromRepo()\`."` + ); +}); + +it('rejects if neither path is absolute', async () => { + await expect( + scanCopy({ + source: 'foo/bar', + destination: 'foo/bar', + }) + ).rejects.toThrowErrorMatchingInlineSnapshot( + `"Please use absolute paths to keep things explicit. You probably want to use \`build.resolvePath()\` or \`config.resolveFromRepo()\`."` + ); +}); + +it('copies files and directories from source to dest, including dot files, creating dest if necessary, respecting mode', async () => { + const destination = resolve(TMP, 'a/b/c'); + await scanCopy({ + source: FIXTURES, + destination, + }); + + expect((await getChildPaths(resolve(destination, 'foo_dir'))).sort()).toEqual([ + resolve(destination, 'foo_dir/.bar'), + resolve(destination, 'foo_dir/bar.txt'), + resolve(destination, 'foo_dir/foo'), + ]); + + expect(getCommonMode(resolve(destination, 'bin/world_executable'))).toBe( + IS_WINDOWS ? '666' : '777' + ); + + expect(getCommonMode(resolve(destination, 'foo_dir/bar.txt'))).toBe(IS_WINDOWS ? '666' : '644'); +}); + +it('applies filter function specified', async () => { + const destination = resolve(TMP, 'a/b/c/d'); + await scanCopy({ + source: FIXTURES, + destination, + filter: record => !record.name.includes('bar'), + }); + + expect((await getChildPaths(resolve(destination, 'foo_dir'))).sort()).toEqual([ + resolve(destination, 'foo_dir/foo'), + ]); +}); + +it('supports atime and mtime', async () => { + const destination = resolve(TMP, 'a/b/c/d/e'); + const time = new Date(1425298511000); + + await scanCopy({ + source: FIXTURES, + destination, + time, + }); + + const barTxt = statSync(resolve(destination, 'foo_dir/bar.txt')); + const fooDir = statSync(resolve(destination, 'foo_dir')); + + // precision is platform specific + const oneDay = 86400000; + expect(Math.abs(barTxt.atimeMs - time.getTime())).toBeLessThan(oneDay); + expect(Math.abs(barTxt.mtimeMs - time.getTime())).toBeLessThan(oneDay); + expect(Math.abs(fooDir.atimeMs - time.getTime())).toBeLessThan(oneDay); +}); diff --git a/src/dev/build/lib/scan_copy.ts b/src/dev/build/lib/scan_copy.ts new file mode 100644 index 0000000000000..0a4bfdc8d0b4f --- /dev/null +++ b/src/dev/build/lib/scan_copy.ts @@ -0,0 +1,110 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import Fs from 'fs'; +import { basename, join } from 'path'; +import { promisify } from 'util'; + +// @ts-ignore +import { assertAbsolute, mkdirp } from './fs'; + +const statAsync = promisify(Fs.stat); +const mkdirAsync = promisify(Fs.mkdir); +const utimesAsync = promisify(Fs.utimes); +const copyFileAsync = promisify(Fs.copyFile); +const readdirAsync = promisify(Fs.readdir); + +interface Options { + /** + * directory to copy from + */ + source: string; + /** + * path to copy to + */ + destination: string; + /** + * function that is called with each Record + */ + filter?: (record: Record) => boolean; + /** + * Date to use for atime/mtime + */ + time?: Date; +} + +class Record { + constructor( + public isDirectory: boolean, + public name: string, + public absolute: string, + public absoluteDest: string + ) {} +} + +/** + * Copy all of the files from one directory to another, optionally filtered with a + * function or modifying mtime/atime for each file. + */ +export async function scanCopy(options: Options) { + const { source, destination, filter, time } = options; + + assertAbsolute(source); + assertAbsolute(destination); + + // get filtered Records for files/directories within a directory + const getChildRecords = async (parent: Record) => { + const names = await readdirAsync(parent.absolute); + const records = await Promise.all( + names.map(async name => { + const absolute = join(parent.absolute, name); + const stat = await statAsync(absolute); + return new Record(stat.isDirectory(), name, absolute, join(parent.absoluteDest, name)); + }) + ); + + return records.filter(record => (filter ? filter(record) : true)); + }; + + // create or copy each child of a directory + const copyChildren = async (record: Record) => { + const children = await getChildRecords(record); + await Promise.all(children.map(async child => await copy(child))); + }; + + // create or copy a record and recurse into directories + const copy = async (record: Record) => { + if (record.isDirectory) { + await mkdirAsync(record.absoluteDest); + } else { + await copyFileAsync(record.absolute, record.absoluteDest, Fs.constants.COPYFILE_EXCL); + } + + if (time) { + await utimesAsync(record.absoluteDest, time, time); + } + + if (record.isDirectory) { + await copyChildren(record); + } + }; + + await mkdirp(destination); + await copyChildren(new Record(true, basename(source), source, destination)); +} diff --git a/src/dev/build/tasks/copy_source_task.js b/src/dev/build/tasks/copy_source_task.js index 65be81ededd60..fa531981e55b9 100644 --- a/src/dev/build/tasks/copy_source_task.js +++ b/src/dev/build/tasks/copy_source_task.js @@ -41,6 +41,7 @@ export const CopySourceTask = { '!src/functional_test_runner/**', '!src/dev/**', 'bin/**', + 'typings/**', 'webpackShims/**', 'config/kibana.yml', 'tsconfig*.json', diff --git a/src/dev/build/tasks/create_archives_sources_task.js b/src/dev/build/tasks/create_archives_sources_task.js index d54f295ea7a2d..5697b1784e32b 100644 --- a/src/dev/build/tasks/create_archives_sources_task.js +++ b/src/dev/build/tasks/create_archives_sources_task.js @@ -17,7 +17,7 @@ * under the License. */ -import { copyAll } from '../lib'; +import { scanCopy } from '../lib'; import { getNodeDownloadInfo } from './nodejs'; export const CreateArchivesSourcesTask = { @@ -25,34 +25,26 @@ export const CreateArchivesSourcesTask = { async run(config, log, build) { await Promise.all(config.getTargetPlatforms().map(async platform => { // copy all files from generic build source directory into platform-specific build directory - await copyAll( - build.resolvePath('.'), - build.resolvePathForPlatform(platform, '.'), - { - select: [ - '**/*', - '!node_modules/**', - ], - dot: true, - }, - ); + await scanCopy({ + source: build.resolvePath(), + destination: build.resolvePathForPlatform(platform), + filter: record => !(record.isDirectory && record.name === 'node_modules') + }); + + await scanCopy({ + source: build.resolvePath('node_modules'), + destination: build.resolvePathForPlatform(platform, 'node_modules'), + time: new Date() + }); - const currentTime = new Date(); - await copyAll( - build.resolvePath('node_modules'), - build.resolvePathForPlatform(platform, 'node_modules'), - { - dot: true, - time: currentTime - }, - ); log.debug('Generic build source copied into', platform.getName(), 'specific build directory'); // copy node.js install - await copyAll( - getNodeDownloadInfo(config, platform).extractDir, - build.resolvePathForPlatform(platform, 'node') - ); + await scanCopy({ + source: getNodeDownloadInfo(config, platform).extractDir, + destination: build.resolvePathForPlatform(platform, 'node'), + }); + log.debug('Node.js copied into', platform.getName(), 'specific build directory'); })); } diff --git a/src/dev/build/tasks/transpile_typescript_task.js b/src/dev/build/tasks/transpile_typescript_task.js index dd18b3d7bb01b..97f352e3e0663 100644 --- a/src/dev/build/tasks/transpile_typescript_task.js +++ b/src/dev/build/tasks/transpile_typescript_task.js @@ -41,7 +41,8 @@ export const TranspileTypescriptTask = { ...browserProject.config, include: [ ...browserProject.config.include, - 'src/**/public/**/*' + 'src/**/public/**/*', + 'typings/**/*' ] })); diff --git a/src/dev/ci_setup/load_bootstrap_cache.sh b/src/dev/ci_setup/load_bootstrap_cache.sh new file mode 100755 index 0000000000000..cbbd46a7ff652 --- /dev/null +++ b/src/dev/ci_setup/load_bootstrap_cache.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +set -e + +bootstrapCache="$HOME/.kibana/bootstrap_cache/master.tar" + +if [ -f "$bootstrapCache" ]; then + echo "extracting bootstrap_cache from $bootstrapCache"; + tar -xf "$bootstrapCache"; +else + echo "bootstrap_cache missing"; + exit 1; +fi diff --git a/src/dev/ci_setup/setup.sh b/src/dev/ci_setup/setup.sh index 5a8738018b681..71f8b10513e5e 100755 --- a/src/dev/ci_setup/setup.sh +++ b/src/dev/ci_setup/setup.sh @@ -84,6 +84,11 @@ hash -r yarnVersion="$(node -e "console.log(String(require('./package.json').engines.yarn || '').replace(/^[^\d]+/,''))")" npm install -g yarn@^${yarnVersion} +### +### setup yarn offline cache +### +yarn config set yarn-offline-mirror "$cacheDir/yarn-offline-cache" + ### ### "install" yarn into this shell ### @@ -95,7 +100,7 @@ hash -r ### install dependencies ### echo " -- installing node.js dependencies" -yarn kbn bootstrap +yarn kbn bootstrap --prefer-offline ### ### verify no git modifications diff --git a/src/dev/i18n/extract_default_translations.js b/src/dev/i18n/extract_default_translations.js index 275e051ff8ca5..9860e53109304 100644 --- a/src/dev/i18n/extract_default_translations.js +++ b/src/dev/i18n/extract_default_translations.js @@ -93,7 +93,7 @@ export async function extractMessagesFromPathToMap(inputPath, targetMap) { const entries = await globAsync('*.{js,jsx,pug,ts,tsx,html,hbs,handlebars}', { cwd: inputPath, matchBase: true, - ignore: ['**/node_modules/**', '**/__tests__/**', '**/*.test.{js,jsx,ts,tsx}'], + ignore: ['**/node_modules/**', '**/__tests__/**', '**/*.test.{js,jsx,ts,tsx}', '**/*.d.ts'], }); const { htmlEntries, codeEntries, pugEntries, hbsEntries } = entries.reduce( diff --git a/src/dev/jest/junit_reporter.js b/src/dev/jest/junit_reporter.js index 9f8f042b2ddb7..9d05e1dabf59c 100644 --- a/src/dev/jest/junit_reporter.js +++ b/src/dev/jest/junit_reporter.js @@ -49,7 +49,7 @@ export default class JestJUnitReporter { * @return {undefined} */ onRunComplete(contexts, results) { - if (!process.env.CI) { + if (!process.env.CI || !results.testResults.length) { return; } diff --git a/src/dev/mocha/junit_report_generation.js b/src/dev/mocha/junit_report_generation.js index e5c7aec947b8f..74b271e82f52e 100644 --- a/src/dev/mocha/junit_report_generation.js +++ b/src/dev/mocha/junit_report_generation.js @@ -27,6 +27,8 @@ import xmlBuilder from 'xmlbuilder'; import { getSnapshotOfRunnableLogs } from './log_cache'; import { escapeCdata } from '../xml'; +const dateNow = Date.now.bind(Date); + export function setupJUnitReportGeneration(runner, options = {}) { const { reportName = 'Unnamed Mocha Tests', @@ -47,11 +49,11 @@ export function setupJUnitReportGeneration(runner, options = {}) { ); const setStartTime = (node) => { - node.startTime = Date.now(); + node.startTime = dateNow(); }; const setEndTime = node => { - node.endTime = Date.now(); + node.endTime = dateNow(); }; const getFullTitle = node => { @@ -85,6 +87,9 @@ export function setupJUnitReportGeneration(runner, options = {}) { runner.on('end', () => { // crawl the test graph to collect all defined tests const allTests = findAllTests(runner.suite); + if (!allTests.length) { + return; + } // filter out just the failures const failures = results.filter(result => result.failed); diff --git a/src/dev/register_git_hook/register_git_hook.js b/src/dev/register_git_hook/register_git_hook.js index e0a119b33e421..670a3cb2b3aea 100644 --- a/src/dev/register_git_hook/register_git_hook.js +++ b/src/dev/register_git_hook/register_git_hook.js @@ -22,13 +22,23 @@ import { chmod, unlink, writeFile } from 'fs'; import dedent from 'dedent'; import { resolve } from 'path'; import { promisify } from 'util'; +import SimpleGit from 'simple-git'; import { REPO_ROOT } from '../constants'; +const simpleGit = new SimpleGit(REPO_ROOT); + const chmodAsync = promisify(chmod); +const gitRevParseAsync = promisify(simpleGit.revparse.bind(simpleGit)); const unlinkAsync = promisify(unlink); const writeFileAsync = promisify(writeFile); -const PRECOMMIT_GIT_HOOK_SCRIPT_PATH = resolve(REPO_ROOT, '.git/hooks/pre-commit'); +async function getPrecommitGitHookScriptPath(rootPath) { + // Retrieves the correct location for the .git dir for + // every git setup (including git worktree) + const gitDirPath = (await gitRevParseAsync(['--git-dir'])).trim(); + + return resolve(rootPath, gitDirPath, 'hooks/pre-commit'); +} function getKbnPrecommitGitHookScript(rootPath) { return dedent(` @@ -69,7 +79,7 @@ export async function registerPrecommitGitHook(log) { try { await writeGitHook( - PRECOMMIT_GIT_HOOK_SCRIPT_PATH, + await getPrecommitGitHookScriptPath(REPO_ROOT), getKbnPrecommitGitHookScript(REPO_ROOT) ); } catch (e) { diff --git a/src/fixtures/vislib/_vis_fixture.js b/src/fixtures/vislib/_vis_fixture.js index 056e0a2141a62..c61d21a305d24 100644 --- a/src/fixtures/vislib/_vis_fixture.js +++ b/src/fixtures/vislib/_vis_fixture.js @@ -41,7 +41,7 @@ const visHeight = $visCanvas.height(); $visCanvas.new = function () { count += 1; if (count > 1) $visCanvas.height(visHeight * count); - return $('
').addClass('visualize-chart').appendTo($visCanvas); + return $('
').addClass('visChart').appendTo($visCanvas); }; afterEach(function () { diff --git a/src/functional_test_runner/cli.js b/src/functional_test_runner/cli.js index 6998a1b8bfb7a..9414dcfe1cfd9 100644 --- a/src/functional_test_runner/cli.js +++ b/src/functional_test_runner/cli.js @@ -48,6 +48,7 @@ cmd .option('--exclude [file]', 'Path to a test file that should not be loaded', collectExcludePaths(), []) .option('--include-tag [tag]', 'A tag to be included, pass multiple times for multiple tags', collectIncludeTags(), []) .option('--exclude-tag [tag]', 'A tag to be excluded, pass multiple times for multiple tags', collectExcludeTags(), []) + .option('--test-stats', 'Print the number of tests (included and excluded) to STDERR', false) .option('--verbose', 'Log everything', false) .option('--quiet', 'Only log errors', false) .option('--silent', 'Log nothing', false) @@ -86,8 +87,16 @@ const functionalTestRunner = createFunctionalTestRunner({ async function run() { try { - const failureCount = await functionalTestRunner.run(); - process.exitCode = failureCount ? 1 : 0; + if (cmd.testStats) { + process.stderr.write(JSON.stringify( + await functionalTestRunner.getTestStats(), + null, + 2 + ) + '\n'); + } else { + const failureCount = await functionalTestRunner.run(); + process.exitCode = failureCount ? 1 : 0; + } } catch (err) { await teardown(err); } finally { diff --git a/src/functional_test_runner/functional_test_runner.js b/src/functional_test_runner/functional_test_runner.js index 65d347fbee450..9356412ba0738 100644 --- a/src/functional_test_runner/functional_test_runner.js +++ b/src/functional_test_runner/functional_test_runner.js @@ -20,7 +20,8 @@ import { createLifecycle, readConfigFile, - createProviderCollection, + ProviderCollection, + readProviderSpec, setupMocha, runTests, } from './lib'; @@ -36,8 +37,65 @@ export function createFunctionalTestRunner({ log, configFile, configOverrides }) log.verbose('ending %j lifecycle phase', name); }); + class FunctionalTestRunner { async run() { + return await this._run(async (config, coreProviders) => { + const providers = new ProviderCollection(log, [ + ...coreProviders, + ...readProviderSpec('Service', config.get('services')), + ...readProviderSpec('PageObject', config.get('pageObjects')) + ]); + + await providers.loadAll(); + + const mocha = await setupMocha(lifecycle, log, config, providers); + await lifecycle.trigger('beforeTests'); + log.info('Starting tests'); + + return await runTests(lifecycle, log, mocha); + }); + } + + async getTestStats() { + return await this._run(async (config, coreProviders) => { + // replace the function of custom service providers so that they return + // promise-like objects which never resolve, essentially disabling them + // allowing us to load the test files and populate the mocha suites + const stubProvider = provider => ( + coreProviders.includes(provider) + ? provider + : { + ...provider, + fn: () => ({ + then: () => {} + }) + } + ); + + const providers = new ProviderCollection(log, [ + ...coreProviders, + ...readProviderSpec('Service', config.get('services')), + ...readProviderSpec('PageObject', config.get('pageObjects')) + ].map(stubProvider)); + + const mocha = await setupMocha(lifecycle, log, config, providers); + + const countTests = suite => ( + suite.suites.reduce( + (sum, suite) => sum + countTests(suite), + suite.tests.length + ) + ); + + return { + testCount: countTests(mocha.suite), + excludedTests: mocha.excludedTests.map(t => t.fullTitle()) + }; + }); + } + + async _run(handler) { let runErrorOccurred = false; try { @@ -49,14 +107,14 @@ export function createFunctionalTestRunner({ log, configFile, configOverrides }) return; } - const providers = createProviderCollection(lifecycle, log, config); - await providers.loadAll(); - - const mocha = await setupMocha(lifecycle, log, config, providers); - await lifecycle.trigger('beforeTests'); - log.info('Starting tests'); - return await runTests(lifecycle, log, mocha); + // base level services that functional_test_runner exposes + const coreProviders = readProviderSpec('Service', { + lifecycle: () => lifecycle, + log: () => log, + config: () => config, + }); + return await handler(config, coreProviders); } catch (runError) { runErrorOccurred = true; throw runError; diff --git a/src/functional_test_runner/lib/config/read_config_file.js b/src/functional_test_runner/lib/config/read_config_file.js index bd3cddcf8e25a..e733d2b989ff3 100644 --- a/src/functional_test_runner/lib/config/read_config_file.js +++ b/src/functional_test_runner/lib/config/read_config_file.js @@ -22,18 +22,17 @@ import { defaultsDeep } from 'lodash'; import { Config } from './config'; import { transformDeprecations } from './transform_deprecations'; -async function getSettingsFromFile(log, path, settingOverrides) { - log.debug('Loading config file from %j', path); +const cache = new WeakMap(); +async function getSettingsFromFile(log, path, settingOverrides) { const configModule = require(path); const configProvider = configModule.__esModule ? configModule.default : configModule; - const settingsWithDefaults = defaultsDeep( - {}, - settingOverrides, - await configProvider({ + if (!cache.has(configProvider)) { + log.debug('Loading config file from %j', path); + cache.set(configProvider, configProvider({ log, async readConfigFile(...args) { return new Config({ @@ -42,7 +41,13 @@ async function getSettingsFromFile(log, path, settingOverrides) { path, }); } - }) + })); + } + + const settingsWithDefaults = defaultsDeep( + {}, + settingOverrides, + await cache.get(configProvider) ); const logDeprecation = (...args) => log.error(...args); diff --git a/src/functional_test_runner/lib/create_provider_collection.js b/src/functional_test_runner/lib/create_provider_collection.js deleted file mode 100644 index e91f473290ec3..0000000000000 --- a/src/functional_test_runner/lib/create_provider_collection.js +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { - ProviderCollection, - readProviderSpec -} from './providers'; - -/** - * Create a ProviderCollection that includes the Service - * providers and PageObject providers from config, as well - * providers for the default services, lifecycle, log, and - * config - * - * @param {Lifecycle} lifecycle - * @param {ToolingLog} log - * @param {Config} config [description] - * @return {ProviderCollection} - */ -export function createProviderCollection(lifecycle, log, config) { - return new ProviderCollection(log, [ - ...readProviderSpec('Service', { - // base level services that functional_test_runner exposes - lifecycle: () => lifecycle, - log: () => log, - config: () => config, - - ...config.get('services'), - }), - ...readProviderSpec('PageObject', { - ...config.get('pageObjects') - }) - ]); -} diff --git a/src/functional_test_runner/lib/index.js b/src/functional_test_runner/lib/index.js index 1a933447bb0fb..e1f93d8ac627c 100644 --- a/src/functional_test_runner/lib/index.js +++ b/src/functional_test_runner/lib/index.js @@ -19,5 +19,5 @@ export { createLifecycle } from './lifecycle'; export { readConfigFile } from './config'; -export { createProviderCollection } from './create_provider_collection'; export { setupMocha, runTests } from './mocha'; +export { readProviderSpec, ProviderCollection } from './providers'; diff --git a/src/functional_test_runner/lib/mocha/filter_suites_by_tags.js b/src/functional_test_runner/lib/mocha/filter_suites_by_tags.js index fd15f936c7625..597be9bab32a2 100644 --- a/src/functional_test_runner/lib/mocha/filter_suites_by_tags.js +++ b/src/functional_test_runner/lib/mocha/filter_suites_by_tags.js @@ -28,6 +28,14 @@ * @param options.exclude an array of tags that will be used to exclude suites from the run */ export function filterSuitesByTags({ log, mocha, include, exclude }) { + mocha.excludedTests = []; + // collect all the tests from some suite, including it's children + const collectTests = (suite) => + suite.suites.reduce( + (acc, s) => acc.concat(collectTests(s)), + suite.tests + ); + // if include tags were provided, filter the tree once to // only include branches that are included at some point if (include.length) { @@ -47,14 +55,18 @@ export function filterSuitesByTags({ log, mocha, include, exclude }) { continue; } + // this suite has an included child but is not included // itself, so strip out its tests and recurse to filter // out child suites which are not included if (isChildIncluded(child)) { + mocha.excludedTests = mocha.excludedTests.concat(child.tests); child.tests = []; parentSuite.suites.push(child); recurse(child); continue; + } else { + mocha.excludedTests = mocha.excludedTests.concat(collectTests(child)); } } }(mocha.suite)); @@ -78,6 +90,8 @@ export function filterSuitesByTags({ log, mocha, include, exclude }) { if (isNotExcluded(child)) { parentSuite.suites.push(child); recurse(child); + } else { + mocha.excludedTests = mocha.excludedTests.concat(collectTests(child)); } } }(mocha.suite)); diff --git a/src/setup_node_env/babel_register/register.js b/src/setup_node_env/babel_register/register.js index 2d909636a02a8..2dd5850cfa9fb 100644 --- a/src/setup_node_env/babel_register/register.js +++ b/src/setup_node_env/babel_register/register.js @@ -39,7 +39,11 @@ var ignore = [ // ignore paths matching `/node_modules/{a}/{b}`, unless `a` // is `x-pack` and `b` is not `node_modules` - /\/node_modules\/(?!x-pack\/(?!node_modules)([^\/]+))([^\/]+\/[^\/]+)/ + /\/node_modules\/(?!x-pack\/(?!node_modules)([^\/]+))([^\/]+\/[^\/]+)/, + + // ignore paths matching `/canvas/canvas_plugin/{a}/{b}` unless + // `a` is `functions` and `b` is `server` + /\/canvas\/canvas_plugin\/(?!functions\/server)([^\/]+\/[^\/]+)/, ]; if (global.__BUILT_WITH_BABEL__) { @@ -52,6 +56,13 @@ if (global.__BUILT_WITH_BABEL__) { // building their server code at require-time since version 4.2 // TODO: the plugin install process could transpile plugin server code... ignore.push(resolve(__dirname, '../../../src')); +} else { + ignore.push( + // ignore any path in the packages, unless it is in the package's + // root `src` directory, in any test or __tests__ directory, or it + // ends with .test.js, .test.ts, or .test.tsx + /\/packages\/(eslint-|kbn-)[^\/]+\/(?!src\/.*|(.+\/)?(test|__tests__)\/.+|.+\.test\.(js|ts|tsx)$)(.+$)/ + ); } // modifies all future calls to require() to automatically diff --git a/src/ui/public/_index.scss b/src/ui/public/_index.scss new file mode 100644 index 0000000000000..828c9b6860d80 --- /dev/null +++ b/src/ui/public/_index.scss @@ -0,0 +1,5 @@ +@import './query_bar/index'; +// Can't import vis folder here because of cascading issues, it's imported in core_plugins/kibana +// @import './vis/index'; +@import './vislib/index'; +@import './visualize/index'; diff --git a/src/ui/public/agg_response/hierarchical/_tooltip.html b/src/ui/public/agg_response/hierarchical/_tooltip.html index 6688d9270910f..de59cf871d46f 100644 --- a/src/ui/public/agg_response/hierarchical/_tooltip.html +++ b/src/ui/public/agg_response/hierarchical/_tooltip.html @@ -1,15 +1,15 @@ - +
- + - + - + diff --git a/src/ui/public/agg_response/point_series/_tooltip.html b/src/ui/public/agg_response/point_series/_tooltip.html index 2384fab317f2b..d6054594ec776 100644 --- a/src/ui/public/agg_response/point_series/_tooltip.html +++ b/src/ui/public/agg_response/point_series/_tooltip.html @@ -1,8 +1,8 @@
field value {{metricCol.label}}
{{row.field}}{{row.bucket}}{{row.bucket}} {{row.metric}}
- - + diff --git a/src/ui/public/agg_types/__tests__/buckets/_geo_hash.js b/src/ui/public/agg_types/__tests__/buckets/_geo_hash.js index a0736ebd2f526..041030a9afb2a 100644 --- a/src/ui/public/agg_types/__tests__/buckets/_geo_hash.js +++ b/src/ui/public/agg_types/__tests__/buckets/_geo_hash.js @@ -94,7 +94,7 @@ describe('Geohash Agg', () => { describe('precision parameter', () => { - const PRECISION_PARAM_INDEX = 6; + const PRECISION_PARAM_INDEX = 7; let precisionParam; beforeEach(() => { precisionParam = geoHashBucketAgg.params[PRECISION_PARAM_INDEX]; diff --git a/src/ui/public/agg_types/buckets/_interval_options.js b/src/ui/public/agg_types/buckets/_interval_options.js index 959574941dfbb..983daa0ca4335 100644 --- a/src/ui/public/agg_types/buckets/_interval_options.js +++ b/src/ui/public/agg_types/buckets/_interval_options.js @@ -16,10 +16,13 @@ * specific language governing permissions and limitations * under the License. */ +import { i18n } from '@kbn/i18n'; export const intervalOptions = [ { - display: 'Auto', + display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.autoDisplayName', { + defaultMessage: 'Auto', + }), val: 'auto', enabled: function (agg) { // not only do we need a time field, but the selected field needs @@ -28,39 +31,57 @@ export const intervalOptions = [ } }, { - display: 'Millisecond', + display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.millisecondDisplayName', { + defaultMessage: 'Millisecond', + }), val: 'ms' }, { - display: 'Second', + display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.secondDisplayName', { + defaultMessage: 'Second', + }), val: 's' }, { - display: 'Minute', + display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.minuteDisplayName', { + defaultMessage: 'Minute', + }), val: 'm' }, { - display: 'Hourly', + display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.hourlyDisplayName', { + defaultMessage: 'Hourly', + }), val: 'h' }, { - display: 'Daily', + display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.dailyDisplayName', { + defaultMessage: 'Daily', + }), val: 'd' }, { - display: 'Weekly', + display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.weeklyDisplayName', { + defaultMessage: 'Weekly', + }), val: 'w' }, { - display: 'Monthly', + display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.monthlyDisplayName', { + defaultMessage: 'Monthly', + }), val: 'M' }, { - display: 'Yearly', + display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.yearlyDisplayName', { + defaultMessage: 'Yearly', + }), val: 'y' }, { - display: 'Custom', + display: i18n.translate('common.ui.aggTypes.buckets.intervalOptions.customDisplayName', { + defaultMessage: 'Custom', + }), val: 'custom' } ]; diff --git a/src/ui/public/agg_types/buckets/_terms_other_bucket_helper.js b/src/ui/public/agg_types/buckets/_terms_other_bucket_helper.js index 7e2d4070189d4..3e7156abe22bd 100644 --- a/src/ui/public/agg_types/buckets/_terms_other_bucket_helper.js +++ b/src/ui/public/agg_types/buckets/_terms_other_bucket_helper.js @@ -18,9 +18,7 @@ */ import _ from 'lodash'; -import { buildExistsFilter } from '../../filter_manager/lib/exists'; -import { buildPhrasesFilter } from '../../filter_manager/lib/phrases'; -import { buildQueryFromFilters } from '../../courier'; +import { buildExistsFilter, buildPhrasesFilter, buildQueryFromFilters } from '@kbn/es-query'; /** * walks the aggregation DSL and returns DSL starting at aggregation with id of startFromAggId diff --git a/src/ui/public/agg_types/buckets/create_filter/date_histogram.js b/src/ui/public/agg_types/buckets/create_filter/date_histogram.js index 0bfefe35381e4..41f1148f7f431 100644 --- a/src/ui/public/agg_types/buckets/create_filter/date_histogram.js +++ b/src/ui/public/agg_types/buckets/create_filter/date_histogram.js @@ -18,7 +18,7 @@ */ import moment from 'moment'; -import { buildRangeFilter } from '../../../filter_manager/lib/range'; +import { buildRangeFilter } from '@kbn/es-query'; export function createFilterDateHistogram(agg, key) { const start = moment(key); diff --git a/src/ui/public/agg_types/buckets/create_filter/date_range.js b/src/ui/public/agg_types/buckets/create_filter/date_range.js index 5acace8e53dfc..11274e1a4235e 100644 --- a/src/ui/public/agg_types/buckets/create_filter/date_range.js +++ b/src/ui/public/agg_types/buckets/create_filter/date_range.js @@ -19,7 +19,7 @@ import chrome from '../../../chrome'; import { dateRange } from '../../../utils/date_range'; -import { buildRangeFilter } from '../../../filter_manager/lib/range'; +import { buildRangeFilter } from '@kbn/es-query'; const config = chrome.getUiSettingsClient(); diff --git a/src/ui/public/agg_types/buckets/create_filter/filters.js b/src/ui/public/agg_types/buckets/create_filter/filters.js index 23dfeb109cc1a..e2e141ddf2adc 100644 --- a/src/ui/public/agg_types/buckets/create_filter/filters.js +++ b/src/ui/public/agg_types/buckets/create_filter/filters.js @@ -17,7 +17,7 @@ * under the License. */ -import { buildQueryFilter } from '../../../filter_manager/lib/query'; +import { buildQueryFilter } from '@kbn/es-query'; import _ from 'lodash'; export function createFilterFilters(aggConfig, key) { diff --git a/src/ui/public/agg_types/buckets/create_filter/histogram.js b/src/ui/public/agg_types/buckets/create_filter/histogram.js index d9ddae8ae30f1..1694fc04006c1 100644 --- a/src/ui/public/agg_types/buckets/create_filter/histogram.js +++ b/src/ui/public/agg_types/buckets/create_filter/histogram.js @@ -17,7 +17,7 @@ * under the License. */ -import { buildRangeFilter } from '../../../filter_manager/lib/range'; +import { buildRangeFilter } from '@kbn/es-query'; export function createFilterHistogram(aggConfig, key) { const value = parseInt(key, 10); diff --git a/src/ui/public/agg_types/buckets/create_filter/ip_range.js b/src/ui/public/agg_types/buckets/create_filter/ip_range.js index 578607edb903d..f13c4a6369c02 100644 --- a/src/ui/public/agg_types/buckets/create_filter/ip_range.js +++ b/src/ui/public/agg_types/buckets/create_filter/ip_range.js @@ -18,7 +18,7 @@ */ import { CidrMask } from '../../../utils/cidr_mask'; -import { buildRangeFilter } from '../../../filter_manager/lib/range'; +import { buildRangeFilter } from '@kbn/es-query'; export function createFilterIpRange(aggConfig, key) { let range; diff --git a/src/ui/public/agg_types/buckets/create_filter/range.js b/src/ui/public/agg_types/buckets/create_filter/range.js index e344aae438d40..099d564f120f5 100644 --- a/src/ui/public/agg_types/buckets/create_filter/range.js +++ b/src/ui/public/agg_types/buckets/create_filter/range.js @@ -17,7 +17,7 @@ * under the License. */ -import { buildRangeFilter } from '../../../filter_manager/lib/range'; +import { buildRangeFilter } from '@kbn/es-query'; export function createFilterRange(aggConfig, key) { return buildRangeFilter( diff --git a/src/ui/public/agg_types/buckets/create_filter/terms.js b/src/ui/public/agg_types/buckets/create_filter/terms.js index cd3377d6e473b..76e874d4fb69b 100644 --- a/src/ui/public/agg_types/buckets/create_filter/terms.js +++ b/src/ui/public/agg_types/buckets/create_filter/terms.js @@ -17,9 +17,7 @@ * under the License. */ -import { buildPhraseFilter } from '../../../filter_manager/lib/phrase'; -import { buildPhrasesFilter } from '../../../filter_manager/lib/phrases'; -import { buildExistsFilter } from '../../../filter_manager/lib/exists'; +import { buildPhraseFilter, buildPhrasesFilter, buildExistsFilter } from '@kbn/es-query'; export function createFilterTerms(aggConfig, key, params) { const field = aggConfig.params.field; diff --git a/src/ui/public/agg_types/buckets/date_histogram.js b/src/ui/public/agg_types/buckets/date_histogram.js index 78c926e9cdfcf..1a8b06832e8d9 100644 --- a/src/ui/public/agg_types/buckets/date_histogram.js +++ b/src/ui/public/agg_types/buckets/date_histogram.js @@ -17,10 +17,9 @@ * under the License. */ -import { jstz as tzDetect } from 'jstimezonedetect'; import _ from 'lodash'; import chrome from '../../chrome'; -import moment from 'moment'; +import moment from 'moment-timezone'; import '../../filters/field_type'; import '../../validate_date_interval'; import { BucketAggType } from './_bucket_agg_type'; @@ -30,9 +29,10 @@ import { intervalOptions } from './_interval_options'; import intervalTemplate from '../controls/time_interval.html'; import { timefilter } from '../../timefilter'; import dropPartialTemplate from '../controls/drop_partials.html'; +import { i18n } from '@kbn/i18n'; const config = chrome.getUiSettingsClient(); -const detectedTimezone = tzDetect.determine().name(); +const detectedTimezone = moment.tz.guess(); const tzOffset = moment().format('Z'); function getInterval(agg) { @@ -53,14 +53,22 @@ function setBounds(agg, force) { export const dateHistogramBucketAgg = new BucketAggType({ name: 'date_histogram', - title: 'Date Histogram', + title: i18n.translate('common.ui.aggTypes.buckets.dateHistogramTitle', { + defaultMessage: 'Date Histogram', + }), ordered: { date: true }, makeLabel: function (agg) { const output = this.params.write(agg); const field = agg.getFieldDisplayName(); - return field + ' per ' + (output.metricScaleText || output.bucketInterval.description); + return i18n.translate('common.ui.aggTypes.buckets.dateHistogramLabel', { + defaultMessage: '{fieldName} per {intervalDescription}', + values: { + fieldName: field, + intervalDescription: output.metricScaleText || output.bucketInterval.description, + } + }); }, createFilter: createFilterDateHistogram, decorateAggConfig: function () { diff --git a/src/ui/public/agg_types/buckets/date_range.js b/src/ui/public/agg_types/buckets/date_range.js index c197efb68ddb7..4643f7f6f9c34 100644 --- a/src/ui/public/agg_types/buckets/date_range.js +++ b/src/ui/public/agg_types/buckets/date_range.js @@ -24,10 +24,13 @@ import { BucketAggType } from './_bucket_agg_type'; import { createFilterDateRange } from './create_filter/date_range'; import { fieldFormats } from '../../registry/field_formats'; import dateRangesTemplate from '../controls/date_ranges.html'; +import { i18n } from '@kbn/i18n'; export const dateRangeBucketAgg = new BucketAggType({ name: 'date_range', - title: 'Date Range', + title: i18n.translate('common.ui.aggTypes.buckets.dateRangeTitle', { + defaultMessage: 'Date Range', + }), createFilter: createFilterDateRange, getKey: function (bucket, key, agg) { const formatter = agg.fieldOwnFormatter('text', fieldFormats.getDefaultInstance('date')); diff --git a/src/ui/public/agg_types/buckets/filter.js b/src/ui/public/agg_types/buckets/filter.js index 8ac750fe36239..02b54a63ea794 100644 --- a/src/ui/public/agg_types/buckets/filter.js +++ b/src/ui/public/agg_types/buckets/filter.js @@ -18,10 +18,13 @@ */ import { BucketAggType } from './_bucket_agg_type'; +import { i18n } from '@kbn/i18n'; export const filterBucketAgg = new BucketAggType({ name: 'filter', - title: 'Filter', + title: i18n.translate('common.ui.aggTypes.buckets.filterTitle', { + defaultMessage: 'Filter', + }), params: [ { name: 'geo_bounding_box' diff --git a/src/ui/public/agg_types/buckets/filters.js b/src/ui/public/agg_types/buckets/filters.js index 381abfb86ef34..a13631d54a637 100644 --- a/src/ui/public/agg_types/buckets/filters.js +++ b/src/ui/public/agg_types/buckets/filters.js @@ -22,12 +22,17 @@ import angular from 'angular'; import { BucketAggType } from './_bucket_agg_type'; import { createFilterFilters } from './create_filter/filters'; -import { decorateQuery, luceneStringToDsl } from '../../courier'; +import { decorateQuery, luceneStringToDsl } from '@kbn/es-query'; import filtersTemplate from '../controls/filters.html'; +import { i18n } from '@kbn/i18n'; + +import chrome from 'ui/chrome'; export const filtersBucketAgg = new BucketAggType({ name: 'filters', - title: 'Filters', + title: i18n.translate('common.ui.aggTypes.buckets.filtersTitle', { + defaultMessage: 'Filters', + }), createFilter: createFilterFilters, customLabels: false, params: [ @@ -53,7 +58,7 @@ export const filtersBucketAgg = new BucketAggType({ return; } - decorateQuery(query); + decorateQuery(query, chrome.getUiSettingsClient()); const matchAllLabel = (filter.input.query === '' && _.has(query, 'match_all')) ? '*' : ''; const label = filter.label || matchAllLabel || _.get(query, 'query_string.query') || angular.toJson(query); diff --git a/src/ui/public/agg_types/buckets/geo_hash.js b/src/ui/public/agg_types/buckets/geo_hash.js index 1ade0f904c332..f3bc503da2d22 100644 --- a/src/ui/public/agg_types/buckets/geo_hash.js +++ b/src/ui/public/agg_types/buckets/geo_hash.js @@ -23,6 +23,7 @@ import { BucketAggType } from './_bucket_agg_type'; import precisionTemplate from '../controls/precision.html'; import { geohashColumns } from '../../utils/decode_geo_hash'; import { geoContains, scaleBounds } from '../../utils/geo_utils'; +import { i18n } from '@kbn/i18n'; const config = chrome.getUiSettingsClient(); @@ -68,7 +69,9 @@ function isOutsideCollar(bounds, collar) { export const geoHashBucketAgg = new BucketAggType({ name: 'geohash_grid', - title: 'Geohash', + title: i18n.translate('common.ui.aggTypes.buckets.geohashGridTitle', { + defaultMessage: 'Geohash', + }), params: [ { name: 'field', @@ -100,6 +103,11 @@ export const geoHashBucketAgg = new BucketAggType({ default: [0, 0], write: _.noop }, + { + name: 'mapBounds', + default: null, + write: _.noop + }, { name: 'precision', editor: precisionTemplate, diff --git a/src/ui/public/agg_types/buckets/histogram.js b/src/ui/public/agg_types/buckets/histogram.js index 5cbaa3a145b12..aac100b5bffdc 100644 --- a/src/ui/public/agg_types/buckets/histogram.js +++ b/src/ui/public/agg_types/buckets/histogram.js @@ -19,7 +19,6 @@ import _ from 'lodash'; -import { i18n } from '@kbn/i18n'; import { toastNotifications } from 'ui/notify'; import '../../validate_date_interval'; import chrome from '../../chrome'; @@ -28,11 +27,14 @@ import { createFilterHistogram } from './create_filter/histogram'; import intervalTemplate from '../controls/number_interval.html'; import minDocCountTemplate from '../controls/min_doc_count.html'; import extendedBoundsTemplate from '../controls/extended_bounds.html'; +import { i18n } from '@kbn/i18n'; const config = chrome.getUiSettingsClient(); export const histogramBucketAgg = new BucketAggType({ name: 'histogram', - title: 'Histogram', + title: i18n.translate('common.ui.aggTypes.buckets.histogramTitle', { + defaultMessage: 'Histogram', + }), ordered: {}, makeLabel: function (aggConfig) { return aggConfig.getFieldDisplayName(); diff --git a/src/ui/public/agg_types/buckets/ip_range.js b/src/ui/public/agg_types/buckets/ip_range.js index 8e8c724c87533..e124ad69ee94a 100644 --- a/src/ui/public/agg_types/buckets/ip_range.js +++ b/src/ui/public/agg_types/buckets/ip_range.js @@ -23,10 +23,13 @@ import '../../directives/validate_cidr_mask'; import { BucketAggType } from './_bucket_agg_type'; import { createFilterIpRange } from './create_filter/ip_range'; import ipRangesTemplate from '../controls/ip_ranges.html'; +import { i18n } from '@kbn/i18n'; export const ipRangeBucketAgg = new BucketAggType({ name: 'ip_range', - title: 'IPv4 Range', + title: i18n.translate('common.ui.aggTypes.buckets.ipRangeTitle', { + defaultMessage: 'IPv4 Range', + }), createFilter: createFilterIpRange, getKey: function (bucket, key) { if (key) return key; @@ -35,7 +38,12 @@ export const ipRangeBucketAgg = new BucketAggType({ return `${from} to ${to}`; }, makeLabel: function (aggConfig) { - return aggConfig.getFieldDisplayName() + ' IP ranges'; + return i18n.translate('common.ui.aggTypes.buckets.ipRangeLabel', { + defaultMessage: '{fieldName} IP ranges', + values: { + fieldName: aggConfig.getFieldDisplayName() + } + }); }, params: [ { diff --git a/src/ui/public/agg_types/buckets/range.js b/src/ui/public/agg_types/buckets/range.js index d51e315084bc8..20d0de4b4f02d 100644 --- a/src/ui/public/agg_types/buckets/range.js +++ b/src/ui/public/agg_types/buckets/range.js @@ -22,16 +22,24 @@ import { createFilterRange } from './create_filter/range'; import { FieldFormat } from '../../../field_formats/field_format'; import { RangeKey } from './range_key'; import rangesTemplate from '../controls/ranges.html'; +import { i18n } from '@kbn/i18n'; const keyCaches = new WeakMap(); const formats = new WeakMap(); export const rangeBucketAgg = new BucketAggType({ name: 'range', - title: 'Range', + title: i18n.translate('common.ui.aggTypes.buckets.rangeTitle', { + defaultMessage: 'Range', + }), createFilter: createFilterRange, makeLabel: function (aggConfig) { - return aggConfig.getFieldDisplayName() + ' ranges'; + return i18n.translate('common.ui.aggTypes.buckets.rangesLabel', { + defaultMessage: '{fieldName} ranges', + values: { + fieldName: aggConfig.getFieldDisplayName() + } + }); }, getKey: function (bucket, key, agg) { let keys = keyCaches.get(agg); @@ -57,7 +65,13 @@ export const rangeBucketAgg = new BucketAggType({ const RangeFormat = FieldFormat.from(function (range) { const format = agg.fieldOwnFormatter(); - return `${format(range.gte)} to ${format(range.lt)}`; + return i18n.translate('common.ui.aggTypes.buckets.ranges.rangesFormatMessage', { + defaultMessage: '{from} to {to}', + values: { + from: format(range.gte), + to: format(range.lt) + } + }); }); format = new RangeFormat(); diff --git a/src/ui/public/agg_types/buckets/significant_terms.js b/src/ui/public/agg_types/buckets/significant_terms.js index 544a85123cc0d..77e8a0f80a506 100644 --- a/src/ui/public/agg_types/buckets/significant_terms.js +++ b/src/ui/public/agg_types/buckets/significant_terms.js @@ -20,12 +20,21 @@ import { BucketAggType } from './_bucket_agg_type'; import { createFilterTerms } from './create_filter/terms'; import orderAndSizeTemplate from '../controls/order_and_size.html'; +import { i18n } from '@kbn/i18n'; export const significantTermsBucketAgg = new BucketAggType({ name: 'significant_terms', - title: 'Significant Terms', + title: i18n.translate('common.ui.aggTypes.buckets.significantTermsTitle', { + defaultMessage: 'Significant Terms', + }), makeLabel: function (aggConfig) { - return 'Top ' + aggConfig.params.size + ' unusual terms in ' + aggConfig.getFieldDisplayName(); + return i18n.translate('common.ui.aggTypes.buckets.significantTermsLabel', { + defaultMessage: 'Top {size} unusual terms in {fieldName}', + values: { + size: aggConfig.params.size, + fieldName: aggConfig.getFieldDisplayName(), + } + }); }, createFilter: createFilterTerms, params: [ diff --git a/src/ui/public/agg_types/buckets/terms.js b/src/ui/public/agg_types/buckets/terms.js index 848c37ff355ab..55e0625cec694 100644 --- a/src/ui/public/agg_types/buckets/terms.js +++ b/src/ui/public/agg_types/buckets/terms.js @@ -26,6 +26,7 @@ import { createFilterTerms } from './create_filter/terms'; import orderAggTemplate from '../controls/order_agg.html'; import orderAndSizeTemplate from '../controls/order_and_size.html'; import otherBucketTemplate from '../controls/other_bucket.html'; +import { i18n } from '@kbn/i18n'; import { getRequestInspectorStats, getResponseInspectorStats } from '../../courier/utils/courier_inspector_utils'; import { buildOtherBucketAgg, mergeOtherBucketAggResponse, updateMissingBucket } from './_terms_other_bucket_helper'; @@ -40,7 +41,9 @@ const orderAggSchema = (new Schemas([ { group: 'none', name: 'orderAgg', - title: 'Order Agg', + title: i18n.translate('common.ui.aggTypes.buckets.terms.orderAggTitle', { + defaultMessage: 'Order Agg', + }), hideCustomLabel: true, aggFilter: aggFilter } @@ -70,7 +73,9 @@ const migrateIncludeExcludeFormat = { export const termsBucketAgg = new BucketAggType({ name: 'terms', - title: 'Terms', + title: i18n.translate('common.ui.aggTypes.buckets.termsTitle', { + defaultMessage: 'Terms', + }), makeLabel: function (agg) { const params = agg.params; return agg.getFieldDisplayName() + ': ' + params.order.display; @@ -254,8 +259,18 @@ export const termsBucketAgg = new BucketAggType({ default: 'desc', editor: orderAndSizeTemplate, options: [ - { display: 'Descending', val: 'desc' }, - { display: 'Ascending', val: 'asc' } + { + display: i18n.translate('common.ui.aggTypes.buckets.terms.orderDescendingTitle', { + defaultMessage: 'Descending', + }), + val: 'desc' + }, + { + display: i18n.translate('common.ui.aggTypes.buckets.terms.orderAscendingTitle', { + defaultMessage: 'Ascending', + }), + val: 'asc' + } ], write: _.noop // prevent default write, it's handled by orderAgg }, @@ -270,7 +285,9 @@ export const termsBucketAgg = new BucketAggType({ write: _.noop }, { name: 'otherBucketLabel', - default: 'Other', + default: i18n.translate('common.ui.aggTypes.buckets.terms.otherBucketLabel', { + defaultMessage: 'Other', + }), write: _.noop }, { name: 'missingBucket', @@ -278,7 +295,9 @@ export const termsBucketAgg = new BucketAggType({ write: _.noop }, { name: 'missingBucketLabel', - default: 'Missing', + default: i18n.translate('common.ui.aggTypes.buckets.terms.missingBucketLabel', { + defaultMessage: 'Missing', + }), write: _.noop }, { diff --git a/src/ui/public/agg_types/controls/drop_partials.html b/src/ui/public/agg_types/controls/drop_partials.html index 3b3713db22f87..776916e4817dd 100644 --- a/src/ui/public/agg_types/controls/drop_partials.html +++ b/src/ui/public/agg_types/controls/drop_partials.html @@ -1,6 +1,6 @@ -
+
{{detail.label}} + {{detail.label}} {{detail.value}} ({{detail.percent}})
- - - - - - - - - - - - - - - -
- Below is a table of - 0 - items. -
- - - - - - - - - -
-
- - No items found - -
-
-
-
-
-
-
-
- -
-
-
-
+ + opbeans-python + + `; -exports[`ErrorGroupOverview -> List should render with data 1`] = ` -.c0 { - font-size: 16px; - max-width: 100%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} +exports[`ErrorGroupOverview -> List should render empty state 1`] = ` + +`; -
-
-
-
-
-
- -
-
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
- Below is a table of - 2 - items. -
- - - - - - - - - -
- - -
- nodejs -
-
-
- N/A -
-
-
- 0 tpm -
-
-
- 46.1 err. -
-
- - -
- python -
-
-
- 92 ms -
-
-
- 86.9 tpm -
-
-
- 12.6 err. -
-
-
-
-
-
-
-
- -
-
-
-
+exports[`ErrorGroupOverview -> List should render with data 1`] = ` + `; diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/index.js b/x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/index.tsx similarity index 65% rename from x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/index.js rename to x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/index.tsx index 76317a51fa6f2..5ac5530540466 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/index.js +++ b/x-pack/plugins/apm/public/components/app/ServiceOverview/ServiceList/index.tsx @@ -4,16 +4,21 @@ * you may not use this file except in compliance with the Elastic License. */ +import { EuiToolTip } from '@elastic/eui'; import React from 'react'; -import PropTypes from 'prop-types'; import styled from 'styled-components'; -import { RelativeLink } from '../../../../utils/url'; +import { IServiceListItem } from 'x-pack/plugins/apm/server/lib/services/get_services'; import { fontSizes, truncate } from '../../../../style/variables'; -import TooltipOverlay from '../../../shared/TooltipOverlay'; -import { asMillis, asDecimal } from '../../../../utils/formatters'; +import { asDecimal, asMillis } from '../../../../utils/formatters'; +import { RelativeLink } from '../../../../utils/url'; import { ManagedTable } from '../../../shared/ManagedTable'; -function formatNumber(value) { +interface Props { + items: IServiceListItem[]; + noItemsMessage?: React.ReactNode; +} + +function formatNumber(value: number) { if (value === 0) { return '0'; } else if (value <= 0.1) { @@ -23,7 +28,7 @@ function formatNumber(value) { } } -function formatString(value) { +function formatString(value?: string | null) { return value || 'N/A'; } @@ -32,50 +37,50 @@ const AppLink = styled(RelativeLink)` ${truncate('100%')}; `; -const SERVICE_COLUMNS = [ +export const SERVICE_COLUMNS = [ { field: 'serviceName', name: 'Name', width: '50%', sortable: true, - render: serviceName => ( - + render: (serviceName: string) => ( + {formatString(serviceName)} - + ) }, { field: 'agentName', name: 'Agent', sortable: true, - render: agentName => formatString(agentName) + render: (agentName: string) => formatString(agentName) }, { field: 'avgResponseTime', name: 'Avg. response time', sortable: true, dataType: 'number', - render: value => asMillis(value) + render: (value: number) => asMillis(value) }, { field: 'transactionsPerMinute', name: 'Trans. per minute', sortable: true, dataType: 'number', - render: value => `${formatNumber(value)} tpm` + render: (value: number) => `${formatNumber(value)} tpm` }, { field: 'errorsPerMinute', name: 'Errors per minute', sortable: true, dataType: 'number', - render: value => `${formatNumber(value)} err.` + render: (value: number) => `${formatNumber(value)} err.` } ]; -export function ServiceList({ items, noItemsMessage }) { +export function ServiceList({ items = [], noItemsMessage }: Props) { return ( ); } - -ServiceList.propTypes = { - noItemsMessage: PropTypes.node, - items: PropTypes.array -}; - -ServiceList.defaultProps = { - items: [] -}; diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.js b/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.js index 8ef92b829be9c..2f93eb0b2f1f0 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.js +++ b/x-pack/plugins/apm/public/components/app/ServiceOverview/__test__/ServiceOverview.test.js @@ -13,11 +13,21 @@ import * as apmRestServices from '../../../../services/rest/apm'; jest.mock('../../../../services/rest/apm'); describe('Service Overview -> View', () => { + let mockAgentStatus; let wrapper; let instance; beforeEach(() => { - wrapper = shallow(); + mockAgentStatus = { + dataFound: true + }; + + // eslint-disable-next-line import/namespace + apmRestServices.loadAgentStatus = jest.fn(() => + Promise.resolve(mockAgentStatus) + ); + + wrapper = shallow(); instance = wrapper.instance(); }); @@ -40,53 +50,10 @@ describe('Service Overview -> View', () => { expect(List.props).toMatchSnapshot(); }); - describe('checking for historical data', () => { - let mockAgentStatus; - - beforeEach(() => { - mockAgentStatus = { - dataFound: true - }; - // eslint-disable-next-line import/namespace - apmRestServices.loadAgentStatus = jest.fn(() => - Promise.resolve(mockAgentStatus) - ); - }); - - it('should happen if service list status is success and data is empty', async () => { - const props = { - serviceList: { - status: STATUS.SUCCESS, - data: [] - } - }; - await instance.checkForHistoricalData(props); - expect(apmRestServices.loadAgentStatus).toHaveBeenCalledTimes(1); - }); - - it('should not happen if sevice list status is not success', async () => { - const props = { - serviceList: { - status: STATUS.FAILURE, - data: [] - } - }; - await instance.checkForHistoricalData(props); - expect(apmRestServices.loadAgentStatus).not.toHaveBeenCalled(); - }); - - it('should not happen if service list data is not empty', async () => { - const props = { - serviceList: { - status: STATUS.SUCCESS, - data: [1, 2, 3] - } - }; - await instance.checkForHistoricalData(props); - expect(apmRestServices.loadAgentStatus).not.toHaveBeenCalled(); - }); + it('should check for historical data once', () => {}); - it('should leave historical data state as true if data is found', async () => { + describe('checking for historical data', () => { + it('should set historical data to true if data is found', async () => { const props = { serviceList: { status: STATUS.SUCCESS, diff --git a/x-pack/plugins/apm/public/components/app/ServiceOverview/view.js b/x-pack/plugins/apm/public/components/app/ServiceOverview/view.tsx similarity index 51% rename from x-pack/plugins/apm/public/components/app/ServiceOverview/view.js rename to x-pack/plugins/apm/public/components/app/ServiceOverview/view.tsx index 9e0759bb735a1..2bd9373b94c3c 100644 --- a/x-pack/plugins/apm/public/components/app/ServiceOverview/view.js +++ b/x-pack/plugins/apm/public/components/app/ServiceOverview/view.tsx @@ -4,40 +4,39 @@ * you may not use this file except in compliance with the Elastic License. */ +import { EuiSpacer } from '@elastic/eui'; import React, { Component } from 'react'; -import { STATUS } from '../../../constants'; -import { isEmpty } from 'lodash'; +import { RRRRenderResponse } from 'react-redux-request'; +import { IUrlParams } from 'x-pack/plugins/apm/public/store/urlParams'; +import { IServiceListItem } from 'x-pack/plugins/apm/server/lib/services/get_services'; import { loadAgentStatus } from '../../../services/rest/apm'; -import { ServiceList } from './ServiceList'; -import { EuiSpacer } from '@elastic/eui'; import { ServiceListRequest } from '../../../store/reactReduxRequest/serviceList'; -import EmptyMessage from '../../shared/EmptyMessage'; +import { EmptyMessage } from '../../shared/EmptyMessage'; import { SetupInstructionsLink } from '../../shared/SetupInstructionsLink'; +import { ServiceList } from './ServiceList'; -export class ServiceOverview extends Component { - state = { - historicalDataFound: true - }; +interface Props { + urlParams: IUrlParams; + serviceList: RRRRenderResponse; +} - async checkForHistoricalData({ serviceList }) { - if (serviceList.status === STATUS.SUCCESS && isEmpty(serviceList.data)) { - const result = await loadAgentStatus(); - if (!result.dataFound) { - this.setState({ historicalDataFound: false }); - } - } - } +interface State { + historicalDataFound: boolean; +} + +export class ServiceOverview extends Component { + public state = { historicalDataFound: true }; - componentDidMount() { - this.checkForHistoricalData(this.props); + public async checkForHistoricalData() { + const result = await loadAgentStatus(); + this.setState({ historicalDataFound: result.dataFound }); } - componentDidUpdate() { - // QUESTION: Do we want to check on ANY update, or only if serviceList status/data have changed? - this.checkForHistoricalData(this.props); + public componentDidMount() { + this.checkForHistoricalData(); } - render() { + public render() { const { urlParams } = this.props; const { historicalDataFound } = this.state; @@ -54,13 +53,19 @@ export class ServiceOverview extends Component { /> ); + // Render method here uses this.props.serviceList instead of received "data" from RRR + // to make it easier to test -- mapStateToProps uses the RRR selector so the data + // is the same either way return (
( - + render={() => ( + )} />
diff --git a/x-pack/plugins/apm/public/components/app/TraceOverview/TraceList.tsx b/x-pack/plugins/apm/public/components/app/TraceOverview/TraceList.tsx index 0de0fb0c183aa..e587f513a58b6 100644 --- a/x-pack/plugins/apm/public/components/app/TraceOverview/TraceList.tsx +++ b/x-pack/plugins/apm/public/components/app/TraceOverview/TraceList.tsx @@ -6,7 +6,7 @@ import React from 'react'; import styled from 'styled-components'; -import { ITransactionGroup } from '../../../../typings/TransactionGroup'; +import { ITransactionGroup } from 'x-pack/plugins/apm/server/lib/transaction_groups/transform'; import { fontSizes, truncate } from '../../../style/variables'; import { asMillis } from '../../../utils/formatters'; import { ImpactBar } from '../../shared/ImpactBar'; @@ -22,7 +22,7 @@ const StyledTransactionLink = styled(TransactionLink)` interface Props { items: ITransactionGroup[]; - noItemsMessage: any; + noItemsMessage: React.ReactNode; isLoading: boolean; } diff --git a/x-pack/plugins/apm/public/components/app/TraceOverview/view.tsx b/x-pack/plugins/apm/public/components/app/TraceOverview/view.tsx index 3d14db033bd41..0e6224f83bfdf 100644 --- a/x-pack/plugins/apm/public/components/app/TraceOverview/view.tsx +++ b/x-pack/plugins/apm/public/components/app/TraceOverview/view.tsx @@ -7,10 +7,10 @@ import { EuiCallOut, EuiLink, EuiSpacer, EuiText } from '@elastic/eui'; import React from 'react'; import { RRRRenderResponse } from 'react-redux-request'; -import { ITransactionGroup } from '../../../../typings/TransactionGroup'; +import { TraceListAPIResponse } from 'x-pack/plugins/apm/server/lib/traces/get_top_traces'; // @ts-ignore import { TraceListRequest } from '../../../store/reactReduxRequest/traceList'; -import EmptyMessage from '../../shared/EmptyMessage'; +import { EmptyMessage } from '../../shared/EmptyMessage'; import { TraceList } from './TraceList'; interface Props { @@ -37,7 +37,7 @@ export function TraceOverview(props: Props) { ) => ( + render={({ data, status }: RRRRenderResponse) => ( ) => ( + render={({ data }) => ( = ({ + query, + children, + ...rest +}) => { return ( ); -} +}; diff --git a/x-pack/plugins/apm/public/components/shared/EmptyMessage.tsx b/x-pack/plugins/apm/public/components/shared/EmptyMessage.tsx index 274d063555d14..33a0c012c16b8 100644 --- a/x-pack/plugins/apm/public/components/shared/EmptyMessage.tsx +++ b/x-pack/plugins/apm/public/components/shared/EmptyMessage.tsx @@ -4,14 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiEmptyPrompt } from '@elastic/eui'; +import { EuiEmptyPrompt, EuiEmptyPromptProps } from '@elastic/eui'; import React from 'react'; -function EmptyMessage({ +interface Props { + heading?: string; + subheading?: EuiEmptyPromptProps['body']; + hideSubheading?: boolean; +} + +const EmptyMessage: React.SFC = ({ heading = 'No data found.', subheading = 'Try another time range or reset the search filter.', hideSubheading = false -}) { +}) => { return ( ); -} +}; -// tslint:disable-next-line:no-default-export -export default EmptyMessage; +export { EmptyMessage }; diff --git a/x-pack/plugins/apm/public/components/shared/HistoryTabs/__test__/HistoryTabs.test.tsx b/x-pack/plugins/apm/public/components/shared/HistoryTabs/__test__/HistoryTabs.test.tsx new file mode 100644 index 0000000000000..89624bbbe599a --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/HistoryTabs/__test__/HistoryTabs.test.tsx @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// @ts-ignore otherwise TS complains "Module ''@elastic/eui'' has no exported member 'EuiTab'" +import { EuiTab } from '@elastic/eui'; +import { mount, ReactWrapper, shallow, ShallowWrapper } from 'enzyme'; +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import { + HistoryTabs, + HistoryTabsProps, + HistoryTabsWithoutRouter, + IHistoryTab +} from '..'; + +describe('HistoryTabs', () => { + let mockLocation: any; + let mockHistory: any; + let testTabs: IHistoryTab[]; + let testProps: HistoryTabsProps; + + beforeEach(() => { + mockLocation = { + pathname: '' + }; + mockHistory = { + push: jest.fn() + }; + + const Content = (props: { name: string }) =>
{props.name}
; + + testTabs = [ + { + name: 'One', + path: '/one', + component: () => + }, + { + name: 'Two', + path: '/two', + component: () => + }, + { + name: 'Three', + path: '/three', + component: () => + } + ]; + + testProps = ({ + location: mockLocation, + history: mockHistory, + tabs: testTabs + } as unknown) as HistoryTabsProps; + }); + + it('should render correctly', () => { + mockLocation.pathname = '/two'; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); + + const tabs: ShallowWrapper = wrapper.find(EuiTab); + expect(tabs.at(0).props().isSelected).toEqual(false); + expect(tabs.at(1).props().isSelected).toEqual(true); + expect(tabs.at(2).props().isSelected).toEqual(false); + }); + + it('should change the selected item on tab click', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.find('Content')).toMatchSnapshot(); + + wrapper + .find(EuiTab) + .at(2) + .simulate('click'); + + const tabs: ReactWrapper = wrapper.find(EuiTab); + expect(tabs.at(0).props().isSelected).toEqual(false); + expect(tabs.at(1).props().isSelected).toEqual(false); + expect(tabs.at(2).props().isSelected).toEqual(true); + + expect(wrapper.find('Content')).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/apm/public/components/shared/HistoryTabs/__test__/__snapshots__/HistoryTabs.test.tsx.snap b/x-pack/plugins/apm/public/components/shared/HistoryTabs/__test__/__snapshots__/HistoryTabs.test.tsx.snap new file mode 100644 index 0000000000000..05b72c5ab7f2e --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/HistoryTabs/__test__/__snapshots__/HistoryTabs.test.tsx.snap @@ -0,0 +1,70 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`HistoryTabs should change the selected item on tab click 1`] = ` + +
+ two +
+
+`; + +exports[`HistoryTabs should change the selected item on tab click 2`] = ` + +
+ three +
+
+`; + +exports[`HistoryTabs should render correctly 1`] = ` + + + + One + + + Two + + + Three + + + + + + +`; diff --git a/x-pack/plugins/apm/public/components/shared/HistoryTabs/index.tsx b/x-pack/plugins/apm/public/components/shared/HistoryTabs/index.tsx new file mode 100644 index 0000000000000..db4d0b4141bcd --- /dev/null +++ b/x-pack/plugins/apm/public/components/shared/HistoryTabs/index.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// @ts-ignore otherwise TS complains "Module ''@elastic/eui'' has no exported member 'EuiTab'" +import { EuiTab, EuiTabs } from '@elastic/eui'; +import React from 'react'; +import { Route, RouteComponentProps, withRouter } from 'react-router-dom'; + +export interface IHistoryTab { + path: string; + name: string; + component: React.SFC | React.ComponentClass; +} + +export interface HistoryTabsProps extends RouteComponentProps { + tabs: IHistoryTab[]; +} + +const HistoryTabsWithoutRouter = ({ + tabs, + history, + location +}: HistoryTabsProps) => { + return ( + + + {tabs.map(tab => ( + history.push({ ...location, pathname: tab.path })} + isSelected={location.pathname === tab.path} + key={`${tab.path}--${tab.name}`} + > + {tab.name} + + ))} + + {tabs.map(tab => ( + + ))} + + ); +}; + +const HistoryTabs = withRouter(HistoryTabsWithoutRouter); + +export { HistoryTabsWithoutRouter, HistoryTabs }; diff --git a/x-pack/plugins/apm/public/components/shared/ManagedTable/index.tsx b/x-pack/plugins/apm/public/components/shared/ManagedTable/index.tsx index 1b426121fce63..447c34b0189aa 100644 --- a/x-pack/plugins/apm/public/components/shared/ManagedTable/index.tsx +++ b/x-pack/plugins/apm/public/components/shared/ManagedTable/index.tsx @@ -18,7 +18,7 @@ export interface ITableColumn { align?: string; width?: string; sortable?: boolean; - render?: (value: any, item?: any) => any; + render?: (value: any, item?: any) => unknown; } export interface IManagedTableProps { @@ -31,7 +31,7 @@ export interface IManagedTableProps { field: string; direction: 'asc' | 'desc'; }; - noItemsMessage?: any; + noItemsMessage?: React.ReactNode; } export class ManagedTable extends Component { diff --git a/x-pack/plugins/apm/public/components/shared/PropertiesTable/NestedKeyValueTable.tsx b/x-pack/plugins/apm/public/components/shared/PropertiesTable/NestedKeyValueTable.tsx index 4804adbc80e4a..bea0f145bbac4 100644 --- a/x-pack/plugins/apm/public/components/shared/PropertiesTable/NestedKeyValueTable.tsx +++ b/x-pack/plugins/apm/public/components/shared/PropertiesTable/NestedKeyValueTable.tsx @@ -17,7 +17,7 @@ import { units } from '../../../style/variables'; -export type KeySorter = (data: StringMap, parentKey?: string) => string[]; +export type KeySorter = (data: StringMap, parentKey?: string) => string[]; const Table = styled.table` font-family: ${fontFamilyCode}; @@ -60,7 +60,7 @@ export function FormattedKey({ value }: { k: string; - value: any; + value: unknown; }): JSX.Element { if (value == null) { return {k}; @@ -87,7 +87,7 @@ export function NestedValue({ depth, keySorter }: { - value: any; + value: StringMap; depth: number; parentKey?: string; keySorter?: KeySorter; diff --git a/x-pack/plugins/apm/public/components/shared/Stacktrace/index.js b/x-pack/plugins/apm/public/components/shared/Stacktrace/index.js index 0bec4149914d4..b7a7f5f567f7b 100644 --- a/x-pack/plugins/apm/public/components/shared/Stacktrace/index.js +++ b/x-pack/plugins/apm/public/components/shared/Stacktrace/index.js @@ -10,7 +10,7 @@ import { isEmpty, get } from 'lodash'; import CodePreview from '../../shared/CodePreview'; import { Ellipsis } from '../../shared/Icons'; import { units, px } from '../../../style/variables'; -import EmptyMessage from '../../shared/EmptyMessage'; +import { EmptyMessage } from '../../shared/EmptyMessage'; import { EuiLink, EuiTitle } from '@elastic/eui'; const LibraryFrameToggle = styled.div` diff --git a/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/test/responseWithData.json b/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/test/responseWithData.json index 472f2b65861ca..9efb6e7d9a531 100644 --- a/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/test/responseWithData.json +++ b/x-pack/plugins/apm/public/components/shared/charts/CustomPlot/test/responseWithData.json @@ -131,13 +131,12 @@ 2547299.079999993, 4586742.89999998, 0 - ], - "avgAnomalies": {} + ] }, "tpmBuckets": [ { "key": "2xx", - "avg": "41.61538461538461", + "avg": 41.61538461538461, "values": [ 0, 0, @@ -174,7 +173,7 @@ }, { "key": "3xx", - "avg": "0", + "avg": 0, "values": [ 0, 0, @@ -211,7 +210,7 @@ }, { "key": "4xx", - "avg": "1.4615384615384615", + "avg": 1.4615384615384615, "values": [ 0, 0, @@ -248,7 +247,7 @@ }, { "key": "5xx", - "avg": "5.6923076923076925", + "avg": 5.6923076923076925, "values": [ 0, 0, diff --git a/x-pack/plugins/apm/public/services/__test__/SessionStorageMock.js b/x-pack/plugins/apm/public/services/__test__/SessionStorageMock.ts similarity index 65% rename from x-pack/plugins/apm/public/services/__test__/SessionStorageMock.js rename to x-pack/plugins/apm/public/services/__test__/SessionStorageMock.ts index c42d84b1da1d7..2423cbd11a2d0 100644 --- a/x-pack/plugins/apm/public/services/__test__/SessionStorageMock.js +++ b/x-pack/plugins/apm/public/services/__test__/SessionStorageMock.ts @@ -4,22 +4,24 @@ * you may not use this file except in compliance with the Elastic License. */ +import { StringMap } from 'x-pack/plugins/apm/typings/common'; + export class SessionStorageMock { - store = {}; + private store: StringMap = {}; - clear() { + public clear() { this.store = {}; } - getItem(key) { + public getItem(key: string) { return this.store[key] || null; } - setItem(key, value) { + public setItem(key: string, value: any) { this.store[key] = value.toString(); } - removeItem(key) { + public removeItem(key: string) { delete this.store[key]; } } diff --git a/x-pack/plugins/apm/public/services/__test__/callApi.test.js b/x-pack/plugins/apm/public/services/__test__/callApi.test.ts similarity index 98% rename from x-pack/plugins/apm/public/services/__test__/callApi.test.js rename to x-pack/plugins/apm/public/services/__test__/callApi.test.ts index 7e5439b2e6eb4..9533902fe39f8 100644 --- a/x-pack/plugins/apm/public/services/__test__/callApi.test.js +++ b/x-pack/plugins/apm/public/services/__test__/callApi.test.ts @@ -5,16 +5,17 @@ */ import * as kfetchModule from 'ui/kfetch'; -import { SessionStorageMock } from './SessionStorageMock'; import { callApi } from '../rest/callApi'; +import { SessionStorageMock } from './SessionStorageMock'; describe('callApi', () => { - let kfetchSpy; + let kfetchSpy: jest.Mock; beforeEach(() => { kfetchSpy = jest.spyOn(kfetchModule, 'kfetch').mockResolvedValue({ my_key: 'hello world' }); + // @ts-ignore global.sessionStorage = new SessionStorageMock(); }); diff --git a/x-pack/plugins/apm/public/services/kuery.js b/x-pack/plugins/apm/public/services/kuery.js index 9b9b00e2970a1..02cfd94661a86 100644 --- a/x-pack/plugins/apm/public/services/kuery.js +++ b/x-pack/plugins/apm/public/services/kuery.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { fromKueryExpression, toElasticsearchQuery } from 'ui/kuery'; +import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; import { getAutocompleteProvider } from 'ui/autocomplete_providers'; export function convertKueryToEsQuery(kuery, indexPattern) { diff --git a/x-pack/plugins/apm/public/services/rest/apm.ts b/x-pack/plugins/apm/public/services/rest/apm.ts index 3a390492f8c72..692c360980ef8 100644 --- a/x-pack/plugins/apm/public/services/rest/apm.ts +++ b/x-pack/plugins/apm/public/services/rest/apm.ts @@ -6,17 +6,20 @@ // @ts-ignore import { camelizeKeys } from 'humps'; -import { ServiceResponse } from 'x-pack/plugins/apm/server/lib/services/get_service'; -import { ServiceListItemResponse } from 'x-pack/plugins/apm/server/lib/services/get_services'; -import { IDistributionResponse } from 'x-pack/plugins/apm/server/lib/transactions/distribution/get_distribution'; +import { ServiceAPIResponse } from 'x-pack/plugins/apm/server/lib/services/get_service'; +import { ServiceListAPIResponse } from 'x-pack/plugins/apm/server/lib/services/get_services'; +import { TraceListAPIResponse } from 'x-pack/plugins/apm/server/lib/traces/get_top_traces'; +import { TraceAPIResponse } from 'x-pack/plugins/apm/server/lib/traces/get_trace'; +import { TimeSeriesAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform'; +import { ITransactionDistributionAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/distribution'; +import { TransactionListAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/get_top_transactions'; +import { TransactionAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/get_transaction'; +import { SpanListAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/spans/get_spans'; import { Span } from 'x-pack/plugins/apm/typings/Span'; import { Transaction } from 'x-pack/plugins/apm/typings/Transaction'; -import { ITransactionGroup } from 'x-pack/plugins/apm/typings/TransactionGroup'; -import { WaterfallResponse } from 'x-pack/plugins/apm/typings/waterfall'; import { IUrlParams } from '../../store/urlParams'; // @ts-ignore import { convertKueryToEsQuery } from '../kuery'; -// @ts-ignore import { callApi } from './callApi'; // @ts-ignore import { getAPMIndexPattern } from './savedObjects'; @@ -34,7 +37,7 @@ export async function loadServerStatus() { } export async function loadAgentStatus() { - return callApi({ + return callApi<{ dataFound: boolean }>({ pathname: `/api/apm/status/agent` }); } @@ -54,12 +57,8 @@ export async function getEncodedEsQuery(kuery?: string) { return encodeURIComponent(JSON.stringify(esFilterQuery)); } -export async function loadServiceList({ - start, - end, - kuery -}: IUrlParams): Promise { - return callApi({ +export async function loadServiceList({ start, end, kuery }: IUrlParams) { + return callApi({ pathname: `/api/apm/services`, query: { start, @@ -74,8 +73,8 @@ export async function loadServiceDetails({ start, end, kuery -}: IUrlParams): Promise { - return callApi({ +}: IUrlParams) { + return callApi({ pathname: `/api/apm/services/${serviceName}`, query: { start, @@ -85,12 +84,8 @@ export async function loadServiceDetails({ }); } -export async function loadTraceList({ - start, - end, - kuery -}: IUrlParams): Promise { - const groups: ITransactionGroup[] = await callApi({ +export async function loadTraceList({ start, end, kuery }: IUrlParams) { + const groups = await callApi({ pathname: '/api/apm/traces', query: { start, @@ -111,8 +106,8 @@ export async function loadTransactionList({ end, kuery, transactionType -}: IUrlParams): Promise { - const groups: ITransactionGroup[] = await callApi({ +}: IUrlParams) { + const groups = await callApi({ pathname: `/api/apm/services/${serviceName}/transactions`, query: { start, @@ -134,8 +129,8 @@ export async function loadTransactionDistribution({ end, transactionName, kuery -}: IUrlParams): Promise { - return callApi({ +}: IUrlParams) { + return callApi({ pathname: `/api/apm/services/${serviceName}/transactions/distribution`, query: { start, @@ -168,8 +163,8 @@ export async function loadSpans({ start, end, transactionId -}: IUrlParams): Promise { - const hits: Span[] = await callApi({ +}: IUrlParams) { + const hits = await callApi({ pathname: `/api/apm/services/${serviceName}/transactions/${transactionId}/spans`, query: { start, @@ -181,7 +176,7 @@ export async function loadSpans({ } export async function loadTrace({ traceId, start, end }: IUrlParams) { - const hits: WaterfallResponse = await callApi( + const hits = await callApi( { pathname: `/api/apm/traces/${traceId}`, query: { @@ -205,7 +200,7 @@ export async function loadTransaction({ traceId, kuery }: IUrlParams) { - const result: Transaction | null = await callApi( + const result = await callApi( { pathname: `/api/apm/services/${serviceName}/transactions/${transactionId}`, query: { @@ -231,7 +226,7 @@ export async function loadCharts({ transactionType, transactionName }: IUrlParams) { - return callApi({ + return callApi({ pathname: `/api/apm/services/${serviceName}/transactions/charts`, query: { start, @@ -278,7 +273,8 @@ export async function loadErrorGroupDetails({ kuery, errorGroupId }: IUrlParams) { - const res = await callApi( + // TODO: add types when error section is converted to ts + const res = await callApi( { pathname: `/api/apm/services/${serviceName}/errors/${errorGroupId}`, query: { @@ -291,7 +287,7 @@ export async function loadErrorGroupDetails({ camelcase: false } ); - const camelizedRes = camelizeKeys(res); + const camelizedRes: any = camelizeKeys(res); if (res.error.context) { camelizedRes.error.context = res.error.context; } diff --git a/x-pack/plugins/apm/public/services/rest/callApi.js b/x-pack/plugins/apm/public/services/rest/callApi.ts similarity index 81% rename from x-pack/plugins/apm/public/services/rest/callApi.js rename to x-pack/plugins/apm/public/services/rest/callApi.ts index d33bea0644d5d..3de91a3f5e98f 100644 --- a/x-pack/plugins/apm/public/services/rest/callApi.js +++ b/x-pack/plugins/apm/public/services/rest/callApi.ts @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import 'isomorphic-fetch'; import { camelizeKeys } from 'humps'; -import { kfetch } from 'ui/kfetch'; +import 'isomorphic-fetch'; import { startsWith } from 'lodash'; +import { kfetch, KFetchOptions } from 'ui/kfetch'; -function fetchOptionsWithDebug(fetchOptions) { +function fetchOptionsWithDebug(fetchOptions: KFetchOptions) { const debugEnabled = sessionStorage.getItem('apm_debug') === 'true' && startsWith(fetchOptions.pathname, '/api/apm'); @@ -27,10 +27,10 @@ function fetchOptionsWithDebug(fetchOptions) { }; } -export async function callApi( - fetchOptions, +export async function callApi( + fetchOptions: KFetchOptions, { camelcase = true, prependBasePath = true } = {} -) { +): Promise { const combinedFetchOptions = fetchOptionsWithDebug(fetchOptions); const res = await kfetch(combinedFetchOptions, { prependBasePath }); return camelcase ? camelizeKeys(res) : res; diff --git a/x-pack/plugins/apm/public/store/mockData/mockTraceList.json b/x-pack/plugins/apm/public/store/mockData/mockTraceList.json deleted file mode 100644 index 4e97a030a2621..0000000000000 --- a/x-pack/plugins/apm/public/store/mockData/mockTraceList.json +++ /dev/null @@ -1,30 +0,0 @@ -[ - { - "name": "log", - "serviceName": "flask-server", - "averageResponseTime": 1329, - "tracesPerMinute": 3201, - "impact": 70 - }, - { - "name": "products/item", - "serviceName": "client", - "averageResponseTime": 2301, - "tracesPerMinute": 5432, - "impact": 42 - }, - { - "name": "billing/payment", - "serviceName": "client", - "averageResponseTime": 789, - "tracesPerMinute": 1201, - "impact": 14 - }, - { - "name": "user/profile", - "serviceName": "client", - "averageResponseTime": 1212, - "tracesPerMinute": 904, - "impact": 92 - } -] diff --git a/x-pack/plugins/apm/public/store/reactReduxRequest/serviceList.js b/x-pack/plugins/apm/public/store/reactReduxRequest/serviceList.tsx similarity index 59% rename from x-pack/plugins/apm/public/store/reactReduxRequest/serviceList.js rename to x-pack/plugins/apm/public/store/reactReduxRequest/serviceList.tsx index 10175d3c507bb..ef5e59649b767 100644 --- a/x-pack/plugins/apm/public/store/reactReduxRequest/serviceList.js +++ b/x-pack/plugins/apm/public/store/reactReduxRequest/serviceList.tsx @@ -5,19 +5,31 @@ */ import React from 'react'; +import { Request, RRRRender, RRRRenderResponse } from 'react-redux-request'; +import { IServiceListItem } from 'x-pack/plugins/apm/server/lib/services/get_services'; import { loadServiceList } from '../../services/rest/apm'; -import { Request } from 'react-redux-request'; +import { IReduxState } from '../rootReducer'; +import { IUrlParams } from '../urlParams'; +// @ts-ignore import { createInitialDataSelector } from './helpers'; const ID = 'serviceList'; -const INITIAL_DATA = []; +const INITIAL_DATA: IServiceListItem[] = []; const withInitialData = createInitialDataSelector(INITIAL_DATA); -export function getServiceList(state) { +export function getServiceList( + state: IReduxState +): RRRRenderResponse { return withInitialData(state.reactReduxRequest[ID]); } -export function ServiceListRequest({ urlParams, render }) { +export function ServiceListRequest({ + urlParams, + render +}: { + urlParams: IUrlParams; + render: RRRRender; +}) { const { start, end, kuery } = urlParams; if (!(start && end)) { diff --git a/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDetailsCharts.js b/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDetailsCharts.tsx similarity index 64% rename from x-pack/plugins/apm/public/store/reactReduxRequest/transactionDetailsCharts.js rename to x-pack/plugins/apm/public/store/reactReduxRequest/transactionDetailsCharts.tsx index 484555bf86328..45c0d3e9f53d1 100644 --- a/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDetailsCharts.js +++ b/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDetailsCharts.tsx @@ -5,36 +5,44 @@ */ import React from 'react'; +import { Request, RRRRender } from 'react-redux-request'; import { createSelector } from 'reselect'; -import { getCharts } from '../selectors/chartSelectors'; -import { getUrlParams } from '../urlParams'; -import { Request } from 'react-redux-request'; +import { TimeSeriesAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform'; import { loadCharts } from '../../services/rest/apm'; -import { createInitialDataSelector } from './helpers'; +import { IReduxState } from '../rootReducer'; +import { getCharts } from '../selectors/chartSelectors'; +import { getUrlParams, IUrlParams } from '../urlParams'; const ID = 'transactionDetailsCharts'; const INITIAL_DATA = { totalHits: 0, dates: [], - responseTimes: {}, + responseTimes: { + avg: [], + p95: [], + p99: [] + }, tpmBuckets: [], - overallAvgDuration: null + overallAvgDuration: undefined }; -const withInitialData = createInitialDataSelector(INITIAL_DATA); - export const getTransactionDetailsCharts = createSelector( getUrlParams, - state => withInitialData(state.reactReduxRequest[ID]), - (urlParams, detailCharts) => { + (state: IReduxState) => state.reactReduxRequest[ID], + (urlParams, detailCharts = {}) => { return { ...detailCharts, - data: getCharts(urlParams, detailCharts.data) + data: getCharts(urlParams, detailCharts.data || INITIAL_DATA) }; } ); -export function TransactionDetailsChartsRequest({ urlParams, render }) { +interface Props { + urlParams: IUrlParams; + render: RRRRender; +} + +export function TransactionDetailsChartsRequest({ urlParams, render }: Props) { const { serviceName, start, diff --git a/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDistribution.tsx b/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDistribution.tsx index b52644d6881d5..bf2e803b2c46c 100644 --- a/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDistribution.tsx +++ b/x-pack/plugins/apm/public/store/reactReduxRequest/transactionDistribution.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { Request, RRRRender, RRRRenderResponse } from 'react-redux-request'; -import { IDistributionResponse } from '../../../server/lib/transactions/distribution/get_distribution'; +import { ITransactionDistributionAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/distribution'; import { loadTransactionDistribution } from '../../services/rest/apm'; import { IReduxState } from '../rootReducer'; import { IUrlParams } from '../urlParams'; @@ -19,7 +19,7 @@ const withInitialData = createInitialDataSelector(INITIAL_DATA); export function getTransactionDistribution( state: IReduxState -): RRRRenderResponse { +): RRRRenderResponse { return withInitialData(state.reactReduxRequest[ID]); } @@ -37,7 +37,7 @@ export function TransactionDistributionRequest({ render }: { urlParams: IUrlParams; - render: RRRRender; + render: RRRRender; }) { const { serviceName, start, end, transactionName, kuery } = urlParams; diff --git a/x-pack/plugins/apm/public/store/reactReduxRequest/transactionOverviewCharts.js b/x-pack/plugins/apm/public/store/reactReduxRequest/transactionOverviewCharts.tsx similarity index 69% rename from x-pack/plugins/apm/public/store/reactReduxRequest/transactionOverviewCharts.js rename to x-pack/plugins/apm/public/store/reactReduxRequest/transactionOverviewCharts.tsx index a5313c7efda10..87ecf58e0e338 100644 --- a/x-pack/plugins/apm/public/store/reactReduxRequest/transactionOverviewCharts.js +++ b/x-pack/plugins/apm/public/store/reactReduxRequest/transactionOverviewCharts.tsx @@ -4,26 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ +import { get, isEmpty } from 'lodash'; import React from 'react'; +import { Request, RRRRender } from 'react-redux-request'; import { createSelector } from 'reselect'; -import { get, isEmpty } from 'lodash'; -import { getCharts } from '../selectors/chartSelectors'; -import { getUrlParams } from '../urlParams'; -import { Request } from 'react-redux-request'; +import { TimeSeriesAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform'; import { loadCharts } from '../../services/rest/apm'; +import { IReduxState } from '../rootReducer'; +import { getCharts } from '../selectors/chartSelectors'; +import { getUrlParams, IUrlParams } from '../urlParams'; const ID = 'transactionOverviewCharts'; const INITIAL_DATA = { totalHits: 0, dates: [], - responseTimes: {}, + responseTimes: { + avg: [], + p95: [], + p99: [] + }, tpmBuckets: [], - overallAvgDuration: null + overallAvgDuration: undefined }; export const getTransactionOverviewCharts = createSelector( getUrlParams, - state => state.reactReduxRequest[ID], + (state: IReduxState) => state.reactReduxRequest[ID], (urlParams, overviewCharts = {}) => { return { ...overviewCharts, @@ -32,7 +38,7 @@ export const getTransactionOverviewCharts = createSelector( } ); -export function hasDynamicBaseline(state) { +export function hasDynamicBaseline(state: IReduxState) { return !isEmpty( get( state, @@ -41,7 +47,12 @@ export function hasDynamicBaseline(state) { ); } -export function TransactionOverviewChartsRequest({ urlParams, render }) { +interface Props { + urlParams: IUrlParams; + render: RRRRender; +} + +export function TransactionOverviewChartsRequest({ urlParams, render }: Props) { const { serviceName, start, end, transactionType, kuery } = urlParams; if (!(serviceName && start && end && transactionType)) { diff --git a/x-pack/plugins/apm/public/store/reactReduxRequest/waterfallV2.tsx b/x-pack/plugins/apm/public/store/reactReduxRequest/waterfallV2.tsx index 43e4f65c91d4b..996c80bb8a055 100644 --- a/x-pack/plugins/apm/public/store/reactReduxRequest/waterfallV2.tsx +++ b/x-pack/plugins/apm/public/store/reactReduxRequest/waterfallV2.tsx @@ -8,8 +8,8 @@ import { get } from 'lodash'; import React from 'react'; import { Request, RRRRender } from 'react-redux-request'; import { TRACE_ID } from 'x-pack/plugins/apm/common/constants'; +import { TraceAPIResponse } from 'x-pack/plugins/apm/server/lib/traces/get_trace'; import { Transaction } from 'x-pack/plugins/apm/typings/Transaction'; -import { WaterfallResponse } from 'x-pack/plugins/apm/typings/waterfall'; import { getWaterfall, IWaterfall @@ -36,7 +36,7 @@ export function WaterfallV2Request({ urlParams, transaction, render }: Props) { } return ( - + id={ID} fn={loadTrace} args={[{ traceId, start, end }]} diff --git a/x-pack/plugins/apm/public/store/selectors/__tests__/__snapshots__/chartSelectors.test.js.snap b/x-pack/plugins/apm/public/store/selectors/__tests__/__snapshots__/chartSelectors.test.ts.snap similarity index 100% rename from x-pack/plugins/apm/public/store/selectors/__tests__/__snapshots__/chartSelectors.test.js.snap rename to x-pack/plugins/apm/public/store/selectors/__tests__/__snapshots__/chartSelectors.test.ts.snap diff --git a/x-pack/plugins/apm/public/store/selectors/__tests__/chartSelectors.test.js b/x-pack/plugins/apm/public/store/selectors/__tests__/chartSelectors.test.ts similarity index 79% rename from x-pack/plugins/apm/public/store/selectors/__tests__/chartSelectors.test.js rename to x-pack/plugins/apm/public/store/selectors/__tests__/chartSelectors.test.ts index 1850fd990c957..608a87ee48f2c 100644 --- a/x-pack/plugins/apm/public/store/selectors/__tests__/chartSelectors.test.js +++ b/x-pack/plugins/apm/public/store/selectors/__tests__/chartSelectors.test.ts @@ -4,14 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ +import { AvgAnomalyBucket } from 'x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_anomaly_aggs/transform'; +import { TimeSeriesAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform'; import { + getAnomalyBoundaryValues, getAnomalyScoreValues, getResponseTimeSeries, - getTpmSeries, - getAnomalyBoundaryValues + getTpmSeries } from '../chartSelectors'; - -import anomalyData from './mockData/anomalyData.json'; +import { anomalyData } from './mockData/anomalyData'; describe('chartSelectors', () => { describe('getAnomalyScoreValues', () => { @@ -39,7 +40,7 @@ describe('chartSelectors', () => { { anomalyScore: 0 } - ]; + ] as AvgAnomalyBucket[]; expect(getAnomalyScoreValues(dates, buckets, 1000)).toEqual([ { x: 1000, y: 1 }, @@ -58,11 +59,10 @@ describe('chartSelectors', () => { responseTimes: { avg: [100, 200, 150, 250, 100, 50], p95: [200, 300, 250, 350, 200, 150], - p99: [300, 400, 350, 450, 100, 50], - avgAnomalies: {} + p99: [300, 400, 350, 450, 100, 50] }, overallAvgDuration: 200 - }; + } as TimeSeriesAPIResponse; it('should match snapshot', () => { expect(getResponseTimeSeries(chartsData)).toMatchSnapshot(); @@ -93,7 +93,7 @@ describe('chartSelectors', () => { values: [0, 1, 2, 1, 0, 2] } ] - }; + } as TimeSeriesAPIResponse; const transactionType = 'MyTransactionType'; @@ -104,10 +104,10 @@ describe('chartSelectors', () => { describe('getAnomalyBoundaryValues', () => { const { dates, buckets } = anomalyData; - const bucketSpan = 240000; + const bucketSize = 240000; it('should return correct buckets', () => { - expect(getAnomalyBoundaryValues(dates, buckets, bucketSpan)).toEqual([ + expect(getAnomalyBoundaryValues(dates, buckets, bucketSize)).toEqual([ { x: 1530614880000, y: 54799, y0: 15669 }, { x: 1530615060000, y: 49874, y0: 17808 }, { x: 1530615300000, y: 49421, y0: 18012 }, @@ -120,18 +120,18 @@ describe('chartSelectors', () => { ]); }); - it('should extend the last bucket with a size of bucketSpan', () => { + it('should extend the last bucket with a size of bucketSize', () => { const [lastBucket, secondLastBuckets] = getAnomalyBoundaryValues( dates, buckets, - bucketSpan + bucketSize ).reverse(); expect(secondLastBuckets.y).toBe(lastBucket.y); expect(secondLastBuckets.y0).toBe(lastBucket.y0); - expect(lastBucket.x - secondLastBuckets.x).toBeLessThanOrEqual( - bucketSpan - ); + expect( + (lastBucket.x as number) - (secondLastBuckets.x as number) + ).toBeLessThanOrEqual(bucketSize); }); }); }); diff --git a/x-pack/plugins/apm/public/store/selectors/__tests__/mockData/anomalyData.json b/x-pack/plugins/apm/public/store/selectors/__tests__/mockData/anomalyData.json deleted file mode 100644 index d91fc736801a7..0000000000000 --- a/x-pack/plugins/apm/public/store/selectors/__tests__/mockData/anomalyData.json +++ /dev/null @@ -1,186 +0,0 @@ -{ - "dates": [ - 1530614880000, - 1530614940000, - 1530615000000, - 1530615060000, - 1530615120000, - 1530615180000, - 1530615240000, - 1530615300000, - 1530615360000, - 1530615420000, - 1530615480000, - 1530615540000, - 1530615600000, - 1530615660000, - 1530615720000, - 1530615780000, - 1530615840000, - 1530615900000, - 1530615960000, - 1530616020000, - 1530616080000, - 1530616140000, - 1530616200000, - 1530616260000, - 1530616320000, - 1530616380000, - 1530616440000, - 1530616500000, - 1530616560000, - 1530616620000 - ], - "buckets": [ - { - "anomalyScore": null, - "lower": 15669, - "upper": 54799 - }, - { - "anomalyScore": null, - "lower": null, - "upper": null - }, - { - "anomalyScore": null, - "lower": null, - "upper": null - }, - { - "anomalyScore": 0, - "lower": 17808, - "upper": 49874 - }, - { - "anomalyScore": null, - "lower": null, - "upper": null - }, - { - "anomalyScore": null, - "lower": null, - "upper": null - }, - { - "anomalyScore": null, - "lower": null, - "upper": null - }, - { - "anomalyScore": 0, - "lower": 18012, - "upper": 49421 - }, - { - "anomalyScore": null, - "lower": null, - "upper": null - }, - { - "anomalyScore": null, - "lower": null, - "upper": null - }, - { - "anomalyScore": null, - "lower": null, - "upper": null - }, - { - "anomalyScore": 0, - "lower": 17889, - "upper": 49654 - }, - { - "anomalyScore": null, - "lower": null, - "upper": null - }, - { - "anomalyScore": null, - "lower": null, - "upper": null - }, - { - "anomalyScore": null, - "lower": null, - "upper": null - }, - { - "anomalyScore": 0, - "lower": 17713, - "upper": 50026 - }, - { - "anomalyScore": null, - "lower": null, - "upper": null - }, - { - "anomalyScore": null, - "lower": null, - "upper": null - }, - { - "anomalyScore": null, - "lower": null, - "upper": null - }, - { - "anomalyScore": 0, - "lower": 18044, - "upper": 49371 - }, - { - "anomalyScore": null, - "lower": null, - "upper": null - }, - { - "anomalyScore": null, - "lower": null, - "upper": null - }, - { - "anomalyScore": null, - "lower": null, - "upper": null - }, - { - "anomalyScore": 0, - "lower": 17713, - "upper": 50110 - }, - { - "anomalyScore": null, - "lower": null, - "upper": null - }, - { - "anomalyScore": null, - "lower": null, - "upper": null - }, - { - "anomalyScore": null, - "lower": null, - "upper": null - }, - { - "anomalyScore": 0, - "lower": 17582, - "upper": 50419 - }, - { - "anomalyScore": null, - "lower": null, - "upper": null - }, - { - "anomalyScore": null, - "lower": null, - "upper": null - } - ] -} diff --git a/x-pack/plugins/apm/public/store/selectors/__tests__/mockData/anomalyData.ts b/x-pack/plugins/apm/public/store/selectors/__tests__/mockData/anomalyData.ts new file mode 100644 index 0000000000000..af864e69dc72a --- /dev/null +++ b/x-pack/plugins/apm/public/store/selectors/__tests__/mockData/anomalyData.ts @@ -0,0 +1,192 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const anomalyData = { + dates: [ + 1530614880000, + 1530614940000, + 1530615000000, + 1530615060000, + 1530615120000, + 1530615180000, + 1530615240000, + 1530615300000, + 1530615360000, + 1530615420000, + 1530615480000, + 1530615540000, + 1530615600000, + 1530615660000, + 1530615720000, + 1530615780000, + 1530615840000, + 1530615900000, + 1530615960000, + 1530616020000, + 1530616080000, + 1530616140000, + 1530616200000, + 1530616260000, + 1530616320000, + 1530616380000, + 1530616440000, + 1530616500000, + 1530616560000, + 1530616620000 + ], + buckets: [ + { + anomalyScore: null, + lower: 15669, + upper: 54799 + }, + { + anomalyScore: null, + lower: null, + upper: null + }, + { + anomalyScore: null, + lower: null, + upper: null + }, + { + anomalyScore: 0, + lower: 17808, + upper: 49874 + }, + { + anomalyScore: null, + lower: null, + upper: null + }, + { + anomalyScore: null, + lower: null, + upper: null + }, + { + anomalyScore: null, + lower: null, + upper: null + }, + { + anomalyScore: 0, + lower: 18012, + upper: 49421 + }, + { + anomalyScore: null, + lower: null, + upper: null + }, + { + anomalyScore: null, + lower: null, + upper: null + }, + { + anomalyScore: null, + lower: null, + upper: null + }, + { + anomalyScore: 0, + lower: 17889, + upper: 49654 + }, + { + anomalyScore: null, + lower: null, + upper: null + }, + { + anomalyScore: null, + lower: null, + upper: null + }, + { + anomalyScore: null, + lower: null, + upper: null + }, + { + anomalyScore: 0, + lower: 17713, + upper: 50026 + }, + { + anomalyScore: null, + lower: null, + upper: null + }, + { + anomalyScore: null, + lower: null, + upper: null + }, + { + anomalyScore: null, + lower: null, + upper: null + }, + { + anomalyScore: 0, + lower: 18044, + upper: 49371 + }, + { + anomalyScore: null, + lower: null, + upper: null + }, + { + anomalyScore: null, + lower: null, + upper: null + }, + { + anomalyScore: null, + lower: null, + upper: null + }, + { + anomalyScore: 0, + lower: 17713, + upper: 50110 + }, + { + anomalyScore: null, + lower: null, + upper: null + }, + { + anomalyScore: null, + lower: null, + upper: null + }, + { + anomalyScore: null, + lower: null, + upper: null + }, + { + anomalyScore: 0, + lower: 17582, + upper: 50419 + }, + { + anomalyScore: null, + lower: null, + upper: null + }, + { + anomalyScore: null, + lower: null, + upper: null + } + ] +}; diff --git a/x-pack/plugins/apm/public/store/selectors/chartSelectors.js b/x-pack/plugins/apm/public/store/selectors/chartSelectors.ts similarity index 58% rename from x-pack/plugins/apm/public/store/selectors/chartSelectors.js rename to x-pack/plugins/apm/public/store/selectors/chartSelectors.ts index 534da52262d16..d9670364131aa 100644 --- a/x-pack/plugins/apm/public/store/selectors/chartSelectors.js +++ b/x-pack/plugins/apm/public/store/selectors/chartSelectors.ts @@ -5,10 +5,23 @@ */ import d3 from 'd3'; -import { last, zipObject, difference, memoize, get, isEmpty } from 'lodash'; -import { colors } from '../../style/variables'; -import { asMillis, asDecimal, tpmUnit } from '../../utils/formatters'; +import { difference, last, memoize, zipObject } from 'lodash'; import { rgba } from 'polished'; +import { AvgAnomalyBucket } from 'x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_anomaly_aggs/transform'; +import { TimeSeriesAPIResponse } from 'x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform'; +import { StringMap } from 'x-pack/plugins/apm/typings/common'; +import { colors } from '../../style/variables'; +import { asDecimal, asMillis, tpmUnit } from '../../utils/formatters'; +import { IUrlParams } from '../urlParams'; + +interface Coordinate { + x: number; + y?: number | null; +} + +interface BoundaryCoordinate extends Coordinate { + y0: number | null; +} export const getEmptySerie = memoize( (start = Date.now() - 3600000, end = Date.now()) => { @@ -26,10 +39,13 @@ export const getEmptySerie = memoize( } ]; }, - (...args) => args.join('_') + (start: number, end: number) => [start, end].join('_') ); -export function getCharts(urlParams, charts) { +export function getCharts( + urlParams: IUrlParams, + charts: TimeSeriesAPIResponse +) { const { start, end, transactionType } = urlParams; const noHits = charts.totalHits === 0; const tpmSeries = noHits @@ -47,11 +63,23 @@ export function getCharts(urlParams, charts) { }; } -export function getResponseTimeSeries(chartsData) { +interface TimeSerie { + title: string; + titleShort?: string; + hideLegend?: boolean; + hideTooltipValue?: boolean; + data: Coordinate[]; + legendValue?: string; + type: string; + color: string; + areaColor?: string; +} + +export function getResponseTimeSeries(chartsData: TimeSeriesAPIResponse) { const { dates, overallAvgDuration } = chartsData; const { avg, p95, p99, avgAnomalies } = chartsData.responseTimes; - const series = [ + const series: TimeSerie[] = [ { title: 'Avg.', data: getChartValues(dates, avg), @@ -75,7 +103,7 @@ export function getResponseTimeSeries(chartsData) { } ]; - if (!isEmpty(avgAnomalies.buckets)) { + if (avgAnomalies) { // insert after Avg. serie series.splice(1, 0, { title: 'Anomaly Boundaries', @@ -84,7 +112,7 @@ export function getResponseTimeSeries(chartsData) { data: getAnomalyBoundaryValues( dates, avgAnomalies.buckets, - avgAnomalies.bucketSpanAsMillis + avgAnomalies.bucketSizeAsMillis ), type: 'area', color: 'none', @@ -98,7 +126,7 @@ export function getResponseTimeSeries(chartsData) { data: getAnomalyScoreValues( dates, avgAnomalies.buckets, - avgAnomalies.bucketSpanAsMillis + avgAnomalies.bucketSizeAsMillis ), type: 'areaMaxHeight', color: 'none', @@ -109,10 +137,14 @@ export function getResponseTimeSeries(chartsData) { return series; } -export function getTpmSeries(chartsData, transactionType) { +export function getTpmSeries( + chartsData: TimeSeriesAPIResponse, + transactionType?: string +) { const { dates, tpmBuckets } = chartsData; - const getColor = getColorByKey(tpmBuckets.map(({ key }) => key)); - const getTpmLegendTitle = bucketKey => { + const bucketKeys = tpmBuckets.map(({ key }) => key); + const getColor = getColorByKey(bucketKeys); + const getTpmLegendTitle = (bucketKey: string) => { // hide legend text for transactions without "result" if (bucketKey === 'transaction_result_missing') { return ''; @@ -125,15 +157,15 @@ export function getTpmSeries(chartsData, transactionType) { return { title: getTpmLegendTitle(bucket.key), data: getChartValues(dates, bucket.values), - legendValue: `${asDecimal(bucket.avg)} ${tpmUnit(transactionType)}`, + legendValue: `${asDecimal(bucket.avg)} ${tpmUnit(transactionType || '')}`, type: 'line', color: getColor(bucket.key) }; }); } -function getColorByKey(keys) { - const assignedColors = { +function getColorByKey(keys: string[]) { + const assignedColors: StringMap = { 'HTTP 2xx': colors.apmGreen, 'HTTP 3xx': colors.apmYellow, 'HTTP 4xx': colors.apmOrange, @@ -141,7 +173,7 @@ function getColorByKey(keys) { }; const unknownKeys = difference(keys, Object.keys(assignedColors)); - const unassignedColors = zipObject(unknownKeys, [ + const unassignedColors: StringMap = zipObject(unknownKeys, [ colors.apmBlue, colors.apmPurple, colors.apmPink, @@ -150,10 +182,13 @@ function getColorByKey(keys) { colors.apmBrown ]); - return key => assignedColors[key] || unassignedColors[key]; + return (key: string) => assignedColors[key] || unassignedColors[key]; } -function getChartValues(dates = [], buckets = []) { +function getChartValues( + dates: number[] = [], + buckets: Array = [] +) { return dates.map((x, i) => ({ x, y: buckets[i] @@ -161,27 +196,32 @@ function getChartValues(dates = [], buckets = []) { } export function getAnomalyScoreValues( - dates = [], - buckets = [], - bucketSpanAsMillis + dates: number[] = [], + buckets: AvgAnomalyBucket[] = [], + bucketSizeAsMillis: number ) { const ANOMALY_THRESHOLD = 75; - const getX = (currentX, i) => currentX + bucketSpanAsMillis * i; + const getX = (currentX: number, i: number) => + currentX + bucketSizeAsMillis * i; return dates - .map((x, i) => { - const { anomalyScore } = buckets[i] || {}; + .map((date, i) => { + const { anomalyScore } = buckets[i]; return { - x, + x: date, anomalyScore }; }) - .filter(p => p.anomalyScore > ANOMALY_THRESHOLD) - .reduce((acc, p, i, points) => { - acc.push({ x: p.x, y: 1 }); - const { x: nextX } = points[i + 1] || {}; + .filter(p => { + const res = + p && p.anomalyScore != null && p.anomalyScore > ANOMALY_THRESHOLD; + return res; + }) + .reduce((acc, p, i, points) => { + const nextPoint = points[i + 1] || {}; const endX = getX(p.x, 1); - if (nextX == null || nextX > endX) { + acc.push({ x: p.x, y: 1 }); + if (nextPoint.x == null || nextPoint.x > endX) { acc.push( { x: endX, @@ -198,26 +238,29 @@ export function getAnomalyScoreValues( } export function getAnomalyBoundaryValues( - dates = [], - buckets = [], - bucketSpanAsMillis + dates: number[] = [], + buckets: AvgAnomalyBucket[] = [], + bucketSizeAsMillis: number ) { const lastX = last(dates); return dates - .map((x, i) => ({ - x, - y0: get(buckets[i], 'lower'), - y: get(buckets[i], 'upper') - })) - .filter(point => point.y != null) - .reduce((acc, p, i, points) => { + .map((date, i) => { + const bucket = buckets[i]; + return { + x: date, + y0: bucket.lower, + y: bucket.upper + }; + }) + .filter(p => p.y != null) + .reduce((acc, p, i, points) => { const isLast = last(points) === p; acc.push(p); if (isLast) { acc.push({ ...p, - x: Math.min(p.x + bucketSpanAsMillis, lastX) // avoid going beyond the last date + x: Math.min(p.x + bucketSizeAsMillis, lastX) // avoid going beyond the last date }); } return acc; diff --git a/x-pack/plugins/apm/public/store/urlParams.ts b/x-pack/plugins/apm/public/store/urlParams.ts index cedcbfe337fee..b0850aa2cf880 100644 --- a/x-pack/plugins/apm/public/store/urlParams.ts +++ b/x-pack/plugins/apm/public/store/urlParams.ts @@ -157,13 +157,13 @@ export const getUrlParams = createSelector( ); export interface IUrlParams { - end?: string; + end?: number; errorGroupId?: string; flyoutDetailTab?: string; detailTab?: string; kuery?: string; serviceName?: string; - start?: string; + start?: number; traceId?: string; transactionId?: string; transactionName?: string; diff --git a/x-pack/plugins/apm/public/utils/url.tsx b/x-pack/plugins/apm/public/utils/url.tsx index 6ae3fcb643a6c..f2b0dfc9a34f3 100644 --- a/x-pack/plugins/apm/public/utils/url.tsx +++ b/x-pack/plugins/apm/public/utils/url.tsx @@ -30,15 +30,14 @@ interface ViewMlJobArgs { serviceName: string; transactionType: string; location: any; - children?: any; } -export function ViewMLJob({ +export const ViewMLJob: React.SFC = ({ serviceName, transactionType, location, children = 'View Job' -}: ViewMlJobArgs) { +}) => { const pathname = '/app/ml'; const hash = '/timeseriesexplorer'; const jobId = `${serviceName}-${transactionType}-high_mean_response_time`; @@ -59,7 +58,7 @@ export function ViewMLJob({ children={children} /> ); -} +}; export function toQuery(search?: string): StringMap { return search ? qs.parse(search.slice(1)) : {}; diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/__test__/apm_telemetry.test.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/__test__/apm_telemetry.test.ts new file mode 100644 index 0000000000000..9da683b48b618 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/__test__/apm_telemetry.test.ts @@ -0,0 +1,152 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + AgentName, + APM_TELEMETRY_DOC_ID, + ApmTelemetry, + createApmTelementry, + getSavedObjectsClient, + storeApmTelemetry +} from '../apm_telemetry'; + +describe('apm_telemetry', () => { + describe('createApmTelementry', () => { + it('should create a ApmTelemetry object with boolean flag and frequency map of the given list of AgentNames', () => { + const apmTelemetry = createApmTelementry([ + AgentName.GoLang, + AgentName.NodeJs, + AgentName.GoLang, + AgentName.JsBase + ]); + expect(apmTelemetry.has_any_services).toBe(true); + expect(apmTelemetry.services_per_agent).toMatchObject({ + [AgentName.GoLang]: 2, + [AgentName.NodeJs]: 1, + [AgentName.JsBase]: 1 + }); + }); + it('should ignore undefined or unknown AgentName values', () => { + const apmTelemetry = createApmTelementry([ + AgentName.GoLang, + AgentName.NodeJs, + AgentName.GoLang, + AgentName.JsBase, + 'example-platform' as any, + undefined as any + ]); + expect(apmTelemetry.services_per_agent).toMatchObject({ + [AgentName.GoLang]: 2, + [AgentName.NodeJs]: 1, + [AgentName.JsBase]: 1 + }); + }); + }); + + describe('storeApmTelemetry', () => { + let server: any; + let apmTelemetry: ApmTelemetry; + let savedObjectsClientInstance: any; + + beforeEach(() => { + savedObjectsClientInstance = { create: jest.fn() }; + const callWithInternalUser = jest.fn(); + const internalRepository = jest.fn(); + server = { + savedObjects: { + SavedObjectsClient: jest.fn(() => savedObjectsClientInstance), + getSavedObjectsRepository: jest.fn(() => internalRepository) + }, + plugins: { + elasticsearch: { + getCluster: jest.fn(() => ({ callWithInternalUser })) + } + } + }; + apmTelemetry = { + has_any_services: true, + services_per_agent: { + [AgentName.GoLang]: 2, + [AgentName.NodeJs]: 1, + [AgentName.JsBase]: 1 + } + }; + }); + + it('should call savedObjectsClient create with the given ApmTelemetry object', () => { + storeApmTelemetry(server, apmTelemetry); + expect(savedObjectsClientInstance.create.mock.calls[0][1]).toBe( + apmTelemetry + ); + }); + + it('should call savedObjectsClient create with the apm-telemetry document type and ID', () => { + storeApmTelemetry(server, apmTelemetry); + expect(savedObjectsClientInstance.create.mock.calls[0][0]).toBe( + 'apm-telemetry' + ); + expect(savedObjectsClientInstance.create.mock.calls[0][2].id).toBe( + APM_TELEMETRY_DOC_ID + ); + }); + + it('should call savedObjectsClient create with overwrite: true', () => { + storeApmTelemetry(server, apmTelemetry); + expect(savedObjectsClientInstance.create.mock.calls[0][2].overwrite).toBe( + true + ); + }); + }); + + describe('getSavedObjectsClient', () => { + let server: any; + let savedObjectsClientInstance: any; + let callWithInternalUser: any; + let internalRepository: any; + + beforeEach(() => { + savedObjectsClientInstance = { create: jest.fn() }; + callWithInternalUser = jest.fn(); + internalRepository = jest.fn(); + server = { + savedObjects: { + SavedObjectsClient: jest.fn(() => savedObjectsClientInstance), + getSavedObjectsRepository: jest.fn(() => internalRepository) + }, + plugins: { + elasticsearch: { + getCluster: jest.fn(() => ({ callWithInternalUser })) + } + } + }; + }); + + it('should use internal user "admin"', () => { + getSavedObjectsClient(server); + + expect(server.plugins.elasticsearch.getCluster).toHaveBeenCalledWith( + 'admin' + ); + }); + + it('should call getSavedObjectsRepository with a cluster using the internal user context', () => { + getSavedObjectsClient(server); + + expect( + server.savedObjects.getSavedObjectsRepository + ).toHaveBeenCalledWith(callWithInternalUser); + }); + + it('should return a SavedObjectsClient initialized with the saved objects internal repository', () => { + const result = getSavedObjectsClient(server); + + expect(result).toBe(savedObjectsClientInstance); + expect(server.savedObjects.SavedObjectsClient).toHaveBeenCalledWith( + internalRepository + ); + }); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/apm_telemetry.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/apm_telemetry.ts new file mode 100644 index 0000000000000..f136030dd5652 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/apm_telemetry.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Server } from 'hapi'; +import { countBy } from 'lodash'; + +// Support telemetry for additional agent types by appending definitions in +// mappings.json and the AgentName enum. + +export enum AgentName { + Python = 'python', + Java = 'java', + NodeJs = 'nodejs', + JsBase = 'js-base', + Ruby = 'ruby', + GoLang = 'go' +} + +export interface ApmTelemetry { + has_any_services: boolean; + services_per_agent: { [agentName in AgentName]?: number }; +} + +export const APM_TELEMETRY_DOC_ID = 'apm-telemetry'; + +export function createApmTelementry( + agentNames: AgentName[] = [] +): ApmTelemetry { + const validAgentNames = agentNames.filter(agentName => + Object.values(AgentName).includes(agentName) + ); + return { + has_any_services: validAgentNames.length > 0, + services_per_agent: countBy(validAgentNames) + }; +} + +export function storeApmTelemetry( + server: Server, + apmTelemetry: ApmTelemetry +): void { + const savedObjectsClient = getSavedObjectsClient(server); + savedObjectsClient.create('apm-telemetry', apmTelemetry, { + id: APM_TELEMETRY_DOC_ID, + overwrite: true + }); +} + +export function getSavedObjectsClient(server: Server): any { + const { SavedObjectsClient, getSavedObjectsRepository } = server.savedObjects; + const { callWithInternalUser } = server.plugins.elasticsearch.getCluster( + 'admin' + ); + const internalRepository = getSavedObjectsRepository(callWithInternalUser); + return new SavedObjectsClient(internalRepository); +} diff --git a/x-pack/plugins/infra/server/graphql/capabilities/index.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/index.ts similarity index 54% rename from x-pack/plugins/infra/server/graphql/capabilities/index.ts rename to x-pack/plugins/apm/server/lib/apm_telemetry/index.ts index 3f6f9541eda33..96325952fb1a7 100644 --- a/x-pack/plugins/infra/server/graphql/capabilities/index.ts +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/index.ts @@ -4,5 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -export { createCapabilitiesResolvers } from './resolvers'; -export { capabilitiesSchema } from './schema.gql'; +export { + ApmTelemetry, + AgentName, + storeApmTelemetry, + createApmTelementry, + APM_TELEMETRY_DOC_ID +} from './apm_telemetry'; +export { makeApmUsageCollector } from './make_apm_usage_collector'; diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/make_apm_usage_collector.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/make_apm_usage_collector.ts new file mode 100644 index 0000000000000..d09ea4cdab51e --- /dev/null +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/make_apm_usage_collector.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Server } from 'hapi'; +import { + APM_TELEMETRY_DOC_ID, + ApmTelemetry, + createApmTelementry, + getSavedObjectsClient +} from './apm_telemetry'; + +// TODO this type should be defined by the platform +interface KibanaHapiServer extends Server { + usage: { + collectorSet: { + makeUsageCollector: any; + register: any; + }; + }; +} + +export function makeApmUsageCollector(server: KibanaHapiServer): void { + const apmUsageCollector = server.usage.collectorSet.makeUsageCollector({ + type: 'apm', + fetch: async (): Promise => { + const savedObjectsClient = getSavedObjectsClient(server); + try { + const apmTelemetrySavedObject = await savedObjectsClient.get( + 'apm-telemetry', + APM_TELEMETRY_DOC_ID + ); + return apmTelemetrySavedObject.attributes; + } catch (err) { + return createApmTelementry(); + } + } + }); + server.usage.collectorSet.register(apmUsageCollector); +} diff --git a/x-pack/plugins/apm/server/lib/helpers/get_bucket_size/index.js b/x-pack/plugins/apm/server/lib/helpers/get_bucket_size/index.ts similarity index 58% rename from x-pack/plugins/apm/server/lib/helpers/get_bucket_size/index.js rename to x-pack/plugins/apm/server/lib/helpers/get_bucket_size/index.ts index a7e4319243bc9..5ec0c2b7b2d96 100644 --- a/x-pack/plugins/apm/server/lib/helpers/get_bucket_size/index.js +++ b/x-pack/plugins/apm/server/lib/helpers/get_bucket_size/index.ts @@ -4,24 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ -import { calculateAuto } from './calculate_auto'; import moment from 'moment'; +// @ts-ignore +import { calculateAuto } from './calculate_auto'; +// @ts-ignore import { unitToSeconds } from './unit_to_seconds'; -export function getBucketSize(start, end, interval) { - const duration = moment.duration(end - start, 'ms'); - let bucketSize = calculateAuto.near(100, duration).asSeconds(); - if (bucketSize < 1) bucketSize = 1; // don't go too small - let intervalString = `${bucketSize}s`; +export function getBucketSize(start: number, end: number, interval: string) { + const duration = moment.duration(end - start, 'ms'); + const bucketSize = Math.max(calculateAuto.near(100, duration).asSeconds(), 1); + const intervalString = `${bucketSize}s`; const matches = interval && interval.match(/^([\d]+)([shmdwMy]|ms)$/); - let minBucketSize = 0; - if (matches) { - minBucketSize = Number(matches[1]) * unitToSeconds(matches[2]); - } + const minBucketSize = matches + ? Number(matches[1]) * unitToSeconds(matches[2]) + : 0; if (bucketSize < minBucketSize) { - bucketSize = minBucketSize; - intervalString = interval; + return { + bucketSize: minBucketSize, + intervalString: interval + }; } return { bucketSize, intervalString }; diff --git a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts index 70773755c2c78..7d46d07865ba9 100644 --- a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts +++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts @@ -5,7 +5,7 @@ */ /* tslint:disable no-console */ -import { SearchParams, SearchResponse } from 'elasticsearch'; +import { AggregationSearchResponse, SearchParams } from 'elasticsearch'; import { Request } from 'hapi'; import moment from 'moment'; @@ -14,7 +14,7 @@ function decodeEsQuery(esQuery?: string): object { } interface KibanaConfig { - get: (key: string) => any; + get: (key: string) => T; } // Extend the defaults with the plugins and server methods we need. @@ -28,13 +28,16 @@ declare module 'hapi' { } } -type Client = (type: string, params: SearchParams) => SearchResponse; +export type ESClient = ( + type: string, + params: SearchParams +) => Promise>; -export interface Setup { +export interface Setup { start: number; end: number; - esFilterQuery: any; - client: Client; + esFilterQuery?: any; + client: ESClient; config: KibanaConfig; } @@ -49,7 +52,10 @@ export function setupRequest(req: Request) { const query = (req.query as unknown) as APMRequestQuery; const cluster = req.server.plugins.elasticsearch.getCluster('data'); - function client(type: string, params: SearchParams): SearchResponse { + function client( + type: string, + params: SearchParams + ): AggregationSearchResponse { if (query._debug) { console.log(`DEBUG ES QUERY:`); console.log( diff --git a/x-pack/plugins/apm/server/lib/helpers/transaction_group_query.ts b/x-pack/plugins/apm/server/lib/helpers/transaction_group_query.ts deleted file mode 100644 index db7b65c646870..0000000000000 --- a/x-pack/plugins/apm/server/lib/helpers/transaction_group_query.ts +++ /dev/null @@ -1,95 +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 moment from 'moment'; -import { - TRANSACTION_DURATION, - TRANSACTION_NAME -} from '../../../common/constants'; -import { Transaction } from '../../../typings/Transaction'; -import { ITransactionGroup } from '../../../typings/TransactionGroup'; - -export interface ITransactionGroupBucket { - key: string; - doc_count: number; - avg: { - value: number; - }; - p95: { - values: { - '95.0': number; - }; - }; - sample: { - hits: { - hits: Array<{ - _source: Transaction; - }>; - }; - }; -} - -export const TRANSACTION_GROUP_AGGREGATES = { - transactions: { - terms: { - field: `${TRANSACTION_NAME}.keyword`, - order: { avg: 'desc' }, - size: 100 - }, - aggs: { - sample: { - top_hits: { - size: 1, - sort: [{ '@timestamp': { order: 'desc' } }] - } - }, - avg: { avg: { field: TRANSACTION_DURATION } }, - p95: { percentiles: { field: TRANSACTION_DURATION, percents: [95] } } - } - } -}; - -function calculateRelativeImpacts(results: ITransactionGroup[]) { - const values = results.map(({ impact }) => impact); - const max = Math.max(...values); - const min = Math.min(...values); - - return results.map(bucket => ({ - ...bucket, - impact: ((bucket.impact - min) / (max - min)) * 100 - })); -} - -export function prepareTransactionGroups({ - buckets, - start, - end -}: { - buckets: ITransactionGroupBucket[]; - start: number; - end: number; -}) { - const duration = moment.duration(end - start); - const minutes = duration.asMinutes(); - - const results = buckets.map((bucket: ITransactionGroupBucket) => { - const averageResponseTime = bucket.avg.value; - const transactionsPerMinute = bucket.doc_count / minutes; - const impact = Math.round(averageResponseTime * transactionsPerMinute); - const sample = bucket.sample.hits.hits[0]._source; - - return { - name: bucket.key, - sample, - p95: bucket.p95.values['95.0'], - averageResponseTime, - transactionsPerMinute, - impact - }; - }); - - return calculateRelativeImpacts(results); -} diff --git a/x-pack/plugins/apm/server/lib/services/get_service.ts b/x-pack/plugins/apm/server/lib/services/get_service.ts index 2998f3487ecc6..05beb6b96883d 100644 --- a/x-pack/plugins/apm/server/lib/services/get_service.ts +++ b/x-pack/plugins/apm/server/lib/services/get_service.ts @@ -13,22 +13,22 @@ import { } from '../../../common/constants'; import { Setup } from '../helpers/setup_request'; -export interface ServiceResponse { - service_name: string; +export interface ServiceAPIResponse { + serviceName: string; types: string[]; - agent_name?: string; + agentName?: string; } export async function getService( serviceName: string, setup: Setup -): Promise { +): Promise { const { start, end, esFilterQuery, client, config } = setup; const params = { index: [ - config.get('apm_oss.errorIndices'), - config.get('apm_oss.transactionIndices') + config.get('apm_oss.errorIndices'), + config.get('apm_oss.transactionIndices') ], body: { size: 0, @@ -72,12 +72,12 @@ export async function getService( }; } - const resp = await client('search', params); - const aggs: Aggs = resp.aggregations; - + const { aggregations } = await client('search', params); return { - service_name: serviceName, - types: aggs.types.buckets.map(bucket => bucket.key), - agent_name: oc(aggs).agents.buckets[0].key() + serviceName, + types: oc(aggregations) + .types.buckets([]) + .map(bucket => bucket.key), + agentName: oc(aggregations).agents.buckets[0].key() }; } diff --git a/x-pack/plugins/apm/server/lib/services/get_services.ts b/x-pack/plugins/apm/server/lib/services/get_services.ts index eeb39c256e8a3..023e889e62fe5 100644 --- a/x-pack/plugins/apm/server/lib/services/get_services.ts +++ b/x-pack/plugins/apm/server/lib/services/get_services.ts @@ -14,23 +14,25 @@ import { } from '../../../common/constants'; import { Setup } from '../helpers/setup_request'; -export interface ServiceListItemResponse { - service_name: string; - agent_name: string | undefined; - transactions_per_minute: number; - errors_per_minute: number; - avg_response_time: number; +export interface IServiceListItem { + serviceName: string; + agentName: string | undefined; + transactionsPerMinute: number; + errorsPerMinute: number; + avgResponseTime: number; } +export type ServiceListAPIResponse = IServiceListItem[]; + export async function getServices( setup: Setup -): Promise { +): Promise { const { start, end, esFilterQuery, client, config } = setup; const params = { index: [ - config.get('apm_oss.errorIndices'), - config.get('apm_oss.transactionIndices') + config.get('apm_oss.errorIndices'), + config.get('apm_oss.transactionIndices') ], body: { size: 0, @@ -101,8 +103,8 @@ export async function getServices( }; } - const resp = await client('search', params); - const aggs: Aggs = resp.aggregations; + const resp = await client('search', params); + const aggs = resp.aggregations; const serviceBuckets = oc(aggs).services.buckets([]); return serviceBuckets.map(bucket => { @@ -118,11 +120,11 @@ export async function getServices( const errorsPerMinute = totalErrors / deltaAsMinutes; return { - service_name: bucket.key, - agent_name: oc(bucket).agents.buckets[0].key(), - transactions_per_minute: transactionsPerMinute, - errors_per_minute: errorsPerMinute, - avg_response_time: bucket.avg.value + serviceName: bucket.key, + agentName: oc(bucket).agents.buckets[0].key(), + transactionsPerMinute, + errorsPerMinute, + avgResponseTime: bucket.avg.value }; }); } diff --git a/x-pack/plugins/apm/server/lib/traces/get_top_traces.ts b/x-pack/plugins/apm/server/lib/traces/get_top_traces.ts index 990879690d9b7..a75f69e3369cb 100644 --- a/x-pack/plugins/apm/server/lib/traces/get_top_traces.ts +++ b/x-pack/plugins/apm/server/lib/traces/get_top_traces.ts @@ -4,74 +4,53 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SearchResponse } from 'elasticsearch'; -import { get } from 'lodash'; import { PARENT_ID, PROCESSOR_EVENT, TRACE_ID } from '../../../common/constants'; -import { Transaction } from '../../../typings/Transaction'; -import { ITransactionGroup } from '../../../typings/TransactionGroup'; import { Setup } from '../helpers/setup_request'; -import { - ITransactionGroupBucket, - prepareTransactionGroups, - TRANSACTION_GROUP_AGGREGATES -} from '../helpers/transaction_group_query'; +import { getTransactionGroups } from '../transaction_groups'; +import { ITransactionGroup } from '../transaction_groups/transform'; -export async function getTopTraces(setup: Setup): Promise { - const { start, end, esFilterQuery, client, config } = setup; +export type TraceListAPIResponse = ITransactionGroup[]; - const params = { - index: config.get('apm_oss.transactionIndices'), - body: { - size: 0, - query: { - bool: { - must: { - // this criterion safeguards against data that lacks a transaction - // parent ID but still is not a "trace" by way of not having a - // trace ID (e.g. old data before parent ID was implemented, etc) - exists: { - field: TRACE_ID - } - }, - must_not: { - // no parent ID alongside a trace ID means this transaction is a - // "root" transaction, i.e. a trace - exists: { - field: PARENT_ID - } - }, - filter: [ - { - range: { - '@timestamp': { - gte: start, - lte: end, - format: 'epoch_millis' - } - } - }, - { term: { [PROCESSOR_EVENT]: 'transaction' } } - ] +export async function getTopTraces( + setup: Setup +): Promise { + const { start, end } = setup; + + const bodyQuery = { + bool: { + must: { + // this criterion safeguards against data that lacks a transaction + // parent ID but still is not a "trace" by way of not having a + // trace ID (e.g. old data before parent ID was implemented, etc) + exists: { + field: TRACE_ID } }, - aggs: TRANSACTION_GROUP_AGGREGATES + must_not: { + // no parent ID alongside a trace ID means this transaction is a + // "root" transaction, i.e. a trace + exists: { + field: PARENT_ID + } + }, + filter: [ + { + range: { + '@timestamp': { + gte: start, + lte: end, + format: 'epoch_millis' + } + } + }, + { term: { [PROCESSOR_EVENT]: 'transaction' } } + ] } }; - if (esFilterQuery) { - params.body.query.bool.filter.push(esFilterQuery); - } - - const response: SearchResponse = await client('search', params); - const buckets: ITransactionGroupBucket[] = get( - response.aggregations, - 'transactions.buckets', - [] - ); - - return prepareTransactionGroups({ buckets, start, end }); + return getTransactionGroups(setup, bodyQuery); } diff --git a/x-pack/plugins/apm/server/lib/traces/get_trace.ts b/x-pack/plugins/apm/server/lib/traces/get_trace.ts index b92e9e377e701..4adb8ece0c81b 100644 --- a/x-pack/plugins/apm/server/lib/traces/get_trace.ts +++ b/x-pack/plugins/apm/server/lib/traces/get_trace.ts @@ -4,17 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SearchParams, SearchResponse } from 'elasticsearch'; -import { WaterfallResponse } from 'x-pack/plugins/apm/typings/waterfall'; +import { SearchParams } from 'elasticsearch'; import { TRACE_ID } from '../../../common/constants'; import { Span } from '../../../typings/Span'; import { Transaction } from '../../../typings/Transaction'; import { Setup } from '../helpers/setup_request'; +export type TraceAPIResponse = Array; + export async function getTrace( traceId: string, setup: Setup -): Promise { +): Promise { const { start, end, client, config } = setup; const params: SearchParams = { @@ -40,10 +41,6 @@ export async function getTrace( } }; - const resp: SearchResponse = await client( - 'search', - params - ); - + const resp = await client('search', params); return resp.hits.hits.map(hit => hit._source); } diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/fetcher.test.ts.snap b/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/fetcher.test.ts.snap new file mode 100644 index 0000000000000..9e180f3fcdb5d --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/fetcher.test.ts.snap @@ -0,0 +1,56 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`transactionGroupsFetcher should call client with correct query 1`] = ` +Array [ + Array [ + "search", + Object { + "body": Object { + "aggs": Object { + "transactions": Object { + "aggs": Object { + "avg": Object { + "avg": Object { + "field": "transaction.duration.us", + }, + }, + "p95": Object { + "percentiles": Object { + "field": "transaction.duration.us", + "percents": Array [ + 95, + ], + }, + }, + "sample": Object { + "top_hits": Object { + "size": 1, + "sort": Array [ + Object { + "@timestamp": Object { + "order": "desc", + }, + }, + ], + }, + }, + }, + "terms": Object { + "field": "transaction.name.keyword", + "order": Object { + "avg": "desc", + }, + "size": 100, + }, + }, + }, + "query": Object { + "my": "bodyQuery", + }, + "size": 0, + }, + "index": "myIndex", + }, + ], +] +`; diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/transform.test.ts.snap b/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/transform.test.ts.snap new file mode 100644 index 0000000000000..a68d7dc80338a --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transaction_groups/__snapshots__/transform.test.ts.snap @@ -0,0 +1,2822 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`transactionGroupsTransformer should match snapshot 1`] = ` +Array [ + Object { + "averageResponseTime": 255966.30555555556, + "impact": 4.369340653255684, + "name": "POST /api/orders", + "p95": 320238.5, + "sample": Object { + "@timestamp": "2018-11-18T20:43:32.010Z", + "agent": Object { + "hostname": "b359e3afece8", + "type": "apm-server", + "version": "7.0.0-alpha1", + }, + "context": Object { + "custom": Object { + "containerId": 4669, + }, + "process": Object { + "argv": Array [ + "/usr/local/bin/node", + "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", + ], + "pid": 2413, + "ppid": 1, + "title": "node /app/server.js", + }, + "request": Object { + "body": "[REDACTED]", + "headers": Object { + "accept": "application/json", + "connection": "close", + "content-length": "129", + "content-type": "application/json", + "host": "opbeans-node:3000", + "user-agent": "workload/2.4.3", + }, + "http_version": "1.1", + "method": "POST", + "socket": Object { + "encrypted": false, + "remote_address": "::ffff:172.18.0.10", + }, + "url": Object { + "full": "http://opbeans-node:3000/api/orders", + "hostname": "opbeans-node", + "pathname": "/api/orders", + "port": "3000", + "protocol": "http:", + "raw": "/api/orders", + }, + }, + "response": Object { + "headers": Object { + "connection": "close", + "content-length": "13", + "content-type": "application/json; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:43:32 GMT", + "etag": "W/\\"d-g9K2iK4ordyN88lGL4LmPlYNfhc\\"", + "x-powered-by": "Express", + }, + "status_code": 200, + }, + "service": Object { + "agent": Object { + "name": "nodejs", + "version": "1.14.2", + }, + "language": Object { + "name": "javascript", + }, + "name": "opbeans-node", + "runtime": Object { + "name": "node", + "version": "8.12.0", + }, + "version": "1.0.0", + }, + "system": Object { + "architecture": "x64", + "hostname": "98195610c255", + "ip": "172.18.0.10", + "platform": "linux", + }, + "tags": Object { + "foo": "bar", + "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", + "multi-line": "foo +bar +baz", + "this-is-a-very-long-tag-name-without-any-spaces": "test", + }, + "user": Object { + "email": "kimchy@elastic.co", + "id": "42", + "username": "kimchy", + }, + }, + "host": Object { + "name": "b359e3afece8", + }, + "processor": Object { + "event": "transaction", + "name": "transaction", + }, + "timestamp": Object { + "us": 1542573812010006, + }, + "trace": Object { + "id": "2b1252a338249daeecf6afb0c236e31b", + }, + "transaction": Object { + "duration": Object { + "us": 291572, + }, + "id": "2c9f39e9ec4a0111", + "name": "POST /api/orders", + "result": "HTTP 2xx", + "sampled": true, + "span_count": Object { + "started": 16, + }, + "type": "request", + }, + }, + "transactionsPerMinute": 5684.210526315789, + }, + Object { + "averageResponseTime": 48021.972616494, + "impact": 100, + "name": "GET /api", + "p95": 67138.18364917398, + "sample": Object { + "@timestamp": "2018-11-18T20:53:44.070Z", + "agent": Object { + "hostname": "b359e3afece8", + "type": "apm-server", + "version": "7.0.0-alpha1", + }, + "context": Object { + "custom": Object { + "containerId": 5176, + }, + "process": Object { + "argv": Array [ + "/usr/local/bin/node", + "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", + ], + "pid": 3756, + "ppid": 1, + "title": "node /app/server.js", + }, + "request": Object { + "headers": Object { + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "elastic-apm-traceparent": "00-86c68779d8a65b06fb78e770ffc436a5-4aaea53dc1791183-01", + "host": "opbeans-node:3000", + "user-agent": "python-requests/2.20.0", + }, + "http_version": "1.1", + "method": "GET", + "socket": Object { + "encrypted": false, + "remote_address": "::ffff:172.18.0.6", + }, + "url": Object { + "full": "http://opbeans-node:3000/api/types/3", + "hostname": "opbeans-node", + "pathname": "/api/types/3", + "port": "3000", + "protocol": "http:", + "raw": "/api/types/3", + }, + }, + "response": Object { + "headers": Object { + "connection": "close", + "content-type": "application/json;charset=UTF-8", + "date": "Sun, 18 Nov 2018 20:53:43 GMT", + "transfer-encoding": "chunked", + "x-powered-by": "Express", + }, + "status_code": 404, + }, + "service": Object { + "agent": Object { + "name": "nodejs", + "version": "1.14.2", + }, + "language": Object { + "name": "javascript", + }, + "name": "opbeans-node", + "runtime": Object { + "name": "node", + "version": "8.12.0", + }, + "version": "1.0.0", + }, + "system": Object { + "architecture": "x64", + "hostname": "98195610c255", + "ip": "172.18.0.10", + "platform": "linux", + }, + "tags": Object { + "foo": "bar", + "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", + "multi-line": "foo +bar +baz", + "this-is-a-very-long-tag-name-without-any-spaces": "test", + }, + "user": Object { + "email": "kimchy@elastic.co", + "id": "42", + "username": "kimchy", + }, + }, + "host": Object { + "name": "b359e3afece8", + }, + "parent": Object { + "id": "4aaea53dc1791183", + }, + "processor": Object { + "event": "transaction", + "name": "transaction", + }, + "timestamp": Object { + "us": 1542574424070007, + }, + "trace": Object { + "id": "86c68779d8a65b06fb78e770ffc436a5", + }, + "transaction": Object { + "duration": Object { + "us": 8684, + }, + "id": "a78bca581dcd8ff8", + "name": "GET /api", + "result": "HTTP 4xx", + "sampled": true, + "span_count": Object { + "started": 1, + }, + "type": "request", + }, + }, + "transactionsPerMinute": 691926.3157894736, + }, + Object { + "averageResponseTime": 33265.03326147213, + "impact": 10.256357027900046, + "name": "GET /api/orders", + "p95": 58827.489999999976, + "sample": Object { + "@timestamp": "2018-11-18T20:53:40.973Z", + "agent": Object { + "hostname": "b359e3afece8", + "type": "apm-server", + "version": "7.0.0-alpha1", + }, + "context": Object { + "custom": Object { + "containerId": 408, + }, + "process": Object { + "argv": Array [ + "/usr/local/bin/node", + "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", + ], + "pid": 3756, + "ppid": 1, + "title": "node /app/server.js", + }, + "request": Object { + "headers": Object { + "connection": "close", + "host": "opbeans-node:3000", + "user-agent": "workload/2.4.3", + }, + "http_version": "1.1", + "method": "GET", + "socket": Object { + "encrypted": false, + "remote_address": "::ffff:172.18.0.10", + }, + "url": Object { + "full": "http://opbeans-node:3000/api/orders", + "hostname": "opbeans-node", + "pathname": "/api/orders", + "port": "3000", + "protocol": "http:", + "raw": "/api/orders", + }, + }, + "response": Object { + "headers": Object { + "connection": "close", + "content-length": "103612", + "content-type": "application/json; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:53:40 GMT", + "etag": "W/\\"194bc-cOw6+iRf7XCeqMXHrle3IOig7tY\\"", + "x-powered-by": "Express", + }, + "status_code": 200, + }, + "service": Object { + "agent": Object { + "name": "nodejs", + "version": "1.14.2", + }, + "language": Object { + "name": "javascript", + }, + "name": "opbeans-node", + "runtime": Object { + "name": "node", + "version": "8.12.0", + }, + "version": "1.0.0", + }, + "system": Object { + "architecture": "x64", + "hostname": "98195610c255", + "ip": "172.18.0.10", + "platform": "linux", + }, + "tags": Object { + "foo": "bar", + "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", + "multi-line": "foo +bar +baz", + "this-is-a-very-long-tag-name-without-any-spaces": "test", + }, + "user": Object { + "email": "kimchy@elastic.co", + "id": "42", + "username": "kimchy", + }, + }, + "host": Object { + "name": "b359e3afece8", + }, + "processor": Object { + "event": "transaction", + "name": "transaction", + }, + "timestamp": Object { + "us": 1542574420973006, + }, + "trace": Object { + "id": "0afce85f593cbbdd09949936fe964f0f", + }, + "transaction": Object { + "duration": Object { + "us": 23040, + }, + "id": "89f200353eb50539", + "name": "GET /api/orders", + "result": "HTTP 2xx", + "sampled": true, + "span_count": Object { + "started": 2, + }, + "type": "request", + }, + }, + "transactionsPerMinute": 102536.84210526315, + }, + Object { + "averageResponseTime": 32900.72714285714, + "impact": 2.179120743402716, + "name": "GET /log-message", + "p95": 40444, + "sample": Object { + "@timestamp": "2018-11-18T20:49:09.225Z", + "agent": Object { + "hostname": "b359e3afece8", + "type": "apm-server", + "version": "7.0.0-alpha1", + }, + "context": Object { + "custom": Object { + "containerId": 321, + }, + "process": Object { + "argv": Array [ + "/usr/local/bin/node", + "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", + ], + "pid": 3142, + "ppid": 1, + "title": "node /app/server.js", + }, + "request": Object { + "headers": Object { + "connection": "close", + "host": "opbeans-node:3000", + "user-agent": "workload/2.4.3", + }, + "http_version": "1.1", + "method": "GET", + "socket": Object { + "encrypted": false, + "remote_address": "::ffff:172.18.0.10", + }, + "url": Object { + "full": "http://opbeans-node:3000/log-message", + "hostname": "opbeans-node", + "pathname": "/log-message", + "port": "3000", + "protocol": "http:", + "raw": "/log-message", + }, + }, + "response": Object { + "headers": Object { + "connection": "close", + "content-length": "24", + "content-type": "text/html; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:49:09 GMT", + "etag": "W/\\"18-MS3VbhH7auHMzO0fUuNF6v14N/M\\"", + "x-powered-by": "Express", + }, + "status_code": 500, + }, + "service": Object { + "agent": Object { + "name": "nodejs", + "version": "1.14.2", + }, + "language": Object { + "name": "javascript", + }, + "name": "opbeans-node", + "runtime": Object { + "name": "node", + "version": "8.12.0", + }, + "version": "1.0.0", + }, + "system": Object { + "architecture": "x64", + "hostname": "98195610c255", + "ip": "172.18.0.10", + "platform": "linux", + }, + "tags": Object { + "foo": "bar", + "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", + "multi-line": "foo +bar +baz", + "this-is-a-very-long-tag-name-without-any-spaces": "test", + }, + "user": Object { + "email": "kimchy@elastic.co", + "id": "42", + "username": "kimchy", + }, + }, + "host": Object { + "name": "b359e3afece8", + }, + "processor": Object { + "event": "transaction", + "name": "transaction", + }, + "timestamp": Object { + "us": 1542574149225004, + }, + "trace": Object { + "id": "ba18b741cdd3ac83eca89a5fede47577", + }, + "transaction": Object { + "duration": Object { + "us": 32381, + }, + "id": "b9a8f96d7554d09f", + "name": "GET /log-message", + "result": "HTTP 5xx", + "sampled": true, + "span_count": Object { + "started": 0, + }, + "type": "request", + }, + }, + "transactionsPerMinute": 22105.263157894737, + }, + Object { + "averageResponseTime": 32554.36257814184, + "impact": 14.344171564855404, + "name": "GET /api/stats", + "p95": 59356.73611111111, + "sample": Object { + "@timestamp": "2018-11-18T20:53:42.560Z", + "agent": Object { + "hostname": "b359e3afece8", + "type": "apm-server", + "version": "7.0.0-alpha1", + }, + "context": Object { + "custom": Object { + "containerId": 207, + }, + "process": Object { + "argv": Array [ + "/usr/local/bin/node", + "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", + ], + "pid": 3756, + "ppid": 1, + "title": "node /app/server.js", + }, + "request": Object { + "headers": Object { + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "elastic-apm-traceparent": "00-63ccc3b0929dafb7f2fbcabdc7f7af25-821a787e73ab1563-01", + "host": "opbeans-node:3000", + "if-none-match": "W/\\"77-uxKJrX5GSMJJWTKh3orUFAEVxSs\\"", + "referer": "http://opbeans-node:3000/dashboard", + "user-agent": "Chromeless 1.4.0", + }, + "http_version": "1.1", + "method": "GET", + "socket": Object { + "encrypted": false, + "remote_address": "::ffff:172.18.0.7", + }, + "url": Object { + "full": "http://opbeans-node:3000/api/stats", + "hostname": "opbeans-node", + "pathname": "/api/stats", + "port": "3000", + "protocol": "http:", + "raw": "/api/stats", + }, + }, + "response": Object { + "headers": Object { + "connection": "keep-alive", + "date": "Sun, 18 Nov 2018 20:53:42 GMT", + "etag": "W/\\"77-uxKJrX5GSMJJWTKh3orUFAEVxSs\\"", + "x-powered-by": "Express", + }, + "status_code": 304, + }, + "service": Object { + "agent": Object { + "name": "nodejs", + "version": "1.14.2", + }, + "language": Object { + "name": "javascript", + }, + "name": "opbeans-node", + "runtime": Object { + "name": "node", + "version": "8.12.0", + }, + "version": "1.0.0", + }, + "system": Object { + "architecture": "x64", + "hostname": "98195610c255", + "ip": "172.18.0.10", + "platform": "linux", + }, + "tags": Object { + "foo": "bar", + "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", + "multi-line": "foo +bar +baz", + "this-is-a-very-long-tag-name-without-any-spaces": "test", + }, + "user": Object { + "email": "kimchy@elastic.co", + "id": "42", + "username": "kimchy", + }, + }, + "host": Object { + "name": "b359e3afece8", + }, + "parent": Object { + "id": "821a787e73ab1563", + }, + "processor": Object { + "event": "transaction", + "name": "transaction", + }, + "timestamp": Object { + "us": 1542574422560002, + }, + "trace": Object { + "id": "63ccc3b0929dafb7f2fbcabdc7f7af25", + }, + "transaction": Object { + "duration": Object { + "us": 28753, + }, + "id": "fb754e7628da2fb5", + "name": "GET /api/stats", + "result": "HTTP 3xx", + "sampled": true, + "span_count": Object { + "started": 7, + }, + "type": "request", + }, + }, + "transactionsPerMinute": 146494.73684210525, + }, + Object { + "averageResponseTime": 32387.73641304348, + "impact": 2.2558112391673664, + "name": "GET /log-error", + "p95": 40061.1, + "sample": Object { + "@timestamp": "2018-11-18T20:52:51.462Z", + "agent": Object { + "hostname": "b359e3afece8", + "type": "apm-server", + "version": "7.0.0-alpha1", + }, + "context": Object { + "custom": Object { + "containerId": 4877, + }, + "process": Object { + "argv": Array [ + "/usr/local/bin/node", + "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", + ], + "pid": 3659, + "ppid": 1, + "title": "node /app/server.js", + }, + "request": Object { + "headers": Object { + "connection": "close", + "host": "opbeans-node:3000", + "user-agent": "workload/2.4.3", + }, + "http_version": "1.1", + "method": "GET", + "socket": Object { + "encrypted": false, + "remote_address": "::ffff:172.18.0.10", + }, + "url": Object { + "full": "http://opbeans-node:3000/log-error", + "hostname": "opbeans-node", + "pathname": "/log-error", + "port": "3000", + "protocol": "http:", + "raw": "/log-error", + }, + }, + "response": Object { + "headers": Object { + "connection": "close", + "content-length": "24", + "content-type": "text/html; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:52:51 GMT", + "etag": "W/\\"18-MS3VbhH7auHMzO0fUuNF6v14N/M\\"", + "x-powered-by": "Express", + }, + "status_code": 500, + }, + "service": Object { + "agent": Object { + "name": "nodejs", + "version": "1.14.2", + }, + "language": Object { + "name": "javascript", + }, + "name": "opbeans-node", + "runtime": Object { + "name": "node", + "version": "8.12.0", + }, + "version": "1.0.0", + }, + "system": Object { + "architecture": "x64", + "hostname": "98195610c255", + "ip": "172.18.0.10", + "platform": "linux", + }, + "tags": Object { + "foo": "bar", + "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", + "multi-line": "foo +bar +baz", + "this-is-a-very-long-tag-name-without-any-spaces": "test", + }, + "user": Object { + "email": "kimchy@elastic.co", + "id": "42", + "username": "kimchy", + }, + }, + "host": Object { + "name": "b359e3afece8", + }, + "processor": Object { + "event": "transaction", + "name": "transaction", + }, + "timestamp": Object { + "us": 1542574371462005, + }, + "trace": Object { + "id": "15366d65659b5fc8f67ff127391b3aff", + }, + "transaction": Object { + "duration": Object { + "us": 33367, + }, + "id": "ec9c465c5042ded8", + "name": "GET /log-error", + "result": "HTTP 5xx", + "sampled": true, + "span_count": Object { + "started": 0, + }, + "type": "request", + }, + }, + "transactionsPerMinute": 23242.105263157893, + }, + Object { + "averageResponseTime": 32159.926322043968, + "impact": 10.279049521913821, + "name": "GET /api/customers", + "p95": 59845.85714285714, + "sample": Object { + "@timestamp": "2018-11-18T20:53:21.180Z", + "agent": Object { + "hostname": "b359e3afece8", + "type": "apm-server", + "version": "7.0.0-alpha1", + }, + "context": Object { + "custom": Object { + "containerId": 2531, + }, + "process": Object { + "argv": Array [ + "/usr/local/bin/node", + "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", + ], + "pid": 3710, + "ppid": 1, + "title": "node /app/server.js", + }, + "request": Object { + "headers": Object { + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "elastic-apm-traceparent": "00-541025da8ecc2f51f21c1a4ad6992b77-ca18d9d4c3879519-01", + "host": "opbeans-node:3000", + "user-agent": "python-requests/2.20.0", + }, + "http_version": "1.1", + "method": "GET", + "socket": Object { + "encrypted": false, + "remote_address": "::ffff:172.18.0.6", + }, + "url": Object { + "full": "http://opbeans-node:3000/api/customers", + "hostname": "opbeans-node", + "pathname": "/api/customers", + "port": "3000", + "protocol": "http:", + "raw": "/api/customers", + }, + }, + "response": Object { + "headers": Object { + "connection": "keep-alive", + "content-length": "186769", + "content-type": "application/json; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:53:21 GMT", + "etag": "W/\\"2d991-yG3J8W/roH7fSxXTudZrO27Ax9s\\"", + "x-powered-by": "Express", + }, + "status_code": 200, + }, + "service": Object { + "agent": Object { + "name": "nodejs", + "version": "1.14.2", + }, + "language": Object { + "name": "javascript", + }, + "name": "opbeans-node", + "runtime": Object { + "name": "node", + "version": "8.12.0", + }, + "version": "1.0.0", + }, + "system": Object { + "architecture": "x64", + "hostname": "98195610c255", + "ip": "172.18.0.10", + "platform": "linux", + }, + "tags": Object { + "foo": "bar", + "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", + "multi-line": "foo +bar +baz", + "this-is-a-very-long-tag-name-without-any-spaces": "test", + }, + "user": Object { + "email": "kimchy@elastic.co", + "id": "42", + "username": "kimchy", + }, + }, + "host": Object { + "name": "b359e3afece8", + }, + "parent": Object { + "id": "ca18d9d4c3879519", + }, + "processor": Object { + "event": "transaction", + "name": "transaction", + }, + "timestamp": Object { + "us": 1542574401180002, + }, + "trace": Object { + "id": "541025da8ecc2f51f21c1a4ad6992b77", + }, + "transaction": Object { + "duration": Object { + "us": 18077, + }, + "id": "94852b9dd1075982", + "name": "GET /api/customers", + "result": "HTTP 2xx", + "sampled": true, + "span_count": Object { + "started": 2, + }, + "type": "request", + }, + }, + "transactionsPerMinute": 106294.73684210525, + }, + Object { + "averageResponseTime": 27516.89144558744, + "impact": 9.651458993728006, + "name": "GET /api/products/top", + "p95": 56064.679999999986, + "sample": Object { + "@timestamp": "2018-11-18T20:52:57.316Z", + "agent": Object { + "hostname": "b359e3afece8", + "type": "apm-server", + "version": "7.0.0-alpha1", + }, + "context": Object { + "custom": Object { + "containerId": 5113, + }, + "process": Object { + "argv": Array [ + "/usr/local/bin/node", + "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", + ], + "pid": 3686, + "ppid": 1, + "title": "node /app/server.js", + }, + "request": Object { + "headers": Object { + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "elastic-apm-traceparent": "00-74f12e705936d66350f4741ebeb55189-fcebe94cd2136215-01", + "host": "opbeans-node:3000", + "referer": "http://opbeans-node:3000/dashboard", + "user-agent": "Chromeless 1.4.0", + }, + "http_version": "1.1", + "method": "GET", + "socket": Object { + "encrypted": false, + "remote_address": "::ffff:172.18.0.7", + }, + "url": Object { + "full": "http://opbeans-node:3000/api/products/top", + "hostname": "opbeans-node", + "pathname": "/api/products/top", + "port": "3000", + "protocol": "http:", + "raw": "/api/products/top", + }, + }, + "response": Object { + "headers": Object { + "connection": "keep-alive", + "content-length": "282", + "content-type": "application/json; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:52:57 GMT", + "etag": "W/\\"11a-lcI9zuMZYYsDRpEZgYqDYr96cKM\\"", + "x-powered-by": "Express", + }, + "status_code": 200, + }, + "service": Object { + "agent": Object { + "name": "nodejs", + "version": "1.14.2", + }, + "language": Object { + "name": "javascript", + }, + "name": "opbeans-node", + "runtime": Object { + "name": "node", + "version": "8.12.0", + }, + "version": "1.0.0", + }, + "system": Object { + "architecture": "x64", + "hostname": "98195610c255", + "ip": "172.18.0.10", + "platform": "linux", + }, + "tags": Object { + "foo": "bar", + "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", + "multi-line": "foo +bar +baz", + "this-is-a-very-long-tag-name-without-any-spaces": "test", + }, + "user": Object { + "email": "kimchy@elastic.co", + "id": "42", + "username": "kimchy", + }, + }, + "host": Object { + "name": "b359e3afece8", + }, + "parent": Object { + "id": "fcebe94cd2136215", + }, + "processor": Object { + "event": "transaction", + "name": "transaction", + }, + "timestamp": Object { + "us": 1542574377316005, + }, + "trace": Object { + "id": "74f12e705936d66350f4741ebeb55189", + }, + "transaction": Object { + "duration": Object { + "us": 48781, + }, + "id": "be4bd5475d5d9e6f", + "name": "GET /api/products/top", + "result": "HTTP 2xx", + "sampled": true, + "span_count": Object { + "started": 4, + }, + "type": "request", + }, + }, + "transactionsPerMinute": 116652.63157894736, + }, + Object { + "averageResponseTime": 21331.714285714286, + "impact": 0.28817488008070574, + "name": "POST /api", + "p95": 30938, + "sample": Object { + "@timestamp": "2018-11-18T20:29:42.751Z", + "agent": Object { + "hostname": "b359e3afece8", + "type": "apm-server", + "version": "7.0.0-alpha1", + }, + "context": Object { + "custom": Object { + "containerId": 2927, + }, + "process": Object { + "argv": Array [ + "/usr/local/bin/node", + "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", + ], + "pid": 546, + "ppid": 1, + "title": "node /app/server.js", + }, + "request": Object { + "body": "[REDACTED]", + "headers": Object { + "accept": "application/json", + "connection": "close", + "content-length": "129", + "content-type": "application/json", + "host": "opbeans-node:3000", + "user-agent": "workload/2.4.3", + }, + "http_version": "1.1", + "method": "POST", + "socket": Object { + "encrypted": false, + "remote_address": "::ffff:172.18.0.10", + }, + "url": Object { + "full": "http://opbeans-node:3000/api/orders", + "hostname": "opbeans-node", + "pathname": "/api/orders", + "port": "3000", + "protocol": "http:", + "raw": "/api/orders", + }, + }, + "response": Object { + "headers": Object { + "connection": "close", + "content-length": "0", + "date": "Sun, 18 Nov 2018 20:29:42 GMT", + "x-powered-by": "Express", + }, + "status_code": 400, + }, + "service": Object { + "agent": Object { + "name": "nodejs", + "version": "1.14.2", + }, + "language": Object { + "name": "javascript", + }, + "name": "opbeans-node", + "runtime": Object { + "name": "node", + "version": "8.12.0", + }, + "version": "1.0.0", + }, + "system": Object { + "architecture": "x64", + "hostname": "98195610c255", + "ip": "172.18.0.10", + "platform": "linux", + }, + "tags": Object { + "foo": "bar", + "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", + "multi-line": "foo +bar +baz", + "this-is-a-very-long-tag-name-without-any-spaces": "test", + }, + "user": Object { + "email": "kimchy@elastic.co", + "id": "42", + "username": "kimchy", + }, + }, + "host": Object { + "name": "b359e3afece8", + }, + "processor": Object { + "event": "transaction", + "name": "transaction", + }, + "timestamp": Object { + "us": 1542572982751005, + }, + "trace": Object { + "id": "8ed4d94ec8fc11b1ea1b0aa59c2320ff", + }, + "transaction": Object { + "duration": Object { + "us": 21083, + }, + "id": "d67c2f7aa897110c", + "name": "POST /api", + "result": "HTTP 4xx", + "sampled": true, + "span_count": Object { + "started": 1, + }, + "type": "request", + }, + }, + "transactionsPerMinute": 4642.105263157894, + }, + Object { + "averageResponseTime": 17189.329210275926, + "impact": 3.424381788267164, + "name": "GET /api/products/:id/customers", + "p95": 39284.79999999999, + "sample": Object { + "@timestamp": "2018-11-18T20:48:24.769Z", + "agent": Object { + "hostname": "b359e3afece8", + "type": "apm-server", + "version": "7.0.0-alpha1", + }, + "context": Object { + "custom": Object { + "containerId": 1735, + }, + "process": Object { + "argv": Array [ + "/usr/local/bin/node", + "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", + ], + "pid": 3100, + "ppid": 1, + "title": "node /app/server.js", + }, + "request": Object { + "headers": Object { + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "elastic-apm-traceparent": "00-28f178c354d17f400dea04bc4a7b3c57-68f5d1607cac7779-01", + "host": "opbeans-node:3000", + "user-agent": "python-requests/2.20.0", + }, + "http_version": "1.1", + "method": "GET", + "socket": Object { + "encrypted": false, + "remote_address": "::ffff:172.18.0.6", + }, + "url": Object { + "full": "http://opbeans-node:3000/api/products/2/customers", + "hostname": "opbeans-node", + "pathname": "/api/products/2/customers", + "port": "3000", + "protocol": "http:", + "raw": "/api/products/2/customers", + }, + }, + "response": Object { + "headers": Object { + "connection": "keep-alive", + "content-length": "186570", + "content-type": "application/json; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:48:24 GMT", + "etag": "W/\\"2d8ca-Z9NzuHyGyxwtzpOkcIxBvzm24iw\\"", + "x-powered-by": "Express", + }, + "status_code": 200, + }, + "service": Object { + "agent": Object { + "name": "nodejs", + "version": "1.14.2", + }, + "language": Object { + "name": "javascript", + }, + "name": "opbeans-node", + "runtime": Object { + "name": "node", + "version": "8.12.0", + }, + "version": "1.0.0", + }, + "system": Object { + "architecture": "x64", + "hostname": "98195610c255", + "ip": "172.18.0.10", + "platform": "linux", + }, + "tags": Object { + "foo": "bar", + "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", + "multi-line": "foo +bar +baz", + "this-is-a-very-long-tag-name-without-any-spaces": "test", + }, + "user": Object { + "email": "kimchy@elastic.co", + "id": "42", + "username": "kimchy", + }, + }, + "host": Object { + "name": "b359e3afece8", + }, + "parent": Object { + "id": "68f5d1607cac7779", + }, + "processor": Object { + "event": "transaction", + "name": "transaction", + }, + "timestamp": Object { + "us": 1542574104769029, + }, + "trace": Object { + "id": "28f178c354d17f400dea04bc4a7b3c57", + }, + "transaction": Object { + "duration": Object { + "us": 49338, + }, + "id": "2a87ae20ad04ee0c", + "name": "GET /api/products/:id/customers", + "result": "HTTP 2xx", + "sampled": true, + "span_count": Object { + "started": 1, + }, + "type": "request", + }, + }, + "transactionsPerMinute": 66378.94736842105, + }, + Object { + "averageResponseTime": 12763.68806073154, + "impact": 1.747992435179465, + "name": "GET /api/types/:id", + "p95": 30576.749999999996, + "sample": Object { + "@timestamp": "2018-11-18T20:53:35.967Z", + "agent": Object { + "hostname": "b359e3afece8", + "type": "apm-server", + "version": "7.0.0-alpha1", + }, + "context": Object { + "custom": Object { + "containerId": 5345, + }, + "process": Object { + "argv": Array [ + "/usr/local/bin/node", + "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", + ], + "pid": 3756, + "ppid": 1, + "title": "node /app/server.js", + }, + "request": Object { + "headers": Object { + "connection": "close", + "host": "opbeans-node:3000", + "user-agent": "workload/2.4.3", + }, + "http_version": "1.1", + "method": "GET", + "socket": Object { + "encrypted": false, + "remote_address": "::ffff:172.18.0.10", + }, + "url": Object { + "full": "http://opbeans-node:3000/api/types/1", + "hostname": "opbeans-node", + "pathname": "/api/types/1", + "port": "3000", + "protocol": "http:", + "raw": "/api/types/1", + }, + }, + "response": Object { + "headers": Object { + "connection": "close", + "content-length": "217", + "content-type": "application/json; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:53:35 GMT", + "etag": "W/\\"d9-cebOOHODBQMZd1wt+ZZBaSPgQLQ\\"", + "x-powered-by": "Express", + }, + "status_code": 200, + }, + "service": Object { + "agent": Object { + "name": "nodejs", + "version": "1.14.2", + }, + "language": Object { + "name": "javascript", + }, + "name": "opbeans-node", + "runtime": Object { + "name": "node", + "version": "8.12.0", + }, + "version": "1.0.0", + }, + "system": Object { + "architecture": "x64", + "hostname": "98195610c255", + "ip": "172.18.0.10", + "platform": "linux", + }, + "tags": Object { + "foo": "bar", + "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", + "multi-line": "foo +bar +baz", + "this-is-a-very-long-tag-name-without-any-spaces": "test", + }, + "user": Object { + "email": "kimchy@elastic.co", + "id": "42", + "username": "kimchy", + }, + }, + "host": Object { + "name": "b359e3afece8", + }, + "processor": Object { + "event": "transaction", + "name": "transaction", + }, + "timestamp": Object { + "us": 1542574415967005, + }, + "trace": Object { + "id": "2223b30b5cbaf2e221fcf70ac6d9abbe", + }, + "transaction": Object { + "duration": Object { + "us": 13064, + }, + "id": "053436abacdec0a4", + "name": "GET /api/types/:id", + "result": "HTTP 2xx", + "sampled": true, + "span_count": Object { + "started": 2, + }, + "type": "request", + }, + }, + "transactionsPerMinute": 45757.8947368421, + }, + Object { + "averageResponseTime": 12683.190864600327, + "impact": 4.4239778511514745, + "name": "GET /api/products", + "p95": 35009.67999999999, + "sample": Object { + "@timestamp": "2018-11-18T20:53:43.477Z", + "agent": Object { + "hostname": "b359e3afece8", + "type": "apm-server", + "version": "7.0.0-alpha1", + }, + "context": Object { + "custom": Object { + "containerId": 2857, + }, + "process": Object { + "argv": Array [ + "/usr/local/bin/node", + "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", + ], + "pid": 3756, + "ppid": 1, + "title": "node /app/server.js", + }, + "request": Object { + "headers": Object { + "connection": "close", + "host": "opbeans-node:3000", + "user-agent": "workload/2.4.3", + }, + "http_version": "1.1", + "method": "GET", + "socket": Object { + "encrypted": false, + "remote_address": "::ffff:172.18.0.10", + }, + "url": Object { + "full": "http://opbeans-node:3000/api/products", + "hostname": "opbeans-node", + "pathname": "/api/products", + "port": "3000", + "protocol": "http:", + "raw": "/api/products", + }, + }, + "response": Object { + "headers": Object { + "connection": "close", + "content-length": "1023", + "content-type": "application/json; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:53:43 GMT", + "etag": "W/\\"3ff-VyOxcDApb+a/lnjkm9FeTOGSDrs\\"", + "x-powered-by": "Express", + }, + "status_code": 200, + }, + "service": Object { + "agent": Object { + "name": "nodejs", + "version": "1.14.2", + }, + "language": Object { + "name": "javascript", + }, + "name": "opbeans-node", + "runtime": Object { + "name": "node", + "version": "8.12.0", + }, + "version": "1.0.0", + }, + "system": Object { + "architecture": "x64", + "hostname": "98195610c255", + "ip": "172.18.0.10", + "platform": "linux", + }, + "tags": Object { + "foo": "bar", + "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", + "multi-line": "foo +bar +baz", + "this-is-a-very-long-tag-name-without-any-spaces": "test", + }, + "user": Object { + "email": "kimchy@elastic.co", + "id": "42", + "username": "kimchy", + }, + }, + "host": Object { + "name": "b359e3afece8", + }, + "processor": Object { + "event": "transaction", + "name": "transaction", + }, + "timestamp": Object { + "us": 1542574423477006, + }, + "trace": Object { + "id": "bee00a8efb523ca4b72adad57f7caba3", + }, + "transaction": Object { + "duration": Object { + "us": 6915, + }, + "id": "d8fc6d3b8707b64c", + "name": "GET /api/products", + "result": "HTTP 2xx", + "sampled": true, + "span_count": Object { + "started": 2, + }, + "type": "request", + }, + }, + "transactionsPerMinute": 116147.36842105263, + }, + Object { + "averageResponseTime": 11257.757916666667, + "impact": 2.558180605423081, + "name": "GET /api/types", + "p95": 35222.944444444445, + "sample": Object { + "@timestamp": "2018-11-18T20:53:44.978Z", + "agent": Object { + "hostname": "b359e3afece8", + "type": "apm-server", + "version": "7.0.0-alpha1", + }, + "context": Object { + "custom": Object { + "containerId": 2193, + }, + "process": Object { + "argv": Array [ + "/usr/local/bin/node", + "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", + ], + "pid": 3756, + "ppid": 1, + "title": "node /app/server.js", + }, + "request": Object { + "headers": Object { + "connection": "close", + "host": "opbeans-node:3000", + "user-agent": "workload/2.4.3", + }, + "http_version": "1.1", + "method": "GET", + "socket": Object { + "encrypted": false, + "remote_address": "::ffff:172.18.0.10", + }, + "url": Object { + "full": "http://opbeans-node:3000/api/types", + "hostname": "opbeans-node", + "pathname": "/api/types", + "port": "3000", + "protocol": "http:", + "raw": "/api/types", + }, + }, + "response": Object { + "headers": Object { + "connection": "close", + "content-length": "112", + "content-type": "application/json; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:53:44 GMT", + "etag": "W/\\"70-1z6hT7P1WHgBgS/BeUEVeHhOCQU\\"", + "x-powered-by": "Express", + }, + "status_code": 200, + }, + "service": Object { + "agent": Object { + "name": "nodejs", + "version": "1.14.2", + }, + "language": Object { + "name": "javascript", + }, + "name": "opbeans-node", + "runtime": Object { + "name": "node", + "version": "8.12.0", + }, + "version": "1.0.0", + }, + "system": Object { + "architecture": "x64", + "hostname": "98195610c255", + "ip": "172.18.0.10", + "platform": "linux", + }, + "tags": Object { + "foo": "bar", + "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", + "multi-line": "foo +bar +baz", + "this-is-a-very-long-tag-name-without-any-spaces": "test", + }, + "user": Object { + "email": "kimchy@elastic.co", + "id": "42", + "username": "kimchy", + }, + }, + "host": Object { + "name": "b359e3afece8", + }, + "processor": Object { + "event": "transaction", + "name": "transaction", + }, + "timestamp": Object { + "us": 1542574424978005, + }, + "trace": Object { + "id": "0d84126973411c19b470f2d9eea958d3", + }, + "transaction": Object { + "duration": Object { + "us": 7891, + }, + "id": "0f10668e4fb3adc7", + "name": "GET /api/types", + "result": "HTTP 2xx", + "sampled": true, + "span_count": Object { + "started": 2, + }, + "type": "request", + }, + }, + "transactionsPerMinute": 75789.47368421052, + }, + Object { + "averageResponseTime": 10584.05144193297, + "impact": 1.2808106158729446, + "name": "GET /api/orders/:id", + "p95": 26555.399999999998, + "sample": Object { + "@timestamp": "2018-11-18T20:51:36.949Z", + "agent": Object { + "hostname": "b359e3afece8", + "type": "apm-server", + "version": "7.0.0-alpha1", + }, + "context": Object { + "custom": Object { + "containerId": 5999, + }, + "process": Object { + "argv": Array [ + "/usr/local/bin/node", + "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", + ], + "pid": 3475, + "ppid": 1, + "title": "node /app/server.js", + }, + "request": Object { + "headers": Object { + "connection": "close", + "host": "opbeans-node:3000", + "user-agent": "workload/2.4.3", + }, + "http_version": "1.1", + "method": "GET", + "socket": Object { + "encrypted": false, + "remote_address": "::ffff:172.18.0.10", + }, + "url": Object { + "full": "http://opbeans-node:3000/api/orders/183", + "hostname": "opbeans-node", + "pathname": "/api/orders/183", + "port": "3000", + "protocol": "http:", + "raw": "/api/orders/183", + }, + }, + "response": Object { + "headers": Object { + "connection": "close", + "content-length": "0", + "date": "Sun, 18 Nov 2018 20:51:36 GMT", + "x-powered-by": "Express", + }, + "status_code": 404, + }, + "service": Object { + "agent": Object { + "name": "nodejs", + "version": "1.14.2", + }, + "language": Object { + "name": "javascript", + }, + "name": "opbeans-node", + "runtime": Object { + "name": "node", + "version": "8.12.0", + }, + "version": "1.0.0", + }, + "system": Object { + "architecture": "x64", + "hostname": "98195610c255", + "ip": "172.18.0.10", + "platform": "linux", + }, + "tags": Object { + "foo": "bar", + "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", + "multi-line": "foo +bar +baz", + "this-is-a-very-long-tag-name-without-any-spaces": "test", + }, + "user": Object { + "email": "kimchy@elastic.co", + "id": "42", + "username": "kimchy", + }, + }, + "host": Object { + "name": "b359e3afece8", + }, + "processor": Object { + "event": "transaction", + "name": "transaction", + }, + "timestamp": Object { + "us": 1542574296949004, + }, + "trace": Object { + "id": "dab6421fa44a6869887e0edf32e1ad6f", + }, + "transaction": Object { + "duration": Object { + "us": 5906, + }, + "id": "937ef5588454f74a", + "name": "GET /api/orders/:id", + "result": "HTTP 4xx", + "sampled": true, + "span_count": Object { + "started": 1, + }, + "type": "request", + }, + }, + "transactionsPerMinute": 40515.789473684206, + }, + Object { + "averageResponseTime": 10548.218597063622, + "impact": 1.8338764008269306, + "name": "GET /api/products/:id", + "p95": 28413.383333333328, + "sample": Object { + "@timestamp": "2018-11-18T20:52:57.963Z", + "agent": Object { + "hostname": "b359e3afece8", + "type": "apm-server", + "version": "7.0.0-alpha1", + }, + "context": Object { + "custom": Object { + "containerId": 7184, + }, + "process": Object { + "argv": Array [ + "/usr/local/bin/node", + "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", + ], + "pid": 3686, + "ppid": 1, + "title": "node /app/server.js", + }, + "request": Object { + "headers": Object { + "connection": "close", + "host": "opbeans-node:3000", + "user-agent": "workload/2.4.3", + }, + "http_version": "1.1", + "method": "GET", + "socket": Object { + "encrypted": false, + "remote_address": "::ffff:172.18.0.10", + }, + "url": Object { + "full": "http://opbeans-node:3000/api/products/3", + "hostname": "opbeans-node", + "pathname": "/api/products/3", + "port": "3000", + "protocol": "http:", + "raw": "/api/products/3", + }, + }, + "response": Object { + "headers": Object { + "connection": "close", + "content-length": "231", + "content-type": "application/json; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:52:57 GMT", + "etag": "W/\\"e7-kkuzj37GZDzXDh0CWqh5Gan0VO4\\"", + "x-powered-by": "Express", + }, + "status_code": 200, + }, + "service": Object { + "agent": Object { + "name": "nodejs", + "version": "1.14.2", + }, + "language": Object { + "name": "javascript", + }, + "name": "opbeans-node", + "runtime": Object { + "name": "node", + "version": "8.12.0", + }, + "version": "1.0.0", + }, + "system": Object { + "architecture": "x64", + "hostname": "98195610c255", + "ip": "172.18.0.10", + "platform": "linux", + }, + "tags": Object { + "foo": "bar", + "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", + "multi-line": "foo +bar +baz", + "this-is-a-very-long-tag-name-without-any-spaces": "test", + }, + "user": Object { + "email": "kimchy@elastic.co", + "id": "42", + "username": "kimchy", + }, + }, + "host": Object { + "name": "b359e3afece8", + }, + "processor": Object { + "event": "transaction", + "name": "transaction", + }, + "timestamp": Object { + "us": 1542574377963005, + }, + "trace": Object { + "id": "ca86ec845e412e4b4506a715d51548ec", + }, + "transaction": Object { + "duration": Object { + "us": 6959, + }, + "id": "d324897ffb7ebcdc", + "name": "GET /api/products/:id", + "result": "HTTP 2xx", + "sampled": true, + "span_count": Object { + "started": 1, + }, + "type": "request", + }, + }, + "transactionsPerMinute": 58073.68421052631, + }, + Object { + "averageResponseTime": 9868.217894736843, + "impact": 1.7722323979309487, + "name": "GET /api/customers/:id", + "p95": 27486.5, + "sample": Object { + "@timestamp": "2018-11-18T20:52:56.797Z", + "agent": Object { + "hostname": "b359e3afece8", + "type": "apm-server", + "version": "7.0.0-alpha1", + }, + "context": Object { + "custom": Object { + "containerId": 8225, + }, + "process": Object { + "argv": Array [ + "/usr/local/bin/node", + "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", + ], + "pid": 3686, + "ppid": 1, + "title": "node /app/server.js", + }, + "request": Object { + "headers": Object { + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "elastic-apm-traceparent": "00-e6140d30363f18b585f5d3b753f4d025-aa82e2c847265626-01", + "host": "opbeans-node:3000", + "user-agent": "python-requests/2.20.0", + }, + "http_version": "1.1", + "method": "GET", + "socket": Object { + "encrypted": false, + "remote_address": "::ffff:172.18.0.6", + }, + "url": Object { + "full": "http://opbeans-node:3000/api/customers/700", + "hostname": "opbeans-node", + "pathname": "/api/customers/700", + "port": "3000", + "protocol": "http:", + "raw": "/api/customers/700", + }, + }, + "response": Object { + "headers": Object { + "connection": "keep-alive", + "content-length": "193", + "content-type": "application/json; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:52:56 GMT", + "etag": "W/\\"c1-LbuhkuLzFyZ0H+7+JQGA5b0kvNs\\"", + "x-powered-by": "Express", + }, + "status_code": 200, + }, + "service": Object { + "agent": Object { + "name": "nodejs", + "version": "1.14.2", + }, + "language": Object { + "name": "javascript", + }, + "name": "opbeans-node", + "runtime": Object { + "name": "node", + "version": "8.12.0", + }, + "version": "1.0.0", + }, + "system": Object { + "architecture": "x64", + "hostname": "98195610c255", + "ip": "172.18.0.10", + "platform": "linux", + }, + "tags": Object { + "foo": "bar", + "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", + "multi-line": "foo +bar +baz", + "this-is-a-very-long-tag-name-without-any-spaces": "test", + }, + "user": Object { + "email": "kimchy@elastic.co", + "id": "42", + "username": "kimchy", + }, + }, + "host": Object { + "name": "b359e3afece8", + }, + "parent": Object { + "id": "aa82e2c847265626", + }, + "processor": Object { + "event": "transaction", + "name": "transaction", + }, + "timestamp": Object { + "us": 1542574376797031, + }, + "trace": Object { + "id": "e6140d30363f18b585f5d3b753f4d025", + }, + "transaction": Object { + "duration": Object { + "us": 9735, + }, + "id": "60e230d12f3f0960", + "name": "GET /api/customers/:id", + "result": "HTTP 2xx", + "sampled": true, + "span_count": Object { + "started": 1, + }, + "type": "request", + }, + }, + "transactionsPerMinute": 59999.99999999999, + }, + Object { + "averageResponseTime": 5192.9, + "impact": 0, + "name": "POST unknown route", + "p95": 13230.5, + "sample": Object { + "@timestamp": "2018-11-18T18:43:50.994Z", + "agent": Object { + "hostname": "b359e3afece8", + "type": "apm-server", + "version": "7.0.0-alpha1", + }, + "context": Object { + "custom": Object { + "containerId": 6102, + }, + "process": Object { + "argv": Array [ + "/usr/local/bin/node", + "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", + ], + "pid": 19196, + "ppid": 1, + "title": "node /app/server.js", + }, + "request": Object { + "body": "[REDACTED]", + "headers": Object { + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "content-length": "380", + "content-type": "multipart/form-data; boundary=2b2e40be188a4cb5a56c05a0c182f6c9", + "elastic-apm-traceparent": "00-19688959ea6cbccda8013c11566ea329-1fc3665eef2dcdfc-01", + "host": "172.18.0.9:3000", + "user-agent": "Python/3.7 aiohttp/3.3.2", + "x-forwarded-for": "172.18.0.11", + }, + "http_version": "1.1", + "method": "POST", + "socket": Object { + "encrypted": false, + "remote_address": "::ffff:172.18.0.9", + }, + "url": Object { + "full": "http://172.18.0.9:3000/api/orders/csv", + "hostname": "172.18.0.9", + "pathname": "/api/orders/csv", + "port": "3000", + "protocol": "http:", + "raw": "/api/orders/csv", + }, + }, + "response": Object { + "headers": Object { + "connection": "keep-alive", + "content-length": "154", + "content-security-policy": "default-src 'self'", + "content-type": "text/html; charset=utf-8", + "date": "Sun, 18 Nov 2018 18:43:50 GMT", + "x-content-type-options": "nosniff", + "x-powered-by": "Express", + }, + "status_code": 404, + }, + "service": Object { + "agent": Object { + "name": "nodejs", + "version": "1.14.2", + }, + "language": Object { + "name": "javascript", + }, + "name": "opbeans-node", + "runtime": Object { + "name": "node", + "version": "8.12.0", + }, + "version": "1.0.0", + }, + "system": Object { + "architecture": "x64", + "hostname": "98195610c255", + "ip": "172.18.0.10", + "platform": "linux", + }, + "tags": Object { + "foo": "bar", + "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", + "multi-line": "foo +bar +baz", + "this-is-a-very-long-tag-name-without-any-spaces": "test", + }, + "user": Object { + "email": "kimchy@elastic.co", + "id": "42", + "username": "kimchy", + }, + }, + "host": Object { + "name": "b359e3afece8", + }, + "parent": Object { + "id": "1fc3665eef2dcdfc", + }, + "processor": Object { + "event": "transaction", + "name": "transaction", + }, + "timestamp": Object { + "us": 1542566630994005, + }, + "trace": Object { + "id": "19688959ea6cbccda8013c11566ea329", + }, + "transaction": Object { + "duration": Object { + "us": 3467, + }, + "id": "92c3ceea57899061", + "name": "POST unknown route", + "result": "HTTP 4xx", + "sampled": true, + "span_count": Object { + "started": 0, + }, + "type": "request", + }, + }, + "transactionsPerMinute": 631.578947368421, + }, + Object { + "averageResponseTime": 4694.005586592179, + "impact": 0.1498514997591876, + "name": "GET /is-it-coffee-time", + "p95": 11022.99999999992, + "sample": Object { + "@timestamp": "2018-11-18T20:46:19.317Z", + "agent": Object { + "hostname": "b359e3afece8", + "type": "apm-server", + "version": "7.0.0-alpha1", + }, + "context": Object { + "custom": Object { + "containerId": 8593, + }, + "process": Object { + "argv": Array [ + "/usr/local/bin/node", + "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", + ], + "pid": 2760, + "ppid": 1, + "title": "node /app/server.js", + }, + "request": Object { + "headers": Object { + "connection": "close", + "host": "opbeans-node:3000", + "user-agent": "workload/2.4.3", + }, + "http_version": "1.1", + "method": "GET", + "socket": Object { + "encrypted": false, + "remote_address": "::ffff:172.18.0.10", + }, + "url": Object { + "full": "http://opbeans-node:3000/is-it-coffee-time", + "hostname": "opbeans-node", + "pathname": "/is-it-coffee-time", + "port": "3000", + "protocol": "http:", + "raw": "/is-it-coffee-time", + }, + }, + "response": Object { + "headers": Object { + "connection": "close", + "content-length": "148", + "content-security-policy": "default-src 'self'", + "content-type": "text/html; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:46:19 GMT", + "x-content-type-options": "nosniff", + "x-powered-by": "Express", + }, + "status_code": 500, + }, + "service": Object { + "agent": Object { + "name": "nodejs", + "version": "1.14.2", + }, + "language": Object { + "name": "javascript", + }, + "name": "opbeans-node", + "runtime": Object { + "name": "node", + "version": "8.12.0", + }, + "version": "1.0.0", + }, + "system": Object { + "architecture": "x64", + "hostname": "98195610c255", + "ip": "172.18.0.10", + "platform": "linux", + }, + "tags": Object { + "foo": "bar", + "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", + "multi-line": "foo +bar +baz", + "this-is-a-very-long-tag-name-without-any-spaces": "test", + }, + "user": Object { + "email": "kimchy@elastic.co", + "id": "42", + "username": "kimchy", + }, + }, + "host": Object { + "name": "b359e3afece8", + }, + "processor": Object { + "event": "transaction", + "name": "transaction", + }, + "timestamp": Object { + "us": 1542573979317007, + }, + "trace": Object { + "id": "821812b416de4c73ced87f8777fa46a6", + }, + "transaction": Object { + "duration": Object { + "us": 4253, + }, + "id": "319a5c555a1ab207", + "name": "GET /is-it-coffee-time", + "result": "HTTP 5xx", + "sampled": true, + "span_count": Object { + "started": 0, + }, + "type": "request", + }, + }, + "transactionsPerMinute": 11305.263157894737, + }, + Object { + "averageResponseTime": 4549.889880952381, + "impact": 0.1354336505457395, + "name": "GET /throw-error", + "p95": 7719.700000000001, + "sample": Object { + "@timestamp": "2018-11-18T20:47:10.714Z", + "agent": Object { + "hostname": "b359e3afece8", + "type": "apm-server", + "version": "7.0.0-alpha1", + }, + "context": Object { + "custom": Object { + "containerId": 7220, + }, + "process": Object { + "argv": Array [ + "/usr/local/bin/node", + "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", + ], + "pid": 2895, + "ppid": 1, + "title": "node /app/server.js", + }, + "request": Object { + "headers": Object { + "connection": "close", + "host": "opbeans-node:3000", + "user-agent": "workload/2.4.3", + }, + "http_version": "1.1", + "method": "GET", + "socket": Object { + "encrypted": false, + "remote_address": "::ffff:172.18.0.10", + }, + "url": Object { + "full": "http://opbeans-node:3000/throw-error", + "hostname": "opbeans-node", + "pathname": "/throw-error", + "port": "3000", + "protocol": "http:", + "raw": "/throw-error", + }, + }, + "response": Object { + "headers": Object { + "connection": "close", + "content-length": "148", + "content-security-policy": "default-src 'self'", + "content-type": "text/html; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:47:10 GMT", + "x-content-type-options": "nosniff", + "x-powered-by": "Express", + }, + "status_code": 500, + }, + "service": Object { + "agent": Object { + "name": "nodejs", + "version": "1.14.2", + }, + "language": Object { + "name": "javascript", + }, + "name": "opbeans-node", + "runtime": Object { + "name": "node", + "version": "8.12.0", + }, + "version": "1.0.0", + }, + "system": Object { + "architecture": "x64", + "hostname": "98195610c255", + "ip": "172.18.0.10", + "platform": "linux", + }, + "tags": Object { + "foo": "bar", + "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", + "multi-line": "foo +bar +baz", + "this-is-a-very-long-tag-name-without-any-spaces": "test", + }, + "user": Object { + "email": "kimchy@elastic.co", + "id": "42", + "username": "kimchy", + }, + }, + "host": Object { + "name": "b359e3afece8", + }, + "processor": Object { + "event": "transaction", + "name": "transaction", + }, + "timestamp": Object { + "us": 1542574030714012, + }, + "trace": Object { + "id": "6c0ef23e1f963f304ce440a909914d35", + }, + "transaction": Object { + "duration": Object { + "us": 4458, + }, + "id": "ecd187dc53f09fbd", + "name": "GET /throw-error", + "result": "HTTP 5xx", + "sampled": true, + "span_count": Object { + "started": 0, + }, + "type": "request", + }, + }, + "transactionsPerMinute": 10610.526315789473, + }, + Object { + "averageResponseTime": 3504.5108924806746, + "impact": 2.36009934580083, + "name": "GET *", + "p95": 11431.738095238095, + "sample": Object { + "@timestamp": "2018-11-18T20:53:42.493Z", + "agent": Object { + "hostname": "b359e3afece8", + "type": "apm-server", + "version": "7.0.0-alpha1", + }, + "context": Object { + "custom": Object { + "containerId": 6446, + }, + "process": Object { + "argv": Array [ + "/usr/local/bin/node", + "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", + ], + "pid": 3756, + "ppid": 1, + "title": "node /app/server.js", + }, + "request": Object { + "headers": Object { + "accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "host": "opbeans-node:3000", + "if-modified-since": "Mon, 12 Nov 2018 10:27:07 GMT", + "if-none-match": "W/\\"280-1670775e878\\"", + "upgrade-insecure-requests": "1", + "user-agent": "Chromeless 1.4.0", + }, + "http_version": "1.1", + "method": "GET", + "socket": Object { + "encrypted": false, + "remote_address": "::ffff:172.18.0.7", + }, + "url": Object { + "full": "http://opbeans-node:3000/dashboard", + "hostname": "opbeans-node", + "pathname": "/dashboard", + "port": "3000", + "protocol": "http:", + "raw": "/dashboard", + }, + }, + "response": Object { + "headers": Object { + "accept-ranges": "bytes", + "cache-control": "public, max-age=0", + "connection": "keep-alive", + "date": "Sun, 18 Nov 2018 20:53:42 GMT", + "etag": "W/\\"280-1670775e878\\"", + "last-modified": "Mon, 12 Nov 2018 10:27:07 GMT", + "x-powered-by": "Express", + }, + "status_code": 304, + }, + "service": Object { + "agent": Object { + "name": "nodejs", + "version": "1.14.2", + }, + "language": Object { + "name": "javascript", + }, + "name": "opbeans-node", + "runtime": Object { + "name": "node", + "version": "8.12.0", + }, + "version": "1.0.0", + }, + "system": Object { + "architecture": "x64", + "hostname": "98195610c255", + "ip": "172.18.0.10", + "platform": "linux", + }, + "tags": Object { + "foo": "bar", + "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", + "multi-line": "foo +bar +baz", + "this-is-a-very-long-tag-name-without-any-spaces": "test", + }, + "user": Object { + "email": "kimchy@elastic.co", + "id": "42", + "username": "kimchy", + }, + }, + "host": Object { + "name": "b359e3afece8", + }, + "processor": Object { + "event": "transaction", + "name": "transaction", + }, + "timestamp": Object { + "us": 1542574422493006, + }, + "trace": Object { + "id": "7efb6ade88cdea20cd96ca482681cde7", + }, + "transaction": Object { + "duration": Object { + "us": 1901, + }, + "id": "f5fc4621949b63fb", + "name": "GET *", + "result": "HTTP 3xx", + "sampled": true, + "span_count": Object { + "started": 0, + }, + "type": "request", + }, + }, + "transactionsPerMinute": 224684.21052631576, + }, + Object { + "averageResponseTime": 2742.4615384615386, + "impact": 0.08501029113483448, + "name": "OPTIONS unknown route", + "p95": 4370.000000000002, + "sample": Object { + "@timestamp": "2018-11-18T20:49:00.707Z", + "agent": Object { + "hostname": "b359e3afece8", + "type": "apm-server", + "version": "7.0.0-alpha1", + }, + "context": Object { + "custom": Object { + "containerId": 3775, + }, + "process": Object { + "argv": Array [ + "/usr/local/bin/node", + "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", + ], + "pid": 3142, + "ppid": 1, + "title": "node /app/server.js", + }, + "request": Object { + "headers": Object { + "connection": "close", + "content-length": "0", + "host": "opbeans-node:3000", + "user-agent": "workload/2.4.3", + }, + "http_version": "1.1", + "method": "OPTIONS", + "socket": Object { + "encrypted": false, + "remote_address": "::ffff:172.18.0.10", + }, + "url": Object { + "full": "http://opbeans-node:3000/", + "hostname": "opbeans-node", + "pathname": "/", + "port": "3000", + "protocol": "http:", + "raw": "/", + }, + }, + "response": Object { + "headers": Object { + "allow": "GET,HEAD", + "connection": "close", + "content-length": "8", + "content-type": "text/html; charset=utf-8", + "date": "Sun, 18 Nov 2018 20:49:00 GMT", + "etag": "W/\\"8-ZRAf8oNBS3Bjb/SU2GYZCmbtmXg\\"", + "x-powered-by": "Express", + }, + "status_code": 200, + }, + "service": Object { + "agent": Object { + "name": "nodejs", + "version": "1.14.2", + }, + "language": Object { + "name": "javascript", + }, + "name": "opbeans-node", + "runtime": Object { + "name": "node", + "version": "8.12.0", + }, + "version": "1.0.0", + }, + "system": Object { + "architecture": "x64", + "hostname": "98195610c255", + "ip": "172.18.0.10", + "platform": "linux", + }, + "tags": Object { + "foo": "bar", + "lorem": "ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.", + "multi-line": "foo +bar +baz", + "this-is-a-very-long-tag-name-without-any-spaces": "test", + }, + "user": Object { + "email": "kimchy@elastic.co", + "id": "42", + "username": "kimchy", + }, + }, + "host": Object { + "name": "b359e3afece8", + }, + "processor": Object { + "event": "transaction", + "name": "transaction", + }, + "timestamp": Object { + "us": 1542574140707006, + }, + "trace": Object { + "id": "469e3e5f91ffe3195a8e58cdd1cdefa8", + }, + "transaction": Object { + "duration": Object { + "us": 2371, + }, + "id": "a8c87ebc7ec68bc0", + "name": "OPTIONS unknown route", + "result": "HTTP 2xx", + "sampled": true, + "span_count": Object { + "started": 0, + }, + "type": "request", + }, + }, + "transactionsPerMinute": 11494.736842105262, + }, + Object { + "averageResponseTime": 2651.8784461553205, + "impact": 15.770246498769827, + "name": "GET static file", + "p95": 6140.579335038363, + "sample": Object { + "@timestamp": "2018-11-18T20:53:43.304Z", + "agent": Object { + "hostname": "b359e3afece8", + "type": "apm-server", + "version": "7.0.0-alpha1", + }, + "context": Object { + "process": Object { + "argv": Array [ + "/usr/local/bin/node", + "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", + ], + "pid": 3756, + "ppid": 1, + "title": "node /app/server.js", + }, + "request": Object { + "headers": Object { + "accept": "*/*", + "host": "opbeans-node:3000", + "user-agent": "curl/7.38.0", + }, + "http_version": "1.1", + "method": "GET", + "socket": Object { + "encrypted": false, + "remote_address": "::ffff:172.18.0.10", + }, + "url": Object { + "full": "http://opbeans-node:3000/", + "hostname": "opbeans-node", + "pathname": "/", + "port": "3000", + "protocol": "http:", + "raw": "/", + }, + }, + "response": Object { + "headers": Object { + "accept-ranges": "bytes", + "cache-control": "public, max-age=0", + "connection": "keep-alive", + "content-length": "640", + "content-type": "text/html; charset=UTF-8", + "date": "Sun, 18 Nov 2018 20:53:43 GMT", + "etag": "W/\\"280-1670775e878\\"", + "last-modified": "Mon, 12 Nov 2018 10:27:07 GMT", + "x-powered-by": "Express", + }, + "status_code": 200, + }, + "service": Object { + "agent": Object { + "name": "nodejs", + "version": "1.14.2", + }, + "language": Object { + "name": "javascript", + }, + "name": "opbeans-node", + "runtime": Object { + "name": "node", + "version": "8.12.0", + }, + "version": "1.0.0", + }, + "system": Object { + "architecture": "x64", + "hostname": "98195610c255", + "ip": "172.18.0.10", + "platform": "linux", + }, + }, + "host": Object { + "name": "b359e3afece8", + }, + "processor": Object { + "event": "transaction", + "name": "transaction", + }, + "timestamp": Object { + "us": 1542574423304006, + }, + "trace": Object { + "id": "b303d2a4a007946b63b9db7fafe639a0", + }, + "transaction": Object { + "duration": Object { + "us": 1801, + }, + "id": "2869c13633534be5", + "name": "GET static file", + "result": "HTTP 2xx", + "sampled": true, + "span_count": Object { + "started": 0, + }, + "type": "request", + }, + }, + "transactionsPerMinute": 1977031.5789473683, + }, + Object { + "averageResponseTime": 1422.926672899693, + "impact": 1.002712481568783, + "name": "GET unknown route", + "p95": 2311.885238095238, + "sample": Object { + "@timestamp": "2018-11-18T20:53:42.504Z", + "agent": Object { + "hostname": "b359e3afece8", + "type": "apm-server", + "version": "7.0.0-alpha1", + }, + "context": Object { + "process": Object { + "argv": Array [ + "/usr/local/bin/node", + "/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js", + ], + "pid": 3756, + "ppid": 1, + "title": "node /app/server.js", + }, + "request": Object { + "headers": Object { + "accept": "*/*", + "accept-encoding": "gzip, deflate", + "connection": "keep-alive", + "host": "opbeans-node:3000", + "referer": "http://opbeans-node:3000/dashboard", + "user-agent": "Chromeless 1.4.0", + }, + "http_version": "1.1", + "method": "GET", + "socket": Object { + "encrypted": false, + "remote_address": "::ffff:172.18.0.7", + }, + "url": Object { + "full": "http://opbeans-node:3000/rum-config.js", + "hostname": "opbeans-node", + "pathname": "/rum-config.js", + "port": "3000", + "protocol": "http:", + "raw": "/rum-config.js", + }, + }, + "response": Object { + "headers": Object { + "connection": "keep-alive", + "content-length": "172", + "content-type": "text/javascript", + "date": "Sun, 18 Nov 2018 20:53:42 GMT", + "x-powered-by": "Express", + }, + "status_code": 200, + }, + "service": Object { + "agent": Object { + "name": "nodejs", + "version": "1.14.2", + }, + "language": Object { + "name": "javascript", + }, + "name": "opbeans-node", + "runtime": Object { + "name": "node", + "version": "8.12.0", + }, + "version": "1.0.0", + }, + "system": Object { + "architecture": "x64", + "hostname": "98195610c255", + "ip": "172.18.0.10", + "platform": "linux", + }, + }, + "host": Object { + "name": "b359e3afece8", + }, + "processor": Object { + "event": "transaction", + "name": "transaction", + }, + "timestamp": Object { + "us": 1542574422504004, + }, + "trace": Object { + "id": "4399e7233e6e7b77e70c2fff111b8f28", + }, + "transaction": Object { + "duration": Object { + "us": 911, + }, + "id": "107881ae2be1b56d", + "name": "GET unknown route", + "result": "HTTP 2xx", + "sampled": true, + "span_count": Object { + "started": 0, + }, + "type": "request", + }, + }, + "transactionsPerMinute": 236431.5789473684, + }, +] +`; diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.test.ts b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.test.ts new file mode 100644 index 0000000000000..cfbffe77222f3 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.test.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ESResponse, transactionGroupsFetcher } from './fetcher'; + +describe('transactionGroupsFetcher', () => { + let res: ESResponse; + let clientSpy: jest.Mock; + beforeEach(async () => { + clientSpy = jest.fn().mockResolvedValue('ES response'); + + const setup = { + start: 1528113600000, + end: 1528977600000, + client: clientSpy, + config: { + get: () => 'myIndex' as any + } + }; + const bodyQuery = { my: 'bodyQuery' }; + res = await transactionGroupsFetcher(setup, bodyQuery); + }); + + it('should call client with correct query', () => { + expect(clientSpy.mock.calls).toMatchSnapshot(); + }); + + it('should return correct response', () => { + expect(res).toBe('ES response'); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts new file mode 100644 index 0000000000000..9ab287402c561 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transaction_groups/fetcher.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AggregationSearchResponse } from 'elasticsearch'; +import { StringMap } from 'x-pack/plugins/apm/typings/common'; +import { + TRANSACTION_DURATION, + TRANSACTION_NAME +} from '../../../common/constants'; +import { Transaction } from '../../../typings/Transaction'; +import { Setup } from '../helpers/setup_request'; + +interface Bucket { + key: string; + doc_count: number; + avg: { + value: number; + }; + p95: { + values: { + '95.0': number; + }; + }; + sample: { + hits: { + total: number; + max_score: number | null; + hits: Array<{ + _source: Transaction; + }>; + }; + }; +} + +interface Aggs { + transactions: { + buckets: Bucket[]; + }; +} + +export type ESResponse = AggregationSearchResponse; + +export function transactionGroupsFetcher( + setup: Setup, + bodyQuery: StringMap +): Promise { + const { esFilterQuery, client, config } = setup; + const params = { + index: config.get('apm_oss.transactionIndices'), + body: { + size: 0, + query: bodyQuery, + aggs: { + transactions: { + terms: { + field: `${TRANSACTION_NAME}.keyword`, + order: { avg: 'desc' }, + size: 100 + }, + aggs: { + sample: { + top_hits: { + size: 1, + sort: [{ '@timestamp': { order: 'desc' } }] + } + }, + avg: { avg: { field: TRANSACTION_DURATION } }, + p95: { + percentiles: { field: TRANSACTION_DURATION, percents: [95] } + } + } + } + } + } + }; + + if (esFilterQuery) { + params.body.query.bool.filter.push(esFilterQuery); + } + + return client('search', params); +} diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/index.ts b/x-pack/plugins/apm/server/lib/transaction_groups/index.ts new file mode 100644 index 0000000000000..ae59a2f8e5e4f --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transaction_groups/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { StringMap } from 'x-pack/plugins/apm/typings/common'; +import { Setup } from '../helpers/setup_request'; +import { transactionGroupsFetcher } from './fetcher'; +import { transactionGroupsTransformer } from './transform'; + +export async function getTransactionGroups(setup: Setup, bodyQuery: StringMap) { + const { start, end } = setup; + const response = await transactionGroupsFetcher(setup, bodyQuery); + return transactionGroupsTransformer({ + response, + start, + end + }); +} diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/mock-responses/transactionGroupsResponse.ts b/x-pack/plugins/apm/server/lib/transaction_groups/mock-responses/transactionGroupsResponse.ts new file mode 100644 index 0000000000000..08492ee7a7839 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transaction_groups/mock-responses/transactionGroupsResponse.ts @@ -0,0 +1,2700 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ESResponse } from '../fetcher'; + +export const transactionGroupsResponse = ({ + took: 139, + timed_out: false, + _shards: { total: 44, successful: 44, skipped: 0, failed: 0 }, + hits: { total: 131557, max_score: null, hits: [] }, + aggregations: { + transactions: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'POST /api/orders', + doc_count: 180, + avg: { value: 255966.30555555556 }, + p95: { values: { '95.0': 320238.5 } }, + sample: { + hits: { + total: 180, + max_score: null, + hits: [ + { + _index: 'apm-7.0.0-alpha1-2018.11.18', + _type: 'doc', + _id: 'TBGQKGcBVMxP8Wrugd8L', + _score: null, + _source: { + '@timestamp': '2018-11-18T20:43:32.010Z', + context: { + request: { + http_version: '1.1', + method: 'POST', + url: { + port: '3000', + pathname: '/api/orders', + full: 'http://opbeans-node:3000/api/orders', + raw: '/api/orders', + protocol: 'http:', + hostname: 'opbeans-node' + }, + socket: { + encrypted: false, + remote_address: '::ffff:172.18.0.10' + }, + headers: { + host: 'opbeans-node:3000', + accept: 'application/json', + 'content-type': 'application/json', + 'content-length': '129', + connection: 'close', + 'user-agent': 'workload/2.4.3' + }, + body: '[REDACTED]' + }, + response: { + status_code: 200, + headers: { + date: 'Sun, 18 Nov 2018 20:43:32 GMT', + connection: 'close', + 'x-powered-by': 'Express', + 'content-type': 'application/json; charset=utf-8', + 'content-length': '13', + etag: 'W/"d-g9K2iK4ordyN88lGL4LmPlYNfhc"' + } + }, + system: { + ip: '172.18.0.10', + hostname: '98195610c255', + architecture: 'x64', + platform: 'linux' + }, + process: { + argv: [ + '/usr/local/bin/node', + '/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js' + ], + pid: 2413, + ppid: 1, + title: 'node /app/server.js' + }, + service: { + runtime: { name: 'node', version: '8.12.0' }, + name: 'opbeans-node', + agent: { name: 'nodejs', version: '1.14.2' }, + version: '1.0.0', + language: { name: 'javascript' } + }, + user: { + id: '42', + username: 'kimchy', + email: 'kimchy@elastic.co' + }, + tags: { + lorem: + 'ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.', + 'this-is-a-very-long-tag-name-without-any-spaces': + 'test', + 'multi-line': 'foo\nbar\nbaz', + foo: 'bar' + }, + custom: { containerId: 4669 } + }, + trace: { id: '2b1252a338249daeecf6afb0c236e31b' }, + timestamp: { us: 1542573812010006 }, + agent: { + type: 'apm-server', + hostname: 'b359e3afece8', + version: '7.0.0-alpha1' + }, + host: { name: 'b359e3afece8' }, + processor: { + name: 'transaction', + event: 'transaction' + }, + transaction: { + sampled: true, + span_count: { started: 16 }, + id: '2c9f39e9ec4a0111', + name: 'POST /api/orders', + duration: { us: 291572 }, + type: 'request', + result: 'HTTP 2xx' + } + }, + sort: [1542573812010] + } + ] + } + } + }, + { + key: 'GET /api', + doc_count: 21911, + avg: { value: 48021.972616494 }, + p95: { values: { '95.0': 67138.18364917398 } }, + sample: { + hits: { + total: 21911, + max_score: null, + hits: [ + { + _index: 'apm-7.0.0-alpha1-2018.11.18', + _type: 'doc', + _id: '_hKZKGcBVMxP8Wru1G13', + _score: null, + _source: { + '@timestamp': '2018-11-18T20:53:44.070Z', + timestamp: { us: 1542574424070007 }, + agent: { + hostname: 'b359e3afece8', + version: '7.0.0-alpha1', + type: 'apm-server' + }, + host: { name: 'b359e3afece8' }, + processor: { + name: 'transaction', + event: 'transaction' + }, + transaction: { + sampled: true, + span_count: { started: 1 }, + id: 'a78bca581dcd8ff8', + name: 'GET /api', + duration: { us: 8684 }, + type: 'request', + result: 'HTTP 4xx' + }, + context: { + response: { + status_code: 404, + headers: { + 'content-type': 'application/json;charset=UTF-8', + 'transfer-encoding': 'chunked', + date: 'Sun, 18 Nov 2018 20:53:43 GMT', + connection: 'close', + 'x-powered-by': 'Express' + } + }, + system: { + hostname: '98195610c255', + architecture: 'x64', + platform: 'linux', + ip: '172.18.0.10' + }, + process: { + argv: [ + '/usr/local/bin/node', + '/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js' + ], + pid: 3756, + ppid: 1, + title: 'node /app/server.js' + }, + service: { + runtime: { name: 'node', version: '8.12.0' }, + name: 'opbeans-node', + agent: { version: '1.14.2', name: 'nodejs' }, + version: '1.0.0', + language: { name: 'javascript' } + }, + user: { + id: '42', + username: 'kimchy', + email: 'kimchy@elastic.co' + }, + tags: { + foo: 'bar', + lorem: + 'ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.', + 'this-is-a-very-long-tag-name-without-any-spaces': + 'test', + 'multi-line': 'foo\nbar\nbaz' + }, + custom: { containerId: 5176 }, + request: { + method: 'GET', + url: { + protocol: 'http:', + hostname: 'opbeans-node', + port: '3000', + pathname: '/api/types/3', + full: 'http://opbeans-node:3000/api/types/3', + raw: '/api/types/3' + }, + socket: { + encrypted: false, + remote_address: '::ffff:172.18.0.6' + }, + headers: { + 'accept-encoding': 'gzip, deflate', + accept: '*/*', + connection: 'keep-alive', + 'elastic-apm-traceparent': + '00-86c68779d8a65b06fb78e770ffc436a5-4aaea53dc1791183-01', + host: 'opbeans-node:3000', + 'user-agent': 'python-requests/2.20.0' + }, + http_version: '1.1' + } + }, + parent: { id: '4aaea53dc1791183' }, + trace: { id: '86c68779d8a65b06fb78e770ffc436a5' } + }, + sort: [1542574424070] + } + ] + } + } + }, + { + key: 'GET /api/orders', + doc_count: 3247, + avg: { value: 33265.03326147213 }, + p95: { values: { '95.0': 58827.489999999976 } }, + sample: { + hits: { + total: 3247, + max_score: null, + hits: [ + { + _index: 'apm-7.0.0-alpha1-2018.11.18', + _type: 'doc', + _id: '6BKZKGcBVMxP8Wru1G13', + _score: null, + _source: { + '@timestamp': '2018-11-18T20:53:40.973Z', + timestamp: { us: 1542574420973006 }, + agent: { + type: 'apm-server', + hostname: 'b359e3afece8', + version: '7.0.0-alpha1' + }, + host: { name: 'b359e3afece8' }, + processor: { + name: 'transaction', + event: 'transaction' + }, + transaction: { + type: 'request', + result: 'HTTP 2xx', + sampled: true, + span_count: { started: 2 }, + id: '89f200353eb50539', + name: 'GET /api/orders', + duration: { us: 23040 } + }, + context: { + user: { + username: 'kimchy', + email: 'kimchy@elastic.co', + id: '42' + }, + tags: { + foo: 'bar', + lorem: + 'ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.', + 'this-is-a-very-long-tag-name-without-any-spaces': + 'test', + 'multi-line': 'foo\nbar\nbaz' + }, + custom: { containerId: 408 }, + request: { + method: 'GET', + url: { + full: 'http://opbeans-node:3000/api/orders', + raw: '/api/orders', + protocol: 'http:', + hostname: 'opbeans-node', + port: '3000', + pathname: '/api/orders' + }, + socket: { + remote_address: '::ffff:172.18.0.10', + encrypted: false + }, + headers: { + 'user-agent': 'workload/2.4.3', + host: 'opbeans-node:3000', + connection: 'close' + }, + http_version: '1.1' + }, + response: { + status_code: 200, + headers: { + etag: 'W/"194bc-cOw6+iRf7XCeqMXHrle3IOig7tY"', + date: 'Sun, 18 Nov 2018 20:53:40 GMT', + connection: 'close', + 'x-powered-by': 'Express', + 'content-type': 'application/json; charset=utf-8', + 'content-length': '103612' + } + }, + system: { + platform: 'linux', + ip: '172.18.0.10', + hostname: '98195610c255', + architecture: 'x64' + }, + process: { + title: 'node /app/server.js', + argv: [ + '/usr/local/bin/node', + '/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js' + ], + pid: 3756, + ppid: 1 + }, + service: { + version: '1.0.0', + language: { name: 'javascript' }, + runtime: { name: 'node', version: '8.12.0' }, + name: 'opbeans-node', + agent: { version: '1.14.2', name: 'nodejs' } + } + }, + trace: { id: '0afce85f593cbbdd09949936fe964f0f' } + }, + sort: [1542574420973] + } + ] + } + } + }, + { + key: 'GET /log-message', + doc_count: 700, + avg: { value: 32900.72714285714 }, + p95: { values: { '95.0': 40444 } }, + sample: { + hits: { + total: 700, + max_score: null, + hits: [ + { + _index: 'apm-7.0.0-alpha1-2018.11.18', + _type: 'doc', + _id: 'qBKVKGcBVMxP8Wruqi_j', + _score: null, + _source: { + '@timestamp': '2018-11-18T20:49:09.225Z', + processor: { + name: 'transaction', + event: 'transaction' + }, + transaction: { + sampled: true, + span_count: { started: 0 }, + id: 'b9a8f96d7554d09f', + name: 'GET /log-message', + duration: { us: 32381 }, + type: 'request', + result: 'HTTP 5xx' + }, + context: { + user: { + id: '42', + username: 'kimchy', + email: 'kimchy@elastic.co' + }, + tags: { + lorem: + 'ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.', + 'this-is-a-very-long-tag-name-without-any-spaces': + 'test', + 'multi-line': 'foo\nbar\nbaz', + foo: 'bar' + }, + custom: { containerId: 321 }, + request: { + socket: { + remote_address: '::ffff:172.18.0.10', + encrypted: false + }, + headers: { + 'user-agent': 'workload/2.4.3', + host: 'opbeans-node:3000', + connection: 'close' + }, + http_version: '1.1', + method: 'GET', + url: { + raw: '/log-message', + protocol: 'http:', + hostname: 'opbeans-node', + port: '3000', + pathname: '/log-message', + full: 'http://opbeans-node:3000/log-message' + } + }, + response: { + status_code: 500, + headers: { + 'x-powered-by': 'Express', + 'content-type': 'text/html; charset=utf-8', + 'content-length': '24', + etag: 'W/"18-MS3VbhH7auHMzO0fUuNF6v14N/M"', + date: 'Sun, 18 Nov 2018 20:49:09 GMT', + connection: 'close' + } + }, + system: { + ip: '172.18.0.10', + hostname: '98195610c255', + architecture: 'x64', + platform: 'linux' + }, + process: { + title: 'node /app/server.js', + argv: [ + '/usr/local/bin/node', + '/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js' + ], + pid: 3142, + ppid: 1 + }, + service: { + language: { name: 'javascript' }, + runtime: { version: '8.12.0', name: 'node' }, + name: 'opbeans-node', + agent: { name: 'nodejs', version: '1.14.2' }, + version: '1.0.0' + } + }, + trace: { id: 'ba18b741cdd3ac83eca89a5fede47577' }, + timestamp: { us: 1542574149225004 }, + agent: { + type: 'apm-server', + hostname: 'b359e3afece8', + version: '7.0.0-alpha1' + }, + host: { name: 'b359e3afece8' } + }, + sort: [1542574149225] + } + ] + } + } + }, + { + key: 'GET /api/stats', + doc_count: 4639, + avg: { value: 32554.36257814184 }, + p95: { values: { '95.0': 59356.73611111111 } }, + sample: { + hits: { + total: 4639, + max_score: null, + hits: [ + { + _index: 'apm-7.0.0-alpha1-2018.11.18', + _type: 'doc', + _id: '9hKZKGcBVMxP8Wru1G13', + _score: null, + _source: { + '@timestamp': '2018-11-18T20:53:42.560Z', + trace: { id: '63ccc3b0929dafb7f2fbcabdc7f7af25' }, + timestamp: { us: 1542574422560002 }, + agent: { + hostname: 'b359e3afece8', + version: '7.0.0-alpha1', + type: 'apm-server' + }, + host: { name: 'b359e3afece8' }, + processor: { + name: 'transaction', + event: 'transaction' + }, + transaction: { + sampled: true, + span_count: { started: 7 }, + id: 'fb754e7628da2fb5', + name: 'GET /api/stats', + duration: { us: 28753 }, + type: 'request', + result: 'HTTP 3xx' + }, + context: { + response: { + headers: { + 'x-powered-by': 'Express', + etag: 'W/"77-uxKJrX5GSMJJWTKh3orUFAEVxSs"', + date: 'Sun, 18 Nov 2018 20:53:42 GMT', + connection: 'keep-alive' + }, + status_code: 304 + }, + system: { + ip: '172.18.0.10', + hostname: '98195610c255', + architecture: 'x64', + platform: 'linux' + }, + process: { + pid: 3756, + ppid: 1, + title: 'node /app/server.js', + argv: [ + '/usr/local/bin/node', + '/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js' + ] + }, + service: { + name: 'opbeans-node', + agent: { version: '1.14.2', name: 'nodejs' }, + version: '1.0.0', + language: { name: 'javascript' }, + runtime: { name: 'node', version: '8.12.0' } + }, + user: { + email: 'kimchy@elastic.co', + id: '42', + username: 'kimchy' + }, + tags: { + 'multi-line': 'foo\nbar\nbaz', + foo: 'bar', + lorem: + 'ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.', + 'this-is-a-very-long-tag-name-without-any-spaces': + 'test' + }, + custom: { containerId: 207 }, + request: { + url: { + protocol: 'http:', + hostname: 'opbeans-node', + port: '3000', + pathname: '/api/stats', + full: 'http://opbeans-node:3000/api/stats', + raw: '/api/stats' + }, + socket: { + remote_address: '::ffff:172.18.0.7', + encrypted: false + }, + headers: { + 'if-none-match': 'W/"77-uxKJrX5GSMJJWTKh3orUFAEVxSs"', + host: 'opbeans-node:3000', + connection: 'keep-alive', + 'user-agent': 'Chromeless 1.4.0', + 'elastic-apm-traceparent': + '00-63ccc3b0929dafb7f2fbcabdc7f7af25-821a787e73ab1563-01', + accept: '*/*', + referer: 'http://opbeans-node:3000/dashboard', + 'accept-encoding': 'gzip, deflate' + }, + http_version: '1.1', + method: 'GET' + } + }, + parent: { id: '821a787e73ab1563' } + }, + sort: [1542574422560] + } + ] + } + } + }, + { + key: 'GET /log-error', + doc_count: 736, + avg: { value: 32387.73641304348 }, + p95: { values: { '95.0': 40061.1 } }, + sample: { + hits: { + total: 736, + max_score: null, + hits: [ + { + _index: 'apm-7.0.0-alpha1-2018.11.18', + _type: 'doc', + _id: 'rBKYKGcBVMxP8Wru9mC0', + _score: null, + _source: { + '@timestamp': '2018-11-18T20:52:51.462Z', + host: { name: 'b359e3afece8' }, + agent: { + type: 'apm-server', + hostname: 'b359e3afece8', + version: '7.0.0-alpha1' + }, + processor: { + name: 'transaction', + event: 'transaction' + }, + transaction: { + sampled: true, + span_count: { started: 0 }, + id: 'ec9c465c5042ded8', + name: 'GET /log-error', + duration: { us: 33367 }, + type: 'request', + result: 'HTTP 5xx' + }, + context: { + service: { + name: 'opbeans-node', + agent: { version: '1.14.2', name: 'nodejs' }, + version: '1.0.0', + language: { name: 'javascript' }, + runtime: { name: 'node', version: '8.12.0' } + }, + user: { + id: '42', + username: 'kimchy', + email: 'kimchy@elastic.co' + }, + tags: { + 'multi-line': 'foo\nbar\nbaz', + foo: 'bar', + lorem: + 'ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.', + 'this-is-a-very-long-tag-name-without-any-spaces': + 'test' + }, + custom: { containerId: 4877 }, + request: { + http_version: '1.1', + method: 'GET', + url: { + full: 'http://opbeans-node:3000/log-error', + raw: '/log-error', + protocol: 'http:', + hostname: 'opbeans-node', + port: '3000', + pathname: '/log-error' + }, + socket: { + remote_address: '::ffff:172.18.0.10', + encrypted: false + }, + headers: { + 'user-agent': 'workload/2.4.3', + host: 'opbeans-node:3000', + connection: 'close' + } + }, + response: { + headers: { + date: 'Sun, 18 Nov 2018 20:52:51 GMT', + connection: 'close', + 'x-powered-by': 'Express', + 'content-type': 'text/html; charset=utf-8', + 'content-length': '24', + etag: 'W/"18-MS3VbhH7auHMzO0fUuNF6v14N/M"' + }, + status_code: 500 + }, + system: { + architecture: 'x64', + platform: 'linux', + ip: '172.18.0.10', + hostname: '98195610c255' + }, + process: { + argv: [ + '/usr/local/bin/node', + '/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js' + ], + pid: 3659, + ppid: 1, + title: 'node /app/server.js' + } + }, + trace: { id: '15366d65659b5fc8f67ff127391b3aff' }, + timestamp: { us: 1542574371462005 } + }, + sort: [1542574371462] + } + ] + } + } + }, + { + key: 'GET /api/customers', + doc_count: 3366, + avg: { value: 32159.926322043968 }, + p95: { values: { '95.0': 59845.85714285714 } }, + sample: { + hits: { + total: 3366, + max_score: null, + hits: [ + { + _index: 'apm-7.0.0-alpha1-2018.11.18', + _type: 'doc', + _id: 'aRKZKGcBVMxP8Wruf2ly', + _score: null, + _source: { + '@timestamp': '2018-11-18T20:53:21.180Z', + transaction: { + sampled: true, + span_count: { started: 2 }, + id: '94852b9dd1075982', + name: 'GET /api/customers', + duration: { us: 18077 }, + type: 'request', + result: 'HTTP 2xx' + }, + context: { + user: { + id: '42', + username: 'kimchy', + email: 'kimchy@elastic.co' + }, + tags: { + foo: 'bar', + lorem: + 'ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.', + 'this-is-a-very-long-tag-name-without-any-spaces': + 'test', + 'multi-line': 'foo\nbar\nbaz' + }, + custom: { containerId: 2531 }, + request: { + http_version: '1.1', + method: 'GET', + url: { + protocol: 'http:', + hostname: 'opbeans-node', + port: '3000', + pathname: '/api/customers', + full: 'http://opbeans-node:3000/api/customers', + raw: '/api/customers' + }, + socket: { + remote_address: '::ffff:172.18.0.6', + encrypted: false + }, + headers: { + accept: '*/*', + connection: 'keep-alive', + 'elastic-apm-traceparent': + '00-541025da8ecc2f51f21c1a4ad6992b77-ca18d9d4c3879519-01', + host: 'opbeans-node:3000', + 'user-agent': 'python-requests/2.20.0', + 'accept-encoding': 'gzip, deflate' + } + }, + response: { + status_code: 200, + headers: { + etag: 'W/"2d991-yG3J8W/roH7fSxXTudZrO27Ax9s"', + date: 'Sun, 18 Nov 2018 20:53:21 GMT', + connection: 'keep-alive', + 'x-powered-by': 'Express', + 'content-type': 'application/json; charset=utf-8', + 'content-length': '186769' + } + }, + system: { + platform: 'linux', + ip: '172.18.0.10', + hostname: '98195610c255', + architecture: 'x64' + }, + process: { + title: 'node /app/server.js', + argv: [ + '/usr/local/bin/node', + '/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js' + ], + pid: 3710, + ppid: 1 + }, + service: { + version: '1.0.0', + language: { name: 'javascript' }, + runtime: { name: 'node', version: '8.12.0' }, + name: 'opbeans-node', + agent: { version: '1.14.2', name: 'nodejs' } + } + }, + parent: { id: 'ca18d9d4c3879519' }, + trace: { id: '541025da8ecc2f51f21c1a4ad6992b77' }, + timestamp: { us: 1542574401180002 }, + agent: { + type: 'apm-server', + hostname: 'b359e3afece8', + version: '7.0.0-alpha1' + }, + host: { name: 'b359e3afece8' }, + processor: { + name: 'transaction', + event: 'transaction' + } + }, + sort: [1542574401180] + } + ] + } + } + }, + { + key: 'GET /api/products/top', + doc_count: 3694, + avg: { value: 27516.89144558744 }, + p95: { values: { '95.0': 56064.679999999986 } }, + sample: { + hits: { + total: 3694, + max_score: null, + hits: [ + { + _index: 'apm-7.0.0-alpha1-2018.11.18', + _type: 'doc', + _id: 'LhKZKGcBVMxP8WruHWMl', + _score: null, + _source: { + '@timestamp': '2018-11-18T20:52:57.316Z', + host: { name: 'b359e3afece8' }, + agent: { + type: 'apm-server', + hostname: 'b359e3afece8', + version: '7.0.0-alpha1' + }, + processor: { + name: 'transaction', + event: 'transaction' + }, + transaction: { + span_count: { started: 4 }, + id: 'be4bd5475d5d9e6f', + name: 'GET /api/products/top', + duration: { us: 48781 }, + type: 'request', + result: 'HTTP 2xx', + sampled: true + }, + context: { + request: { + headers: { + host: 'opbeans-node:3000', + connection: 'keep-alive', + 'user-agent': 'Chromeless 1.4.0', + 'elastic-apm-traceparent': + '00-74f12e705936d66350f4741ebeb55189-fcebe94cd2136215-01', + accept: '*/*', + referer: 'http://opbeans-node:3000/dashboard', + 'accept-encoding': 'gzip, deflate' + }, + http_version: '1.1', + method: 'GET', + url: { + port: '3000', + pathname: '/api/products/top', + full: 'http://opbeans-node:3000/api/products/top', + raw: '/api/products/top', + protocol: 'http:', + hostname: 'opbeans-node' + }, + socket: { + remote_address: '::ffff:172.18.0.7', + encrypted: false + } + }, + response: { + status_code: 200, + headers: { + 'x-powered-by': 'Express', + 'content-type': 'application/json; charset=utf-8', + 'content-length': '282', + etag: 'W/"11a-lcI9zuMZYYsDRpEZgYqDYr96cKM"', + date: 'Sun, 18 Nov 2018 20:52:57 GMT', + connection: 'keep-alive' + } + }, + system: { + hostname: '98195610c255', + architecture: 'x64', + platform: 'linux', + ip: '172.18.0.10' + }, + process: { + title: 'node /app/server.js', + argv: [ + '/usr/local/bin/node', + '/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js' + ], + pid: 3686, + ppid: 1 + }, + service: { + version: '1.0.0', + language: { name: 'javascript' }, + runtime: { name: 'node', version: '8.12.0' }, + name: 'opbeans-node', + agent: { name: 'nodejs', version: '1.14.2' } + }, + user: { + username: 'kimchy', + email: 'kimchy@elastic.co', + id: '42' + }, + tags: { + foo: 'bar', + lorem: + 'ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.', + 'this-is-a-very-long-tag-name-without-any-spaces': + 'test', + 'multi-line': 'foo\nbar\nbaz' + }, + custom: { containerId: 5113 } + }, + parent: { id: 'fcebe94cd2136215' }, + trace: { id: '74f12e705936d66350f4741ebeb55189' }, + timestamp: { us: 1542574377316005 } + }, + sort: [1542574377316] + } + ] + } + } + }, + { + key: 'POST /api', + doc_count: 147, + avg: { value: 21331.714285714286 }, + p95: { values: { '95.0': 30938 } }, + sample: { + hits: { + total: 147, + max_score: null, + hits: [ + { + _index: 'apm-7.0.0-alpha1-2018.11.18', + _type: 'doc', + _id: 'DhGDKGcBVMxP8WruzRXV', + _score: null, + _source: { + '@timestamp': '2018-11-18T20:29:42.751Z', + transaction: { + duration: { us: 21083 }, + type: 'request', + result: 'HTTP 4xx', + sampled: true, + span_count: { started: 1 }, + id: 'd67c2f7aa897110c', + name: 'POST /api' + }, + context: { + user: { + email: 'kimchy@elastic.co', + id: '42', + username: 'kimchy' + }, + tags: { + 'multi-line': 'foo\nbar\nbaz', + foo: 'bar', + lorem: + 'ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.', + 'this-is-a-very-long-tag-name-without-any-spaces': + 'test' + }, + custom: { containerId: 2927 }, + request: { + url: { + raw: '/api/orders', + protocol: 'http:', + hostname: 'opbeans-node', + port: '3000', + pathname: '/api/orders', + full: 'http://opbeans-node:3000/api/orders' + }, + socket: { + encrypted: false, + remote_address: '::ffff:172.18.0.10' + }, + headers: { + accept: 'application/json', + 'content-type': 'application/json', + 'content-length': '129', + connection: 'close', + 'user-agent': 'workload/2.4.3', + host: 'opbeans-node:3000' + }, + body: '[REDACTED]', + http_version: '1.1', + method: 'POST' + }, + response: { + status_code: 400, + headers: { + 'x-powered-by': 'Express', + date: 'Sun, 18 Nov 2018 20:29:42 GMT', + 'content-length': '0', + connection: 'close' + } + }, + system: { + hostname: '98195610c255', + architecture: 'x64', + platform: 'linux', + ip: '172.18.0.10' + }, + process: { + pid: 546, + ppid: 1, + title: 'node /app/server.js', + argv: [ + '/usr/local/bin/node', + '/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js' + ] + }, + service: { + agent: { name: 'nodejs', version: '1.14.2' }, + version: '1.0.0', + language: { name: 'javascript' }, + runtime: { name: 'node', version: '8.12.0' }, + name: 'opbeans-node' + } + }, + trace: { id: '8ed4d94ec8fc11b1ea1b0aa59c2320ff' }, + timestamp: { us: 1542572982751005 }, + agent: { + version: '7.0.0-alpha1', + type: 'apm-server', + hostname: 'b359e3afece8' + }, + host: { name: 'b359e3afece8' }, + processor: { + name: 'transaction', + event: 'transaction' + } + }, + sort: [1542572982751] + } + ] + } + } + }, + { + key: 'GET /api/products/:id/customers', + doc_count: 2102, + avg: { value: 17189.329210275926 }, + p95: { values: { '95.0': 39284.79999999999 } }, + sample: { + hits: { + total: 2102, + max_score: null, + hits: [ + { + _index: 'apm-7.0.0-alpha1-2018.11.18', + _type: 'doc', + _id: 'lhKVKGcBVMxP8WruDCUH', + _score: null, + _source: { + '@timestamp': '2018-11-18T20:48:24.769Z', + agent: { + type: 'apm-server', + hostname: 'b359e3afece8', + version: '7.0.0-alpha1' + }, + host: { name: 'b359e3afece8' }, + processor: { + event: 'transaction', + name: 'transaction' + }, + transaction: { + type: 'request', + result: 'HTTP 2xx', + sampled: true, + span_count: { started: 1 }, + id: '2a87ae20ad04ee0c', + name: 'GET /api/products/:id/customers', + duration: { us: 49338 } + }, + context: { + user: { + id: '42', + username: 'kimchy', + email: 'kimchy@elastic.co' + }, + tags: { + lorem: + 'ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.', + 'this-is-a-very-long-tag-name-without-any-spaces': + 'test', + 'multi-line': 'foo\nbar\nbaz', + foo: 'bar' + }, + custom: { containerId: 1735 }, + request: { + headers: { + accept: '*/*', + connection: 'keep-alive', + 'elastic-apm-traceparent': + '00-28f178c354d17f400dea04bc4a7b3c57-68f5d1607cac7779-01', + host: 'opbeans-node:3000', + 'user-agent': 'python-requests/2.20.0', + 'accept-encoding': 'gzip, deflate' + }, + http_version: '1.1', + method: 'GET', + url: { + port: '3000', + pathname: '/api/products/2/customers', + full: + 'http://opbeans-node:3000/api/products/2/customers', + raw: '/api/products/2/customers', + protocol: 'http:', + hostname: 'opbeans-node' + }, + socket: { + remote_address: '::ffff:172.18.0.6', + encrypted: false + } + }, + response: { + status_code: 200, + headers: { + 'content-length': '186570', + etag: 'W/"2d8ca-Z9NzuHyGyxwtzpOkcIxBvzm24iw"', + date: 'Sun, 18 Nov 2018 20:48:24 GMT', + connection: 'keep-alive', + 'x-powered-by': 'Express', + 'content-type': 'application/json; charset=utf-8' + } + }, + system: { + platform: 'linux', + ip: '172.18.0.10', + hostname: '98195610c255', + architecture: 'x64' + }, + process: { + argv: [ + '/usr/local/bin/node', + '/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js' + ], + pid: 3100, + ppid: 1, + title: 'node /app/server.js' + }, + service: { + language: { name: 'javascript' }, + runtime: { name: 'node', version: '8.12.0' }, + name: 'opbeans-node', + agent: { version: '1.14.2', name: 'nodejs' }, + version: '1.0.0' + } + }, + parent: { id: '68f5d1607cac7779' }, + trace: { id: '28f178c354d17f400dea04bc4a7b3c57' }, + timestamp: { us: 1542574104769029 } + }, + sort: [1542574104769] + } + ] + } + } + }, + { + key: 'GET /api/types/:id', + doc_count: 1449, + avg: { value: 12763.68806073154 }, + p95: { values: { '95.0': 30576.749999999996 } }, + sample: { + hits: { + total: 1449, + max_score: null, + hits: [ + { + _index: 'apm-7.0.0-alpha1-2018.11.18', + _type: 'doc', + _id: 'lxKZKGcBVMxP8WrurGuW', + _score: null, + _source: { + '@timestamp': '2018-11-18T20:53:35.967Z', + processor: { + name: 'transaction', + event: 'transaction' + }, + transaction: { + id: '053436abacdec0a4', + name: 'GET /api/types/:id', + duration: { us: 13064 }, + type: 'request', + result: 'HTTP 2xx', + sampled: true, + span_count: { started: 2 } + }, + context: { + process: { + title: 'node /app/server.js', + argv: [ + '/usr/local/bin/node', + '/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js' + ], + pid: 3756, + ppid: 1 + }, + service: { + name: 'opbeans-node', + agent: { name: 'nodejs', version: '1.14.2' }, + version: '1.0.0', + language: { name: 'javascript' }, + runtime: { name: 'node', version: '8.12.0' } + }, + user: { + id: '42', + username: 'kimchy', + email: 'kimchy@elastic.co' + }, + tags: { + foo: 'bar', + lorem: + 'ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.', + 'this-is-a-very-long-tag-name-without-any-spaces': + 'test', + 'multi-line': 'foo\nbar\nbaz' + }, + custom: { containerId: 5345 }, + request: { + socket: { + remote_address: '::ffff:172.18.0.10', + encrypted: false + }, + headers: { + 'user-agent': 'workload/2.4.3', + host: 'opbeans-node:3000', + connection: 'close' + }, + http_version: '1.1', + method: 'GET', + url: { + pathname: '/api/types/1', + full: 'http://opbeans-node:3000/api/types/1', + raw: '/api/types/1', + protocol: 'http:', + hostname: 'opbeans-node', + port: '3000' + } + }, + response: { + status_code: 200, + headers: { + 'x-powered-by': 'Express', + 'content-type': 'application/json; charset=utf-8', + 'content-length': '217', + etag: 'W/"d9-cebOOHODBQMZd1wt+ZZBaSPgQLQ"', + date: 'Sun, 18 Nov 2018 20:53:35 GMT', + connection: 'close' + } + }, + system: { + platform: 'linux', + ip: '172.18.0.10', + hostname: '98195610c255', + architecture: 'x64' + } + }, + trace: { id: '2223b30b5cbaf2e221fcf70ac6d9abbe' }, + timestamp: { us: 1542574415967005 }, + host: { name: 'b359e3afece8' }, + agent: { + type: 'apm-server', + hostname: 'b359e3afece8', + version: '7.0.0-alpha1' + } + }, + sort: [1542574415967] + } + ] + } + } + }, + { + key: 'GET /api/products', + doc_count: 3678, + avg: { value: 12683.190864600327 }, + p95: { values: { '95.0': 35009.67999999999 } }, + sample: { + hits: { + total: 3678, + max_score: null, + hits: [ + { + _index: 'apm-7.0.0-alpha1-2018.11.18', + _type: 'doc', + _id: '-hKZKGcBVMxP8Wru1G13', + _score: null, + _source: { + '@timestamp': '2018-11-18T20:53:43.477Z', + trace: { id: 'bee00a8efb523ca4b72adad57f7caba3' }, + timestamp: { us: 1542574423477006 }, + agent: { + type: 'apm-server', + hostname: 'b359e3afece8', + version: '7.0.0-alpha1' + }, + host: { name: 'b359e3afece8' }, + processor: { + name: 'transaction', + event: 'transaction' + }, + transaction: { + span_count: { started: 2 }, + id: 'd8fc6d3b8707b64c', + name: 'GET /api/products', + duration: { us: 6915 }, + type: 'request', + result: 'HTTP 2xx', + sampled: true + }, + context: { + custom: { containerId: 2857 }, + request: { + headers: { + 'user-agent': 'workload/2.4.3', + host: 'opbeans-node:3000', + connection: 'close' + }, + http_version: '1.1', + method: 'GET', + url: { + full: 'http://opbeans-node:3000/api/products', + raw: '/api/products', + protocol: 'http:', + hostname: 'opbeans-node', + port: '3000', + pathname: '/api/products' + }, + socket: { + remote_address: '::ffff:172.18.0.10', + encrypted: false + } + }, + response: { + status_code: 200, + headers: { + connection: 'close', + 'x-powered-by': 'Express', + 'content-type': 'application/json; charset=utf-8', + 'content-length': '1023', + etag: 'W/"3ff-VyOxcDApb+a/lnjkm9FeTOGSDrs"', + date: 'Sun, 18 Nov 2018 20:53:43 GMT' + } + }, + system: { + hostname: '98195610c255', + architecture: 'x64', + platform: 'linux', + ip: '172.18.0.10' + }, + process: { + pid: 3756, + ppid: 1, + title: 'node /app/server.js', + argv: [ + '/usr/local/bin/node', + '/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js' + ] + }, + service: { + runtime: { name: 'node', version: '8.12.0' }, + name: 'opbeans-node', + agent: { version: '1.14.2', name: 'nodejs' }, + version: '1.0.0', + language: { name: 'javascript' } + }, + user: { + id: '42', + username: 'kimchy', + email: 'kimchy@elastic.co' + }, + tags: { + foo: 'bar', + lorem: + 'ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.', + 'this-is-a-very-long-tag-name-without-any-spaces': + 'test', + 'multi-line': 'foo\nbar\nbaz' + } + } + }, + sort: [1542574423477] + } + ] + } + } + }, + { + key: 'GET /api/types', + doc_count: 2400, + avg: { value: 11257.757916666667 }, + p95: { values: { '95.0': 35222.944444444445 } }, + sample: { + hits: { + total: 2400, + max_score: null, + hits: [ + { + _index: 'apm-7.0.0-alpha1-2018.11.18', + _type: 'doc', + _id: '_xKZKGcBVMxP8Wru1G13', + _score: null, + _source: { + '@timestamp': '2018-11-18T20:53:44.978Z', + processor: { + name: 'transaction', + event: 'transaction' + }, + transaction: { + id: '0f10668e4fb3adc7', + name: 'GET /api/types', + duration: { us: 7891 }, + type: 'request', + result: 'HTTP 2xx', + sampled: true, + span_count: { started: 2 } + }, + context: { + request: { + http_version: '1.1', + method: 'GET', + url: { + hostname: 'opbeans-node', + port: '3000', + pathname: '/api/types', + full: 'http://opbeans-node:3000/api/types', + raw: '/api/types', + protocol: 'http:' + }, + socket: { + remote_address: '::ffff:172.18.0.10', + encrypted: false + }, + headers: { + connection: 'close', + 'user-agent': 'workload/2.4.3', + host: 'opbeans-node:3000' + } + }, + response: { + status_code: 200, + headers: { + 'content-length': '112', + etag: 'W/"70-1z6hT7P1WHgBgS/BeUEVeHhOCQU"', + date: 'Sun, 18 Nov 2018 20:53:44 GMT', + connection: 'close', + 'x-powered-by': 'Express', + 'content-type': 'application/json; charset=utf-8' + } + }, + system: { + hostname: '98195610c255', + architecture: 'x64', + platform: 'linux', + ip: '172.18.0.10' + }, + process: { + pid: 3756, + ppid: 1, + title: 'node /app/server.js', + argv: [ + '/usr/local/bin/node', + '/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js' + ] + }, + service: { + version: '1.0.0', + language: { name: 'javascript' }, + runtime: { name: 'node', version: '8.12.0' }, + name: 'opbeans-node', + agent: { version: '1.14.2', name: 'nodejs' } + }, + user: { + email: 'kimchy@elastic.co', + id: '42', + username: 'kimchy' + }, + tags: { + foo: 'bar', + lorem: + 'ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.', + 'this-is-a-very-long-tag-name-without-any-spaces': + 'test', + 'multi-line': 'foo\nbar\nbaz' + }, + custom: { containerId: 2193 } + }, + trace: { id: '0d84126973411c19b470f2d9eea958d3' }, + timestamp: { us: 1542574424978005 }, + agent: { + type: 'apm-server', + hostname: 'b359e3afece8', + version: '7.0.0-alpha1' + }, + host: { name: 'b359e3afece8' } + }, + sort: [1542574424978] + } + ] + } + } + }, + { + key: 'GET /api/orders/:id', + doc_count: 1283, + avg: { value: 10584.05144193297 }, + p95: { values: { '95.0': 26555.399999999998 } }, + sample: { + hits: { + total: 1283, + max_score: null, + hits: [ + { + _index: 'apm-7.0.0-alpha1-2018.11.18', + _type: 'doc', + _id: 'SRKXKGcBVMxP8Wru41Gf', + _score: null, + _source: { + '@timestamp': '2018-11-18T20:51:36.949Z', + context: { + tags: { + foo: 'bar', + lorem: + 'ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.', + 'this-is-a-very-long-tag-name-without-any-spaces': + 'test', + 'multi-line': 'foo\nbar\nbaz' + }, + custom: { containerId: 5999 }, + request: { + socket: { + remote_address: '::ffff:172.18.0.10', + encrypted: false + }, + headers: { + connection: 'close', + 'user-agent': 'workload/2.4.3', + host: 'opbeans-node:3000' + }, + http_version: '1.1', + method: 'GET', + url: { + raw: '/api/orders/183', + protocol: 'http:', + hostname: 'opbeans-node', + port: '3000', + pathname: '/api/orders/183', + full: 'http://opbeans-node:3000/api/orders/183' + } + }, + response: { + headers: { + date: 'Sun, 18 Nov 2018 20:51:36 GMT', + connection: 'close', + 'content-length': '0', + 'x-powered-by': 'Express' + }, + status_code: 404 + }, + system: { + hostname: '98195610c255', + architecture: 'x64', + platform: 'linux', + ip: '172.18.0.10' + }, + process: { + pid: 3475, + ppid: 1, + title: 'node /app/server.js', + argv: [ + '/usr/local/bin/node', + '/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js' + ] + }, + service: { + agent: { name: 'nodejs', version: '1.14.2' }, + version: '1.0.0', + language: { name: 'javascript' }, + runtime: { name: 'node', version: '8.12.0' }, + name: 'opbeans-node' + }, + user: { + username: 'kimchy', + email: 'kimchy@elastic.co', + id: '42' + } + }, + trace: { id: 'dab6421fa44a6869887e0edf32e1ad6f' }, + timestamp: { us: 1542574296949004 }, + agent: { + type: 'apm-server', + hostname: 'b359e3afece8', + version: '7.0.0-alpha1' + }, + host: { name: 'b359e3afece8' }, + processor: { + name: 'transaction', + event: 'transaction' + }, + transaction: { + span_count: { started: 1 }, + id: '937ef5588454f74a', + name: 'GET /api/orders/:id', + duration: { us: 5906 }, + type: 'request', + result: 'HTTP 4xx', + sampled: true + } + }, + sort: [1542574296949] + } + ] + } + } + }, + { + key: 'GET /api/products/:id', + doc_count: 1839, + avg: { value: 10548.218597063622 }, + p95: { values: { '95.0': 28413.383333333328 } }, + sample: { + hits: { + total: 1839, + max_score: null, + hits: [ + { + _index: 'apm-7.0.0-alpha1-2018.11.18', + _type: 'doc', + _id: 'OxKZKGcBVMxP8WruHWMl', + _score: null, + _source: { + '@timestamp': '2018-11-18T20:52:57.963Z', + agent: { + type: 'apm-server', + hostname: 'b359e3afece8', + version: '7.0.0-alpha1' + }, + host: { name: 'b359e3afece8' }, + processor: { + name: 'transaction', + event: 'transaction' + }, + transaction: { + span_count: { started: 1 }, + id: 'd324897ffb7ebcdc', + name: 'GET /api/products/:id', + duration: { us: 6959 }, + type: 'request', + result: 'HTTP 2xx', + sampled: true + }, + context: { + service: { + name: 'opbeans-node', + agent: { version: '1.14.2', name: 'nodejs' }, + version: '1.0.0', + language: { name: 'javascript' }, + runtime: { version: '8.12.0', name: 'node' } + }, + user: { + email: 'kimchy@elastic.co', + id: '42', + username: 'kimchy' + }, + tags: { + 'multi-line': 'foo\nbar\nbaz', + foo: 'bar', + lorem: + 'ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.', + 'this-is-a-very-long-tag-name-without-any-spaces': + 'test' + }, + custom: { containerId: 7184 }, + request: { + socket: { + remote_address: '::ffff:172.18.0.10', + encrypted: false + }, + headers: { + host: 'opbeans-node:3000', + connection: 'close', + 'user-agent': 'workload/2.4.3' + }, + http_version: '1.1', + method: 'GET', + url: { + port: '3000', + pathname: '/api/products/3', + full: 'http://opbeans-node:3000/api/products/3', + raw: '/api/products/3', + protocol: 'http:', + hostname: 'opbeans-node' + } + }, + response: { + status_code: 200, + headers: { + 'x-powered-by': 'Express', + 'content-type': 'application/json; charset=utf-8', + 'content-length': '231', + etag: 'W/"e7-kkuzj37GZDzXDh0CWqh5Gan0VO4"', + date: 'Sun, 18 Nov 2018 20:52:57 GMT', + connection: 'close' + } + }, + system: { + ip: '172.18.0.10', + hostname: '98195610c255', + architecture: 'x64', + platform: 'linux' + }, + process: { + pid: 3686, + ppid: 1, + title: 'node /app/server.js', + argv: [ + '/usr/local/bin/node', + '/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js' + ] + } + }, + trace: { id: 'ca86ec845e412e4b4506a715d51548ec' }, + timestamp: { us: 1542574377963005 } + }, + sort: [1542574377963] + } + ] + } + } + }, + { + key: 'GET /api/customers/:id', + doc_count: 1900, + avg: { value: 9868.217894736843 }, + p95: { values: { '95.0': 27486.5 } }, + sample: { + hits: { + total: 1900, + max_score: null, + hits: [ + { + _index: 'apm-7.0.0-alpha1-2018.11.18', + _type: 'doc', + _id: 'IhKZKGcBVMxP8WruHGPb', + _score: null, + _source: { + '@timestamp': '2018-11-18T20:52:56.797Z', + agent: { + hostname: 'b359e3afece8', + version: '7.0.0-alpha1', + type: 'apm-server' + }, + host: { name: 'b359e3afece8' }, + processor: { + name: 'transaction', + event: 'transaction' + }, + transaction: { + span_count: { started: 1 }, + id: '60e230d12f3f0960', + name: 'GET /api/customers/:id', + duration: { us: 9735 }, + type: 'request', + result: 'HTTP 2xx', + sampled: true + }, + context: { + response: { + status_code: 200, + headers: { + connection: 'keep-alive', + 'x-powered-by': 'Express', + 'content-type': 'application/json; charset=utf-8', + 'content-length': '193', + etag: 'W/"c1-LbuhkuLzFyZ0H+7+JQGA5b0kvNs"', + date: 'Sun, 18 Nov 2018 20:52:56 GMT' + } + }, + system: { + architecture: 'x64', + platform: 'linux', + ip: '172.18.0.10', + hostname: '98195610c255' + }, + process: { + ppid: 1, + title: 'node /app/server.js', + argv: [ + '/usr/local/bin/node', + '/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js' + ], + pid: 3686 + }, + service: { + name: 'opbeans-node', + agent: { name: 'nodejs', version: '1.14.2' }, + version: '1.0.0', + language: { name: 'javascript' }, + runtime: { name: 'node', version: '8.12.0' } + }, + user: { + username: 'kimchy', + email: 'kimchy@elastic.co', + id: '42' + }, + tags: { + 'multi-line': 'foo\nbar\nbaz', + foo: 'bar', + lorem: + 'ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.', + 'this-is-a-very-long-tag-name-without-any-spaces': + 'test' + }, + custom: { containerId: 8225 }, + request: { + headers: { + 'accept-encoding': 'gzip, deflate', + accept: '*/*', + connection: 'keep-alive', + 'elastic-apm-traceparent': + '00-e6140d30363f18b585f5d3b753f4d025-aa82e2c847265626-01', + host: 'opbeans-node:3000', + 'user-agent': 'python-requests/2.20.0' + }, + http_version: '1.1', + method: 'GET', + url: { + pathname: '/api/customers/700', + full: 'http://opbeans-node:3000/api/customers/700', + raw: '/api/customers/700', + protocol: 'http:', + hostname: 'opbeans-node', + port: '3000' + }, + socket: { + remote_address: '::ffff:172.18.0.6', + encrypted: false + } + } + }, + parent: { id: 'aa82e2c847265626' }, + trace: { id: 'e6140d30363f18b585f5d3b753f4d025' }, + timestamp: { us: 1542574376797031 } + }, + sort: [1542574376797] + } + ] + } + } + }, + { + key: 'POST unknown route', + doc_count: 20, + avg: { value: 5192.9 }, + p95: { values: { '95.0': 13230.5 } }, + sample: { + hits: { + total: 20, + max_score: null, + hits: [ + { + _index: 'apm-7.0.0-alpha1-2018.11.18', + _type: 'doc', + _id: '4wsiKGcBVMxP8Wru2j59', + _score: null, + _source: { + '@timestamp': '2018-11-18T18:43:50.994Z', + host: { name: 'b359e3afece8' }, + processor: { + name: 'transaction', + event: 'transaction' + }, + transaction: { + sampled: true, + span_count: { started: 0 }, + id: '92c3ceea57899061', + name: 'POST unknown route', + duration: { us: 3467 }, + type: 'request', + result: 'HTTP 4xx' + }, + context: { + system: { + platform: 'linux', + ip: '172.18.0.10', + hostname: '98195610c255', + architecture: 'x64' + }, + process: { + pid: 19196, + ppid: 1, + title: 'node /app/server.js', + argv: [ + '/usr/local/bin/node', + '/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js' + ] + }, + service: { + name: 'opbeans-node', + agent: { version: '1.14.2', name: 'nodejs' }, + version: '1.0.0', + language: { name: 'javascript' }, + runtime: { name: 'node', version: '8.12.0' } + }, + user: { + email: 'kimchy@elastic.co', + id: '42', + username: 'kimchy' + }, + tags: { + 'this-is-a-very-long-tag-name-without-any-spaces': + 'test', + 'multi-line': 'foo\nbar\nbaz', + foo: 'bar', + lorem: + 'ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.' + }, + custom: { containerId: 6102 }, + request: { + method: 'POST', + url: { + raw: '/api/orders/csv', + protocol: 'http:', + hostname: '172.18.0.9', + port: '3000', + pathname: '/api/orders/csv', + full: 'http://172.18.0.9:3000/api/orders/csv' + }, + socket: { + remote_address: '::ffff:172.18.0.9', + encrypted: false + }, + headers: { + 'accept-encoding': 'gzip, deflate', + 'content-type': + 'multipart/form-data; boundary=2b2e40be188a4cb5a56c05a0c182f6c9', + 'elastic-apm-traceparent': + '00-19688959ea6cbccda8013c11566ea329-1fc3665eef2dcdfc-01', + 'x-forwarded-for': '172.18.0.11', + host: '172.18.0.9:3000', + 'user-agent': 'Python/3.7 aiohttp/3.3.2', + 'content-length': '380', + accept: '*/*' + }, + body: '[REDACTED]', + http_version: '1.1' + }, + response: { + headers: { + date: 'Sun, 18 Nov 2018 18:43:50 GMT', + connection: 'keep-alive', + 'x-powered-by': 'Express', + 'content-security-policy': "default-src 'self'", + 'x-content-type-options': 'nosniff', + 'content-type': 'text/html; charset=utf-8', + 'content-length': '154' + }, + status_code: 404 + } + }, + parent: { id: '1fc3665eef2dcdfc' }, + trace: { id: '19688959ea6cbccda8013c11566ea329' }, + timestamp: { us: 1542566630994005 }, + agent: { + version: '7.0.0-alpha1', + type: 'apm-server', + hostname: 'b359e3afece8' + } + }, + sort: [1542566630994] + } + ] + } + } + }, + { + key: 'GET /is-it-coffee-time', + doc_count: 358, + avg: { value: 4694.005586592179 }, + p95: { values: { '95.0': 11022.99999999992 } }, + sample: { + hits: { + total: 358, + max_score: null, + hits: [ + { + _index: 'apm-7.0.0-alpha1-2018.11.18', + _type: 'doc', + _id: '7RKSKGcBVMxP8Wru-gjC', + _score: null, + _source: { + '@timestamp': '2018-11-18T20:46:19.317Z', + agent: { + type: 'apm-server', + hostname: 'b359e3afece8', + version: '7.0.0-alpha1' + }, + host: { name: 'b359e3afece8' }, + processor: { + name: 'transaction', + event: 'transaction' + }, + transaction: { + id: '319a5c555a1ab207', + name: 'GET /is-it-coffee-time', + duration: { us: 4253 }, + type: 'request', + result: 'HTTP 5xx', + sampled: true, + span_count: { started: 0 } + }, + context: { + process: { + pid: 2760, + ppid: 1, + title: 'node /app/server.js', + argv: [ + '/usr/local/bin/node', + '/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js' + ] + }, + service: { + agent: { name: 'nodejs', version: '1.14.2' }, + version: '1.0.0', + language: { name: 'javascript' }, + runtime: { name: 'node', version: '8.12.0' }, + name: 'opbeans-node' + }, + user: { + email: 'kimchy@elastic.co', + id: '42', + username: 'kimchy' + }, + tags: { + foo: 'bar', + lorem: + 'ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.', + 'this-is-a-very-long-tag-name-without-any-spaces': + 'test', + 'multi-line': 'foo\nbar\nbaz' + }, + custom: { containerId: 8593 }, + request: { + headers: { + 'user-agent': 'workload/2.4.3', + host: 'opbeans-node:3000', + connection: 'close' + }, + http_version: '1.1', + method: 'GET', + url: { + port: '3000', + pathname: '/is-it-coffee-time', + full: 'http://opbeans-node:3000/is-it-coffee-time', + raw: '/is-it-coffee-time', + protocol: 'http:', + hostname: 'opbeans-node' + }, + socket: { + remote_address: '::ffff:172.18.0.10', + encrypted: false + } + }, + response: { + status_code: 500, + headers: { + date: 'Sun, 18 Nov 2018 20:46:19 GMT', + connection: 'close', + 'x-powered-by': 'Express', + 'content-security-policy': "default-src 'self'", + 'x-content-type-options': 'nosniff', + 'content-type': 'text/html; charset=utf-8', + 'content-length': '148' + } + }, + system: { + ip: '172.18.0.10', + hostname: '98195610c255', + architecture: 'x64', + platform: 'linux' + } + }, + trace: { id: '821812b416de4c73ced87f8777fa46a6' }, + timestamp: { us: 1542573979317007 } + }, + sort: [1542573979317] + } + ] + } + } + }, + { + key: 'GET /throw-error', + doc_count: 336, + avg: { value: 4549.889880952381 }, + p95: { values: { '95.0': 7719.700000000001 } }, + sample: { + hits: { + total: 336, + max_score: null, + hits: [ + { + _index: 'apm-7.0.0-alpha1-2018.11.18', + _type: 'doc', + _id: 'PhKTKGcBVMxP8WruwxSG', + _score: null, + _source: { + '@timestamp': '2018-11-18T20:47:10.714Z', + agent: { + version: '7.0.0-alpha1', + type: 'apm-server', + hostname: 'b359e3afece8' + }, + host: { name: 'b359e3afece8' }, + processor: { + name: 'transaction', + event: 'transaction' + }, + transaction: { + id: 'ecd187dc53f09fbd', + name: 'GET /throw-error', + duration: { us: 4458 }, + type: 'request', + result: 'HTTP 5xx', + sampled: true, + span_count: { started: 0 } + }, + context: { + user: { + id: '42', + username: 'kimchy', + email: 'kimchy@elastic.co' + }, + tags: { + lorem: + 'ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.', + 'this-is-a-very-long-tag-name-without-any-spaces': + 'test', + 'multi-line': 'foo\nbar\nbaz', + foo: 'bar' + }, + custom: { containerId: 7220 }, + request: { + http_version: '1.1', + method: 'GET', + url: { + port: '3000', + pathname: '/throw-error', + full: 'http://opbeans-node:3000/throw-error', + raw: '/throw-error', + protocol: 'http:', + hostname: 'opbeans-node' + }, + socket: { + remote_address: '::ffff:172.18.0.10', + encrypted: false + }, + headers: { + 'user-agent': 'workload/2.4.3', + host: 'opbeans-node:3000', + connection: 'close' + } + }, + response: { + status_code: 500, + headers: { + 'x-content-type-options': 'nosniff', + 'content-type': 'text/html; charset=utf-8', + 'content-length': '148', + date: 'Sun, 18 Nov 2018 20:47:10 GMT', + connection: 'close', + 'x-powered-by': 'Express', + 'content-security-policy': "default-src 'self'" + } + }, + system: { + platform: 'linux', + ip: '172.18.0.10', + hostname: '98195610c255', + architecture: 'x64' + }, + process: { + title: 'node /app/server.js', + argv: [ + '/usr/local/bin/node', + '/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js' + ], + pid: 2895, + ppid: 1 + }, + service: { + name: 'opbeans-node', + agent: { version: '1.14.2', name: 'nodejs' }, + version: '1.0.0', + language: { name: 'javascript' }, + runtime: { name: 'node', version: '8.12.0' } + } + }, + trace: { id: '6c0ef23e1f963f304ce440a909914d35' }, + timestamp: { us: 1542574030714012 } + }, + sort: [1542574030714] + } + ] + } + } + }, + { + key: 'GET *', + doc_count: 7115, + avg: { value: 3504.5108924806746 }, + p95: { values: { '95.0': 11431.738095238095 } }, + sample: { + hits: { + total: 7115, + max_score: null, + hits: [ + { + _index: 'apm-7.0.0-alpha1-2018.11.18', + _type: 'doc', + _id: '6hKZKGcBVMxP8Wru1G13', + _score: null, + _source: { + '@timestamp': '2018-11-18T20:53:42.493Z', + agent: { + type: 'apm-server', + hostname: 'b359e3afece8', + version: '7.0.0-alpha1' + }, + host: { name: 'b359e3afece8' }, + processor: { + name: 'transaction', + event: 'transaction' + }, + transaction: { + span_count: { started: 0 }, + id: 'f5fc4621949b63fb', + name: 'GET *', + duration: { us: 1901 }, + type: 'request', + result: 'HTTP 3xx', + sampled: true + }, + context: { + request: { + http_version: '1.1', + method: 'GET', + url: { + hostname: 'opbeans-node', + port: '3000', + pathname: '/dashboard', + full: 'http://opbeans-node:3000/dashboard', + raw: '/dashboard', + protocol: 'http:' + }, + socket: { + remote_address: '::ffff:172.18.0.7', + encrypted: false + }, + headers: { + accept: + 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', + 'accept-encoding': 'gzip, deflate', + 'if-none-match': 'W/"280-1670775e878"', + 'if-modified-since': 'Mon, 12 Nov 2018 10:27:07 GMT', + host: 'opbeans-node:3000', + connection: 'keep-alive', + 'upgrade-insecure-requests': '1', + 'user-agent': 'Chromeless 1.4.0' + } + }, + response: { + status_code: 304, + headers: { + 'x-powered-by': 'Express', + 'accept-ranges': 'bytes', + 'cache-control': 'public, max-age=0', + 'last-modified': 'Mon, 12 Nov 2018 10:27:07 GMT', + etag: 'W/"280-1670775e878"', + date: 'Sun, 18 Nov 2018 20:53:42 GMT', + connection: 'keep-alive' + } + }, + system: { + hostname: '98195610c255', + architecture: 'x64', + platform: 'linux', + ip: '172.18.0.10' + }, + process: { + title: 'node /app/server.js', + argv: [ + '/usr/local/bin/node', + '/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js' + ], + pid: 3756, + ppid: 1 + }, + service: { + version: '1.0.0', + language: { name: 'javascript' }, + runtime: { name: 'node', version: '8.12.0' }, + name: 'opbeans-node', + agent: { version: '1.14.2', name: 'nodejs' } + }, + user: { + email: 'kimchy@elastic.co', + id: '42', + username: 'kimchy' + }, + tags: { + 'multi-line': 'foo\nbar\nbaz', + foo: 'bar', + lorem: + 'ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.', + 'this-is-a-very-long-tag-name-without-any-spaces': + 'test' + }, + custom: { containerId: 6446 } + }, + trace: { id: '7efb6ade88cdea20cd96ca482681cde7' }, + timestamp: { us: 1542574422493006 } + }, + sort: [1542574422493] + } + ] + } + } + }, + { + key: 'OPTIONS unknown route', + doc_count: 364, + avg: { value: 2742.4615384615386 }, + p95: { values: { '95.0': 4370.000000000002 } }, + sample: { + hits: { + total: 364, + max_score: null, + hits: [ + { + _index: 'apm-7.0.0-alpha1-2018.11.18', + _type: 'doc', + _id: '-xKVKGcBVMxP8WrucSs2', + _score: null, + _source: { + '@timestamp': '2018-11-18T20:49:00.707Z', + timestamp: { us: 1542574140707006 }, + agent: { + type: 'apm-server', + hostname: 'b359e3afece8', + version: '7.0.0-alpha1' + }, + host: { name: 'b359e3afece8' }, + processor: { + name: 'transaction', + event: 'transaction' + }, + transaction: { + span_count: { started: 0 }, + id: 'a8c87ebc7ec68bc0', + name: 'OPTIONS unknown route', + duration: { us: 2371 }, + type: 'request', + result: 'HTTP 2xx', + sampled: true + }, + context: { + user: { + id: '42', + username: 'kimchy', + email: 'kimchy@elastic.co' + }, + tags: { + 'this-is-a-very-long-tag-name-without-any-spaces': + 'test', + 'multi-line': 'foo\nbar\nbaz', + foo: 'bar', + lorem: + 'ipsum dolor sit amet, consectetur adipiscing elit. Nulla finibus, ipsum id scelerisque consequat, enim leo vulputate massa, vel ultricies ante neque ac risus. Curabitur tincidunt vitae sapien id pulvinar. Mauris eu vestibulum tortor. Integer sit amet lorem fringilla, egestas tellus vitae, vulputate purus. Nulla feugiat blandit nunc et semper. Morbi purus libero, mattis sed mauris non, euismod iaculis lacus. Curabitur eleifend ante eros, non faucibus velit lacinia id. Duis posuere libero augue, at dignissim urna consectetur eget. Praesent eu congue est, iaculis finibus augue.' + }, + custom: { containerId: 3775 }, + request: { + socket: { + remote_address: '::ffff:172.18.0.10', + encrypted: false + }, + headers: { + 'user-agent': 'workload/2.4.3', + host: 'opbeans-node:3000', + 'content-length': '0', + connection: 'close' + }, + http_version: '1.1', + method: 'OPTIONS', + url: { + port: '3000', + pathname: '/', + full: 'http://opbeans-node:3000/', + raw: '/', + protocol: 'http:', + hostname: 'opbeans-node' + } + }, + response: { + status_code: 200, + headers: { + 'content-type': 'text/html; charset=utf-8', + 'content-length': '8', + etag: 'W/"8-ZRAf8oNBS3Bjb/SU2GYZCmbtmXg"', + date: 'Sun, 18 Nov 2018 20:49:00 GMT', + connection: 'close', + 'x-powered-by': 'Express', + allow: 'GET,HEAD' + } + }, + system: { + ip: '172.18.0.10', + hostname: '98195610c255', + architecture: 'x64', + platform: 'linux' + }, + process: { + ppid: 1, + title: 'node /app/server.js', + argv: [ + '/usr/local/bin/node', + '/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js' + ], + pid: 3142 + }, + service: { + agent: { name: 'nodejs', version: '1.14.2' }, + version: '1.0.0', + language: { name: 'javascript' }, + runtime: { name: 'node', version: '8.12.0' }, + name: 'opbeans-node' + } + }, + trace: { id: '469e3e5f91ffe3195a8e58cdd1cdefa8' } + }, + sort: [1542574140707] + } + ] + } + } + }, + { + key: 'GET static file', + doc_count: 62606, + avg: { value: 2651.8784461553205 }, + p95: { values: { '95.0': 6140.579335038363 } }, + sample: { + hits: { + total: 62606, + max_score: null, + hits: [ + { + _index: 'apm-7.0.0-alpha1-2018.11.18', + _type: 'doc', + _id: '-RKZKGcBVMxP8Wru1G13', + _score: null, + _source: { + '@timestamp': '2018-11-18T20:53:43.304Z', + context: { + system: { + platform: 'linux', + ip: '172.18.0.10', + hostname: '98195610c255', + architecture: 'x64' + }, + process: { + title: 'node /app/server.js', + argv: [ + '/usr/local/bin/node', + '/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js' + ], + pid: 3756, + ppid: 1 + }, + service: { + name: 'opbeans-node', + agent: { name: 'nodejs', version: '1.14.2' }, + version: '1.0.0', + language: { name: 'javascript' }, + runtime: { name: 'node', version: '8.12.0' } + }, + request: { + headers: { + 'user-agent': 'curl/7.38.0', + host: 'opbeans-node:3000', + accept: '*/*' + }, + http_version: '1.1', + method: 'GET', + url: { + pathname: '/', + full: 'http://opbeans-node:3000/', + raw: '/', + protocol: 'http:', + hostname: 'opbeans-node', + port: '3000' + }, + socket: { + encrypted: false, + remote_address: '::ffff:172.18.0.10' + } + }, + response: { + status_code: 200, + headers: { + 'content-length': '640', + 'accept-ranges': 'bytes', + 'cache-control': 'public, max-age=0', + etag: 'W/"280-1670775e878"', + 'x-powered-by': 'Express', + 'last-modified': 'Mon, 12 Nov 2018 10:27:07 GMT', + 'content-type': 'text/html; charset=UTF-8', + date: 'Sun, 18 Nov 2018 20:53:43 GMT', + connection: 'keep-alive' + } + } + }, + trace: { id: 'b303d2a4a007946b63b9db7fafe639a0' }, + timestamp: { us: 1542574423304006 }, + agent: { + type: 'apm-server', + hostname: 'b359e3afece8', + version: '7.0.0-alpha1' + }, + host: { name: 'b359e3afece8' }, + processor: { + name: 'transaction', + event: 'transaction' + }, + transaction: { + span_count: { started: 0 }, + id: '2869c13633534be5', + name: 'GET static file', + duration: { us: 1801 }, + type: 'request', + result: 'HTTP 2xx', + sampled: true + } + }, + sort: [1542574423304] + } + ] + } + } + }, + { + key: 'GET unknown route', + doc_count: 7487, + avg: { value: 1422.926672899693 }, + p95: { values: { '95.0': 2311.885238095238 } }, + sample: { + hits: { + total: 7487, + max_score: null, + hits: [ + { + _index: 'apm-7.0.0-alpha1-2018.11.18', + _type: 'doc', + _id: '6xKZKGcBVMxP8Wru1G13', + _score: null, + _source: { + '@timestamp': '2018-11-18T20:53:42.504Z', + processor: { + name: 'transaction', + event: 'transaction' + }, + transaction: { + name: 'GET unknown route', + duration: { us: 911 }, + type: 'request', + result: 'HTTP 2xx', + sampled: true, + span_count: { started: 0 }, + id: '107881ae2be1b56d' + }, + context: { + system: { + hostname: '98195610c255', + architecture: 'x64', + platform: 'linux', + ip: '172.18.0.10' + }, + process: { + pid: 3756, + ppid: 1, + title: 'node /app/server.js', + argv: [ + '/usr/local/bin/node', + '/usr/local/lib/node_modules/pm2/lib/ProcessContainerFork.js' + ] + }, + service: { + agent: { version: '1.14.2', name: 'nodejs' }, + version: '1.0.0', + language: { name: 'javascript' }, + runtime: { name: 'node', version: '8.12.0' }, + name: 'opbeans-node' + }, + request: { + http_version: '1.1', + method: 'GET', + url: { + full: 'http://opbeans-node:3000/rum-config.js', + raw: '/rum-config.js', + protocol: 'http:', + hostname: 'opbeans-node', + port: '3000', + pathname: '/rum-config.js' + }, + socket: { + remote_address: '::ffff:172.18.0.7', + encrypted: false + }, + headers: { + connection: 'keep-alive', + 'user-agent': 'Chromeless 1.4.0', + accept: '*/*', + referer: 'http://opbeans-node:3000/dashboard', + 'accept-encoding': 'gzip, deflate', + host: 'opbeans-node:3000' + } + }, + response: { + headers: { + 'x-powered-by': 'Express', + 'content-type': 'text/javascript', + 'content-length': '172', + date: 'Sun, 18 Nov 2018 20:53:42 GMT', + connection: 'keep-alive' + }, + status_code: 200 + } + }, + trace: { id: '4399e7233e6e7b77e70c2fff111b8f28' }, + timestamp: { us: 1542574422504004 }, + agent: { + type: 'apm-server', + hostname: 'b359e3afece8', + version: '7.0.0-alpha1' + }, + host: { name: 'b359e3afece8' } + }, + sort: [1542574422504] + } + ] + } + } + } + ] + } + } +} as unknown) as ESResponse; diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/transform.test.ts b/x-pack/plugins/apm/server/lib/transaction_groups/transform.test.ts new file mode 100644 index 0000000000000..73053aa04643d --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transaction_groups/transform.test.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ESResponse } from './fetcher'; +import { transactionGroupsResponse } from './mock-responses/transactionGroupsResponse'; +import { transactionGroupsTransformer } from './transform'; + +describe('transactionGroupsTransformer', () => { + it('should match snapshot', () => { + expect( + transactionGroupsTransformer({ + response: transactionGroupsResponse, + start: 100, + end: 2000 + }) + ).toMatchSnapshot(); + }); + + fit('should transform response correctly', () => { + const bucket = { + key: 'POST /api/orders', + doc_count: 180, + avg: { value: 255966.30555555556 }, + p95: { values: { '95.0': 320238.5 } }, + sample: { + hits: { + total: 180, + hits: [{ _source: 'sample source' }] + } + } + }; + + const response = ({ + aggregations: { + transactions: { + buckets: [bucket] + } + } + } as unknown) as ESResponse; + + expect( + transactionGroupsTransformer({ response, start: 100, end: 20000 }) + ).toEqual([ + { + averageResponseTime: 255966.30555555556, + impact: 0, + name: 'POST /api/orders', + p95: 320238.5, + sample: 'sample source', + transactionsPerMinute: 542.713567839196 + } + ]); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/transaction_groups/transform.ts b/x-pack/plugins/apm/server/lib/transaction_groups/transform.ts new file mode 100644 index 0000000000000..229134025b18f --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transaction_groups/transform.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import moment from 'moment'; +import { oc } from 'ts-optchain'; +import { Transaction } from 'x-pack/plugins/apm/typings/Transaction'; +import { ESResponse } from './fetcher'; + +export interface ITransactionGroup { + name: string; + sample: Transaction; + p95: number; + averageResponseTime: number; + transactionsPerMinute: number; + impact: number; +} + +function calculateRelativeImpacts(results: ITransactionGroup[]) { + const values = results.map(({ impact }) => impact); + const max = Math.max(...values); + const min = Math.min(...values); + + return results.map(bucket => ({ + ...bucket, + impact: ((bucket.impact - min) / (max - min)) * 100 || 0 + })); +} + +export function transactionGroupsTransformer({ + response, + start, + end +}: { + response: ESResponse; + start: number; + end: number; +}): ITransactionGroup[] { + const buckets = oc(response).aggregations.transactions.buckets([]); + const duration = moment.duration(end - start); + const minutes = duration.asMinutes(); + const results = buckets.map(bucket => { + const averageResponseTime = bucket.avg.value; + const transactionsPerMinute = bucket.doc_count / minutes; + const impact = Math.round(averageResponseTime * transactionsPerMinute); + const sample = bucket.sample.hits.hits[0]._source; + + return { + name: bucket.key, + sample, + p95: bucket.p95.values['95.0'], + averageResponseTime, + transactionsPerMinute, + impact + }; + }); + + return calculateRelativeImpacts(results); +} diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/__test__/__snapshots__/get_timeseries_data.test.js.snap b/x-pack/plugins/apm/server/lib/transactions/charts/__test__/__snapshots__/get_timeseries_data.test.js.snap deleted file mode 100644 index 7cee615bee11e..0000000000000 --- a/x-pack/plugins/apm/server/lib/transactions/charts/__test__/__snapshots__/get_timeseries_data.test.js.snap +++ /dev/null @@ -1,1240 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`get_timeseries_data should call client with correct query 1`] = ` -Array [ - Array [ - "search", - Object { - "body": Object { - "aggs": Object { - "overall_avg_duration": Object { - "avg": Object { - "field": "transaction.duration.us", - }, - }, - "response_times": Object { - "aggs": Object { - "avg": Object { - "avg": Object { - "field": "transaction.duration.us", - }, - }, - "pct": Object { - "percentiles": Object { - "field": "transaction.duration.us", - "percents": Array [ - 95, - 99, - ], - }, - }, - }, - "date_histogram": Object { - "extended_bounds": Object { - "max": 1528977600000, - "min": 1528113600000, - }, - "field": "@timestamp", - "interval": "10800s", - "min_doc_count": 0, - }, - }, - "transaction_results": Object { - "aggs": Object { - "timeseries": Object { - "date_histogram": Object { - "extended_bounds": Object { - "max": 1528977600000, - "min": 1528113600000, - }, - "field": "@timestamp", - "interval": "10800s", - "min_doc_count": 0, - }, - }, - }, - "terms": Object { - "field": "transaction.result", - "missing": "transaction_result_missing", - }, - }, - }, - "query": Object { - "bool": Object { - "filter": Array [ - Object { - "term": Object { - "context.service.name": "myServiceName", - }, - }, - Object { - "term": Object { - "transaction.type": "myTransactionType", - }, - }, - Object { - "range": Object { - "@timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, - }, - }, - ], - }, - }, - "size": 0, - }, - "index": "myIndex", - }, - ], - Array [ - "search", - Object { - "body": Object { - "aggs": Object { - "ml_avg_response_times": Object { - "aggs": Object { - "anomaly_score": Object { - "max": Object { - "field": "anomaly_score", - }, - }, - "lower": Object { - "min": Object { - "field": "model_lower", - }, - }, - "upper": Object { - "max": Object { - "field": "model_upper", - }, - }, - }, - "date_histogram": Object { - "extended_bounds": Object { - "max": 1528977600000, - "min": 1528113600000, - }, - "field": "timestamp", - "interval": "10800s", - "min_doc_count": 0, - }, - }, - "top_hits": Object { - "top_hits": Object { - "_source": Object { - "includes": Array [ - "bucket_span", - ], - }, - "size": 1, - "sort": Array [ - "bucket_span", - ], - }, - }, - }, - "query": Object { - "bool": Object { - "filter": Array [ - Object { - "range": Object { - "timestamp": Object { - "format": "epoch_millis", - "gte": 1528113600000, - "lte": 1528977600000, - }, - }, - }, - ], - }, - }, - "size": 0, - }, - "index": ".ml-anomalies-myservicename-mytransactiontype-high_mean_response_time", - }, - ], -] -`; - -exports[`get_timeseries_data should match snapshot 1`] = ` -Object { - "dates": Array [ - 1528124400000, - 1528135200000, - 1528146000000, - 1528156800000, - 1528167600000, - 1528178400000, - 1528189200000, - 1528200000000, - 1528210800000, - 1528221600000, - 1528232400000, - 1528243200000, - 1528254000000, - 1528264800000, - 1528275600000, - 1528286400000, - 1528297200000, - 1528308000000, - 1528318800000, - 1528329600000, - 1528340400000, - 1528351200000, - 1528362000000, - 1528372800000, - 1528383600000, - 1528394400000, - 1528405200000, - 1528416000000, - 1528426800000, - 1528437600000, - 1528448400000, - 1528459200000, - 1528470000000, - 1528480800000, - 1528491600000, - 1528502400000, - 1528513200000, - 1528524000000, - 1528534800000, - 1528545600000, - 1528556400000, - 1528567200000, - 1528578000000, - 1528588800000, - 1528599600000, - 1528610400000, - 1528621200000, - 1528632000000, - 1528642800000, - 1528653600000, - 1528664400000, - 1528675200000, - 1528686000000, - 1528696800000, - 1528707600000, - 1528718400000, - 1528729200000, - 1528740000000, - 1528750800000, - 1528761600000, - 1528772400000, - 1528783200000, - 1528794000000, - 1528804800000, - 1528815600000, - 1528826400000, - 1528837200000, - 1528848000000, - 1528858800000, - 1528869600000, - 1528880400000, - 1528891200000, - 1528902000000, - 1528912800000, - 1528923600000, - 1528934400000, - 1528945200000, - 1528956000000, - 1528966800000, - ], - "overall_avg_duration": 32861.15660262639, - "response_times": Object { - "avg": Array [ - 26193.277795595466, - 25291.787065995228, - 24690.306474667796, - 24809.8953814219, - 25460.0394764508, - 26360.440733498916, - 27050.95205479452, - 26555.857333903925, - 26164.343359049206, - 26989.84546419098, - 26314.409430068266, - 27460.774575018477, - 26461.469107431974, - 27657.584946692834, - 27940.445967005213, - 34454.377581534434, - 44024.31809353839, - 36374.53333333333, - 36991.29442471209, - 37178.002701986756, - 37605.57078923814, - 37319.89767295267, - 38709.5041348433, - 38140.131856255066, - 34564.81091043125, - 33256.37743828302, - 37251.5625266752, - 38681.89084929791, - 40677.801045709355, - 39987.86453616932, - 41059.392914139804, - 39630.710111535845, - 41561.81331074284, - 43079.490738297536, - 43925.39609283509, - 25821.91424646782, - 27343.60011755486, - 25249.95060523233, - 25492.77199074074, - 25991.647281682137, - 26273.31290445375, - 26234.98976780795, - 23494.54873786408, - 22008.80482069371, - 22828.136655635586, - 22138.7081404321, - 22634.985579811735, - 22202.780998080616, - 23084.082780163997, - 23109.666146341464, - 23306.89028152719, - 39341.022704095325, - 37467.17153341258, - 52457.50554180566, - 31327.95780166252, - 30695.334941163997, - 28895.042785967435, - 30649.363989982416, - 29802.63622014101, - 30759.03002829892, - 30399.76549608631, - 29421.610233534506, - 32641.679897096656, - 30621.65440666204, - 31039.60391005818, - 30954.760723541545, - 31902.050234568553, - 31594.350653473728, - 31343.87243248879, - 31200.14450867052, - 28560.946668743833, - 24700.216146371717, - 25261.025210523563, - 26041.39789649068, - 26123.556295209142, - 46231.36177177638, - 45350.42005506141, - 48256.049354513096, - 52360.30017052116, - ], - "avg_anomalies": Object { - "bucketSpanAsMillis": 10800000, - "buckets": Array [ - Object { - "anomaly_score": 0, - "lower": 737.7398559597923, - "upper": 27505.95012649385, - }, - Object { - "anomaly_score": 0, - "lower": 740.6510789069575, - "upper": 27831.385094457328, - }, - Object { - "anomaly_score": 0, - "lower": 743.0092006506535, - "upper": 28113.842130309873, - }, - Object { - "anomaly_score": 0, - "lower": 745.266369017907, - "upper": 28166.937431054437, - }, - Object { - "anomaly_score": 0.0214167, - "lower": 747.3207728528188, - "upper": 28506.23990430994, - }, - Object { - "anomaly_score": 0, - "lower": 749.6894207909651, - "upper": 29019.425285291298, - }, - Object { - "anomaly_score": 0.05939392, - "lower": 752.7726473096143, - "upper": 29293.392326541318, - }, - Object { - "anomaly_score": 0.01836784, - "lower": 754.7407389918743, - "upper": 29812.56398272085, - }, - Object { - "anomaly_score": 0, - "lower": 757.2268079827784, - "upper": 30060.955415439636, - }, - Object { - "anomaly_score": 0, - "lower": 758.3112520555287, - "upper": 30376.319798106222, - }, - Object { - "anomaly_score": 0, - "lower": 761.9827340264112, - "upper": 30627.243996529527, - }, - Object { - "anomaly_score": 0, - "lower": 771.7209606007517, - "upper": 31035.75952712361, - }, - Object { - "anomaly_score": 0, - "lower": 822.9174906119024, - "upper": 31062.244462967443, - }, - Object { - "anomaly_score": 0, - "lower": 867.1132870297309, - "upper": 31463.705363442183, - }, - Object { - "anomaly_score": 0, - "lower": 871.6217257693573, - "upper": 31743.78493690837, - }, - Object { - "anomaly_score": 93.09271062370188, - "lower": 630.1794765317344, - "upper": 42784.833235636936, - }, - Object { - "anomaly_score": 94.91969, - "lower": 871.0283056454996, - "upper": 34441.21907642463, - }, - Object { - "anomaly_score": 0.737564554333639, - "lower": 872.694053998953, - "upper": 35467.552283525925, - }, - Object { - "anomaly_score": 0.2233028643272195, - "lower": 872.8967719351887, - "upper": 36793.04873774251, - }, - Object { - "anomaly_score": 4.367981, - "lower": 873.2094351335686, - "upper": 37663.180967734144, - }, - Object { - "anomaly_score": 10.94823, - "lower": 872.096335158222, - "upper": 38704.9736025016, - }, - Object { - "anomaly_score": 0.025502508470513552, - "lower": 870.7141079237686, - "upper": 39411.4696425657, - }, - Object { - "anomaly_score": 0.04643894365231562, - "lower": 865.6904233415615, - "upper": 40436.89001900413, - }, - Object { - "anomaly_score": 0.08232853094836577, - "lower": 864.5587212551438, - "upper": 41583.671803791396, - }, - Object { - "anomaly_score": 0, - "lower": 862.0456649399898, - "upper": 41918.88300390927, - }, - Object { - "anomaly_score": 0, - "lower": 862.6952960489333, - "upper": 42346.34733913561, - }, - Object { - "anomaly_score": 0.09328928850624264, - "lower": 859.02865711764, - "upper": 43503.30873034762, - }, - Object { - "anomaly_score": 0.01490678070537287, - "lower": 853.1249075544679, - "upper": 43582.32622523151, - }, - Object { - "anomaly_score": 0.025631694904640304, - "lower": 850.0412997919607, - "upper": 44583.7194942183, - }, - Object { - "anomaly_score": 0, - "lower": 846.9251200783423, - "upper": 45027.0923125673, - }, - Object { - "anomaly_score": 0.1775235302080928, - "lower": 839.9462033017412, - "upper": 46355.1414826366, - }, - Object { - "anomaly_score": 0, - "lower": 740.8424736560071, - "upper": 46997.18507921725, - }, - Object { - "anomaly_score": 0, - "lower": 675.2681661332581, - "upper": 47730.52441880603, - }, - Object { - "anomaly_score": 0, - "lower": 672.90202453507, - "upper": 48709.29810572524, - }, - Object { - "anomaly_score": 0, - "lower": 676.0944399455826, - "upper": 49406.64628717409, - }, - Object { - "anomaly_score": 0, - "lower": 682.4396257998045, - "upper": 73477.3874808886, - }, - Object { - "anomaly_score": 0, - "lower": 686.44341250381, - "upper": 73487.82525090317, - }, - Object { - "anomaly_score": 0, - "lower": 690.5371630779586, - "upper": 72962.57745081023, - }, - Object { - "anomaly_score": 0, - "lower": 695.8034104561633, - "upper": 72489.77380542927, - }, - Object { - "anomaly_score": 0, - "lower": 707.6839448592744, - "upper": 72463.76348179985, - }, - Object { - "anomaly_score": 0.18086821490763677, - "lower": 781.8674002616508, - "upper": 72996.2561390666, - }, - Object { - "anomaly_score": 0, - "lower": 846.540373740061, - "upper": 73169.30855560771, - }, - Object { - "anomaly_score": 0, - "lower": 860.5368537637945, - "upper": 73491.08437587181, - }, - Object { - "anomaly_score": 0, - "lower": 863.0293573898325, - "upper": 72389.78456634001, - }, - Object { - "anomaly_score": 0, - "lower": 863.4144235290587, - "upper": 72311.41927730369, - }, - Object { - "anomaly_score": 0, - "lower": 863.6781514985616, - "upper": 71383.41956191002, - }, - Object { - "anomaly_score": 0, - "lower": 863.4492510434609, - "upper": 74565.86466696904, - }, - Object { - "anomaly_score": 0, - "lower": 863.1958870174615, - "upper": 71304.14035324028, - }, - Object { - "anomaly_score": 0, - "lower": 862.6161500045084, - "upper": 72434.51561823535, - }, - Object { - "anomaly_score": 0, - "lower": 861.9526316405551, - "upper": 71664.49530601672, - }, - Object { - "anomaly_score": 0, - "lower": 861.1638627191994, - "upper": 71264.41362778837, - }, - Object { - "anomaly_score": 0, - "lower": 860.1741427701811, - "upper": 49087.805718943775, - }, - Object { - "anomaly_score": 0, - "lower": 862.4069845546885, - "upper": 49089.84051479006, - }, - Object { - "anomaly_score": 2.718289217634223, - "lower": 862.7834836635291, - "upper": 51834.13246654848, - }, - Object { - "anomaly_score": 0, - "lower": 863.5914163409548, - "upper": 51136.66870946708, - }, - Object { - "anomaly_score": 0, - "lower": 865.9550918866486, - "upper": 50968.889501405334, - }, - Object { - "anomaly_score": 0, - "lower": 866.5727896346234, - "upper": 51039.136906324355, - }, - Object { - "anomaly_score": 0, - "lower": 866.4824432593966, - "upper": 50450.44063039239, - }, - Object { - "anomaly_score": 0, - "lower": 869.2106974966512, - "upper": 49883.2079974308, - }, - Object { - "anomaly_score": 0, - "lower": 869.7908032042425, - "upper": 50019.796552072105, - }, - Object { - "anomaly_score": 0, - "lower": 872.0969808877924, - "upper": 50352.19806206938, - }, - Object { - "anomaly_score": 0, - "lower": 873.8327021716271, - "upper": 49893.959882267525, - }, - Object { - "anomaly_score": 0, - "lower": 875.6449156690691, - "upper": 49882.868445094966, - }, - Object { - "anomaly_score": 0, - "lower": 874.6294655070553, - "upper": 49766.53895122279, - }, - Object { - "anomaly_score": 0, - "lower": 878.2077083935897, - "upper": 49424.25380462067, - }, - Object { - "anomaly_score": 0, - "lower": 879.6702185935983, - "upper": 50079.967231416216, - }, - Object { - "anomaly_score": 0, - "lower": 880.9475908626134, - "upper": 49447.853072406695, - }, - Object { - "anomaly_score": 0, - "lower": 883.2355577302953, - "upper": 49628.237099331986, - }, - Object { - "anomaly_score": 0, - "lower": 885.0345875232065, - "upper": 49121.655177004985, - }, - Object { - "anomaly_score": 0, - "lower": 886.8375619687324, - "upper": 49439.75445560749, - }, - Object { - "anomaly_score": 0, - "lower": 888.2875318407426, - "upper": 49095.91031920496, - }, - Object { - "anomaly_score": 0, - "lower": 887.4807424937617, - "upper": 48584.97256737151, - }, - Object { - "anomaly_score": 0, - "lower": 885.3782512926099, - "upper": 48401.4014885278, - }, - Object { - "anomaly_score": 0, - "lower": 883.9626695681782, - "upper": 48303.662251777845, - }, - Object { - "anomaly_score": 0, - "lower": 881.5723811019918, - "upper": 48079.01124458592, - }, - Object { - "anomaly_score": 66.42568, - "lower": 0, - "upper": 58715.507088231585, - }, - Object { - "anomaly_score": 0, - "lower": 0, - "upper": 49868.15796918666, - }, - Object { - "anomaly_score": 0.060750193569274885, - "lower": 2003.2599232283408, - "upper": 50310.902258087066, - }, - Object { - "anomaly_score": 0.2831448638843196, - "lower": 1346.2241502014724, - "upper": 51642.47679276076, - }, - ], - }, - "p95": Array [ - 80738.78571428556, - 77058.03529411761, - 77892.20721980717, - 77085.86687499998, - 80048.3462981744, - 84089.21370223971, - 84880.90143416924, - 84554.8884781166, - 81839.39583333326, - 85993.55410163336, - 85001.44588628765, - 86980.16445312503, - 84961.8710743802, - 88906.54601889332, - 90198.34708994703, - 135627.71242424246, - 167037.1993837535, - 128293.12184873945, - 130653.54236263742, - 131630.8902645502, - 133581.33541666638, - 132697.92762266204, - 140003.6918918918, - 138149.5673529411, - 121872.37504835591, - 116378.03873517792, - 131545.40999999995, - 133111.25804878055, - 144821.9855278593, - 134737.3997727272, - 141206.57726666646, - 137731.8994082841, - 141476.23189033198, - 149636.31340909077, - 151934.55000000002, - 82198.17857142858, - 85946.43199999983, - 78617.66249999996, - 79606.48333333322, - 76297.93999999986, - 80742.63333333324, - 81291.45969696966, - 73467.02500000004, - 69177.66999999993, - 71956.06111111109, - 68480.91142857139, - 68957.0999999999, - 67489.50416666668, - 71556.91249999998, - 72157.65128205132, - 76124.5625, - 141709.34661835746, - 132371.48641975303, - 186783.51503759398, - 99540.17819499348, - 95982.62454212455, - 89559.3525925925, - 95769.83153735634, - 94063.90833755062, - 96399.67269119772, - 96436.42520161276, - 91860.16988095238, - 105989.8333333334, - 97937.60342555979, - 98967.2249999999, - 97561.02469135808, - 102557.78813357186, - 100137.87578595306, - 98412.97120445351, - 101607.8328012912, - 92000.51368421057, - 78027.29473684198, - 80762.078801789, - 81160.83425925927, - 84215.58945578222, - 194188.21428571426, - 172616.2293896504, - 182653.81858220184, - 194970.75667682925, - ], - "p99": Array [ - 293257.27333333343, - 290195.8800000004, - 278548.1649999994, - 290701.8973333341, - 286839.5897777779, - 287979.5149999999, - 300107.5009999992, - 294402.2179999999, - 289849.459333332, - 296942.86299999955, - 292048.20571428596, - 299308.7371666667, - 292151.2377777781, - 302274.4192592592, - 299457.1612121209, - 350398.59259259375, - 421204.23333333334, - 368166.68976190523, - 367193.6128571426, - 375658.10190476174, - 368152.03822222137, - 365705.8319999995, - 380075.48533333326, - 375697.1923809518, - 351080.94111111073, - 339294.12799999997, - 378902.90649999987, - 384483.3233333327, - 394692.25000000105, - 403362.50399999996, - 396559.0274999993, - 371815.8320000008, - 405477.6133333326, - 413542.18133333366, - 424399.340000001, - 303815.9000000001, - 306305.0800000006, - 297521.94999999984, - 317938.0900000003, - 312262.3000000003, - 318428.8700000002, - 295421.4099999999, - 293067.86000000004, - 264935.71999999933, - 282795.0400000003, - 285390.8400000001, - 290402.24, - 293655.53, - 292723.56999999995, - 301051.32000000105, - 291322.0499999998, - 379855.2444444447, - 371175.2592000001, - 498378.4238888898, - 331118.6599999997, - 328101.3999999988, - 313951.54249999986, - 323340.5274074075, - 315055.5047619052, - 330070.03599999985, - 320531.54416666675, - 315137.16628571344, - 337251.4042424246, - 327054.9243636365, - 327653.0000000006, - 324505.1399999999, - 338040.3999999998, - 328600.5173333335, - 334060.93628571345, - 328569.4964999998, - 320227.32399999973, - 292019.2899999998, - 297757.72666666657, - 308034.4466666669, - 301128.4895238093, - 447266.9, - 409147.332500001, - 423121.9773333328, - 473485.4199999998, - ], - }, - "total_hits": 1297673, - "tpm_buckets": Array [ - Object { - "avg": 78.106329113924, - "key": "HTTP 2xx", - "values": Array [ - 90.5, - 91.5, - 91.7, - 93.3, - 92, - 91.3, - 91, - 90.5, - 92.8, - 91.5, - 91.5, - 92, - 92.6, - 91.2, - 90.3, - 84.1, - 89.9, - 91.8, - 90.1, - 91.4, - 91.7, - 92.2, - 91.8, - 89.8, - 91.8, - 91.9, - 90.6, - 92.6, - 90, - 92.1, - 90.7, - 92, - 91.9, - 91.6, - 91.1, - 25.2, - 25.3, - 25.4, - 25.5, - 26, - 26, - 25.1, - 25.6, - 25.2, - 25.6, - 25.6, - 25, - 25.6, - 25.5, - 25.5, - 25.6, - 90.3, - 93.5, - 90.5, - 91.6, - 91.3, - 94.5, - 90.9, - 92.5, - 92.8, - 91.7, - 92.2, - 92.7, - 90.9, - 92.5, - 91.7, - 91.2, - 91.5, - 93.1, - 92.2, - 93.4, - 93.7, - 92.5, - 92.9, - 92.1, - 84, - 91.3, - 91.5, - 90.9, - ], - }, - Object { - "avg": 0.46835443037974683, - "key": "HTTP 3xx", - "values": Array [ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 22.5, - 2.5, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 12, - 0, - 0, - 0, - ], - }, - Object { - "avg": 5.68354430379747, - "key": "HTTP 4xx", - "values": Array [ - 6.7, - 6.7, - 6.5, - 6.9, - 6.6, - 7.1, - 6.9, - 6.5, - 6.9, - 6.8, - 6.8, - 6.7, - 6.9, - 6.8, - 6.6, - 6.3, - 6.9, - 6.9, - 6.5, - 6.8, - 6.7, - 6.6, - 6.9, - 6.6, - 6.8, - 6.7, - 6.6, - 6.8, - 6.5, - 6.8, - 7, - 6.9, - 6.5, - 7, - 6.4, - 1.6, - 1.3, - 1.5, - 1.6, - 1.6, - 1.7, - 1.5, - 1.4, - 1.6, - 1.4, - 1.6, - 1.4, - 1.7, - 1.5, - 1.6, - 1.5, - 6.5, - 7.1, - 6.5, - 6.4, - 6.8, - 6.8, - 6.6, - 6.4, - 6.7, - 6.7, - 7.1, - 6.7, - 6.6, - 6.5, - 6.5, - 7.1, - 6.8, - 7, - 6.7, - 6.7, - 7, - 6.8, - 7, - 7.1, - 6.6, - 6.8, - 6.7, - 6.7, - ], - }, - Object { - "avg": 5.689873417721517, - "key": "HTTP 5xx", - "values": Array [ - 6.7, - 6.6, - 6.8, - 6.9, - 7.1, - 6.8, - 6.8, - 6.9, - 6.9, - 6.4, - 6.8, - 6.6, - 6.9, - 6.7, - 6.6, - 6.1, - 6.5, - 6.9, - 6.7, - 6.7, - 7.2, - 6.9, - 6.8, - 6.4, - 6.9, - 6.5, - 6.9, - 7, - 6.6, - 6.6, - 6.8, - 7.1, - 6.5, - 6.7, - 6.9, - 1.6, - 1.7, - 1.6, - 1.7, - 1.8, - 1.5, - 1.7, - 1.5, - 1.6, - 1.5, - 1.6, - 1.3, - 1.6, - 1.4, - 1.5, - 1.6, - 6.7, - 7, - 6.3, - 6.9, - 6.8, - 6.9, - 6.7, - 6.7, - 6.5, - 6.7, - 6.7, - 6.5, - 6.6, - 6.9, - 6.9, - 7.1, - 6.7, - 6.5, - 6.8, - 6.9, - 6.8, - 6.9, - 6.8, - 6.8, - 6, - 6.9, - 6.5, - 6.6, - ], - }, - Object { - "avg": NaN, - "key": "A Custom Bucket (that should be last)", - "values": Array [], - }, - ], -} -`; diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/__test__/get_timeseries_data.test.js b/x-pack/plugins/apm/server/lib/transactions/charts/__test__/get_timeseries_data.test.js deleted file mode 100644 index 36ac9a0aff72e..0000000000000 --- a/x-pack/plugins/apm/server/lib/transactions/charts/__test__/get_timeseries_data.test.js +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import _ from 'lodash'; -import { getTimeseriesData } from '../get_timeseries_data'; -import timeseriesResponse from './timeseries_response.json'; -import responseTimeAnomalyResponse from './response_time_anomaly_response.json'; - -describe('get_timeseries_data', () => { - let res; - let clientSpy; - beforeEach(async () => { - clientSpy = jest - .fn() - .mockResolvedValueOnce(timeseriesResponse) - .mockResolvedValueOnce(responseTimeAnomalyResponse); - - res = await getTimeseriesData({ - serviceName: 'myServiceName', - transactionType: 'myTransactionType', - transactionName: null, - setup: { - start: 1528113600000, - end: 1528977600000, - client: clientSpy, - config: { - get: () => 'myIndex' - } - } - }); - }); - - it('should call client with correct query', () => { - expect(clientSpy.mock.calls).toMatchSnapshot(); - }); - - it('should not contain first and last bucket', () => { - const mockDates = timeseriesResponse.aggregations.transaction_results.buckets[0].timeseries.buckets.map( - bucket => bucket.key - ); - - expect(res.dates).not.toContain(_.first(mockDates)); - expect(res.dates).not.toContain(_.last(mockDates)); - expect(res.tpm_buckets[0].values).toHaveLength(res.dates.length); - }); - - it('should have correct order', () => { - expect(res.tpm_buckets.map(bucket => bucket.key)).toEqual([ - 'HTTP 2xx', - 'HTTP 3xx', - 'HTTP 4xx', - 'HTTP 5xx', - 'A Custom Bucket (that should be last)' - ]); - }); - - it('should match snapshot', () => { - expect(res).toMatchSnapshot(); - }); -}); diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/__test__/response_time_anomaly_response.json b/x-pack/plugins/apm/server/lib/transactions/charts/__test__/response_time_anomaly_response.json deleted file mode 100644 index 8f022a7778e8d..0000000000000 --- a/x-pack/plugins/apm/server/lib/transactions/charts/__test__/response_time_anomaly_response.json +++ /dev/null @@ -1,1175 +0,0 @@ -{ - "took": 14, - "timed_out": false, - "_shards": { - "total": 5, - "successful": 5, - "skipped": 0, - "failed": 0 - }, - "hits": { - "total": 4117, - "max_score": 0, - "hits": [] - }, - "aggregations": { - "ml_avg_response_times": { - "buckets": [ - { - "key_as_string": "2018-06-04T12:00:00.000Z", - "key": 1528113600000, - "doc_count": 60, - "anomaly_score": { - "value": 0.07062823 - }, - "upper": { - "value": 26781.294783193 - }, - "lower": { - "value": 734.6120832292385 - } - }, - { - "key_as_string": "2018-06-04T15:00:00.000Z", - "key": 1528124400000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 27505.95012649385 - }, - "lower": { - "value": 737.7398559597923 - } - }, - { - "key_as_string": "2018-06-04T18:00:00.000Z", - "key": 1528135200000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 27831.385094457328 - }, - "lower": { - "value": 740.6510789069575 - } - }, - { - "key_as_string": "2018-06-04T21:00:00.000Z", - "key": 1528146000000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 28113.842130309873 - }, - "lower": { - "value": 743.0092006506535 - } - }, - { - "key_as_string": "2018-06-05T00:00:00.000Z", - "key": 1528156800000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 28166.937431054437 - }, - "lower": { - "value": 745.266369017907 - } - }, - { - "key_as_string": "2018-06-05T03:00:00.000Z", - "key": 1528167600000, - "doc_count": 50, - "anomaly_score": { - "value": 0.0214167 - }, - "upper": { - "value": 28506.23990430994 - }, - "lower": { - "value": 747.3207728528188 - } - }, - { - "key_as_string": "2018-06-05T06:00:00.000Z", - "key": 1528178400000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 29019.425285291298 - }, - "lower": { - "value": 749.6894207909651 - } - }, - { - "key_as_string": "2018-06-05T09:00:00.000Z", - "key": 1528189200000, - "doc_count": 51, - "anomaly_score": { - "value": 0.05939392 - }, - "upper": { - "value": 29293.392326541318 - }, - "lower": { - "value": 752.7726473096143 - } - }, - { - "key_as_string": "2018-06-05T12:00:00.000Z", - "key": 1528200000000, - "doc_count": 50, - "anomaly_score": { - "value": 0.01836784 - }, - "upper": { - "value": 29812.56398272085 - }, - "lower": { - "value": 754.7407389918743 - } - }, - { - "key_as_string": "2018-06-05T15:00:00.000Z", - "key": 1528210800000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 30060.955415439636 - }, - "lower": { - "value": 757.2268079827784 - } - }, - { - "key_as_string": "2018-06-05T18:00:00.000Z", - "key": 1528221600000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 30376.319798106222 - }, - "lower": { - "value": 758.3112520555287 - } - }, - { - "key_as_string": "2018-06-05T21:00:00.000Z", - "key": 1528232400000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 30627.243996529527 - }, - "lower": { - "value": 761.9827340264112 - } - }, - { - "key_as_string": "2018-06-06T00:00:00.000Z", - "key": 1528243200000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 31035.75952712361 - }, - "lower": { - "value": 771.7209606007517 - } - }, - { - "key_as_string": "2018-06-06T03:00:00.000Z", - "key": 1528254000000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 31062.244462967443 - }, - "lower": { - "value": 822.9174906119024 - } - }, - { - "key_as_string": "2018-06-06T06:00:00.000Z", - "key": 1528264800000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 31463.705363442183 - }, - "lower": { - "value": 867.1132870297309 - } - }, - { - "key_as_string": "2018-06-06T09:00:00.000Z", - "key": 1528275600000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 31743.78493690837 - }, - "lower": { - "value": 871.6217257693573 - } - }, - { - "key_as_string": "2018-06-06T12:00:00.000Z", - "key": 1528286400000, - "doc_count": 86, - "anomaly_score": { - "value": 93.09271062370188 - }, - "upper": { - "value": 42784.833235636936 - }, - "lower": { - "value": 630.1794765317344 - } - }, - { - "key_as_string": "2018-06-06T15:00:00.000Z", - "key": 1528297200000, - "doc_count": 94, - "anomaly_score": { - "value": 94.91969 - }, - "upper": { - "value": 34441.21907642463 - }, - "lower": { - "value": 871.0283056454996 - } - }, - { - "key_as_string": "2018-06-06T18:00:00.000Z", - "key": 1528308000000, - "doc_count": 76, - "anomaly_score": { - "value": 0.737564554333639 - }, - "upper": { - "value": 35467.552283525925 - }, - "lower": { - "value": 872.694053998953 - } - }, - { - "key_as_string": "2018-06-06T21:00:00.000Z", - "key": 1528318800000, - "doc_count": 62, - "anomaly_score": { - "value": 0.2233028643272195 - }, - "upper": { - "value": 36793.04873774251 - }, - "lower": { - "value": 872.8967719351887 - } - }, - { - "key_as_string": "2018-06-07T00:00:00.000Z", - "key": 1528329600000, - "doc_count": 56, - "anomaly_score": { - "value": 4.367981 - }, - "upper": { - "value": 37663.180967734144 - }, - "lower": { - "value": 873.2094351335686 - } - }, - { - "key_as_string": "2018-06-07T03:00:00.000Z", - "key": 1528340400000, - "doc_count": 74, - "anomaly_score": { - "value": 10.94823 - }, - "upper": { - "value": 38704.9736025016 - }, - "lower": { - "value": 872.096335158222 - } - }, - { - "key_as_string": "2018-06-07T06:00:00.000Z", - "key": 1528351200000, - "doc_count": 50, - "anomaly_score": { - "value": 0.025502508470513552 - }, - "upper": { - "value": 39411.4696425657 - }, - "lower": { - "value": 870.7141079237686 - } - }, - { - "key_as_string": "2018-06-07T09:00:00.000Z", - "key": 1528362000000, - "doc_count": 52, - "anomaly_score": { - "value": 0.04643894365231562 - }, - "upper": { - "value": 40436.89001900413 - }, - "lower": { - "value": 865.6904233415615 - } - }, - { - "key_as_string": "2018-06-07T12:00:00.000Z", - "key": 1528372800000, - "doc_count": 52, - "anomaly_score": { - "value": 0.08232853094836577 - }, - "upper": { - "value": 41583.671803791396 - }, - "lower": { - "value": 864.5587212551438 - } - }, - { - "key_as_string": "2018-06-07T15:00:00.000Z", - "key": 1528383600000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 41918.88300390927 - }, - "lower": { - "value": 862.0456649399898 - } - }, - { - "key_as_string": "2018-06-07T18:00:00.000Z", - "key": 1528394400000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 42346.34733913561 - }, - "lower": { - "value": 862.6952960489333 - } - }, - { - "key_as_string": "2018-06-07T21:00:00.000Z", - "key": 1528405200000, - "doc_count": 50, - "anomaly_score": { - "value": 0.09328928850624264 - }, - "upper": { - "value": 43503.30873034762 - }, - "lower": { - "value": 859.02865711764 - } - }, - { - "key_as_string": "2018-06-08T00:00:00.000Z", - "key": 1528416000000, - "doc_count": 50, - "anomaly_score": { - "value": 0.01490678070537287 - }, - "upper": { - "value": 43582.32622523151 - }, - "lower": { - "value": 853.1249075544679 - } - }, - { - "key_as_string": "2018-06-08T03:00:00.000Z", - "key": 1528426800000, - "doc_count": 50, - "anomaly_score": { - "value": 0.025631694904640304 - }, - "upper": { - "value": 44583.7194942183 - }, - "lower": { - "value": 850.0412997919607 - } - }, - { - "key_as_string": "2018-06-08T06:00:00.000Z", - "key": 1528437600000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 45027.0923125673 - }, - "lower": { - "value": 846.9251200783423 - } - }, - { - "key_as_string": "2018-06-08T09:00:00.000Z", - "key": 1528448400000, - "doc_count": 52, - "anomaly_score": { - "value": 0.1775235302080928 - }, - "upper": { - "value": 46355.1414826366 - }, - "lower": { - "value": 839.9462033017412 - } - }, - { - "key_as_string": "2018-06-08T12:00:00.000Z", - "key": 1528459200000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 46997.18507921725 - }, - "lower": { - "value": 740.8424736560071 - } - }, - { - "key_as_string": "2018-06-08T15:00:00.000Z", - "key": 1528470000000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 47730.52441880603 - }, - "lower": { - "value": 675.2681661332581 - } - }, - { - "key_as_string": "2018-06-08T18:00:00.000Z", - "key": 1528480800000, - "doc_count": 50, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 48709.29810572524 - }, - "lower": { - "value": 672.90202453507 - } - }, - { - "key_as_string": "2018-06-08T21:00:00.000Z", - "key": 1528491600000, - "doc_count": 50, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 49406.64628717409 - }, - "lower": { - "value": 676.0944399455826 - } - }, - { - "key_as_string": "2018-06-09T00:00:00.000Z", - "key": 1528502400000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 73477.3874808886 - }, - "lower": { - "value": 682.4396257998045 - } - }, - { - "key_as_string": "2018-06-09T03:00:00.000Z", - "key": 1528513200000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 73487.82525090317 - }, - "lower": { - "value": 686.44341250381 - } - }, - { - "key_as_string": "2018-06-09T06:00:00.000Z", - "key": 1528524000000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 72962.57745081023 - }, - "lower": { - "value": 690.5371630779586 - } - }, - { - "key_as_string": "2018-06-09T09:00:00.000Z", - "key": 1528534800000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 72489.77380542927 - }, - "lower": { - "value": 695.8034104561633 - } - }, - { - "key_as_string": "2018-06-09T12:00:00.000Z", - "key": 1528545600000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 72463.76348179985 - }, - "lower": { - "value": 707.6839448592744 - } - }, - { - "key_as_string": "2018-06-09T15:00:00.000Z", - "key": 1528556400000, - "doc_count": 50, - "anomaly_score": { - "value": 0.18086821490763677 - }, - "upper": { - "value": 72996.2561390666 - }, - "lower": { - "value": 781.8674002616508 - } - }, - { - "key_as_string": "2018-06-09T18:00:00.000Z", - "key": 1528567200000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 73169.30855560771 - }, - "lower": { - "value": 846.540373740061 - } - }, - { - "key_as_string": "2018-06-09T21:00:00.000Z", - "key": 1528578000000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 73491.08437587181 - }, - "lower": { - "value": 860.5368537637945 - } - }, - { - "key_as_string": "2018-06-10T00:00:00.000Z", - "key": 1528588800000, - "doc_count": 49, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 72389.78456634001 - }, - "lower": { - "value": 863.0293573898325 - } - }, - { - "key_as_string": "2018-06-10T03:00:00.000Z", - "key": 1528599600000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 72311.41927730369 - }, - "lower": { - "value": 863.4144235290587 - } - }, - { - "key_as_string": "2018-06-10T06:00:00.000Z", - "key": 1528610400000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 71383.41956191002 - }, - "lower": { - "value": 863.6781514985616 - } - }, - { - "key_as_string": "2018-06-10T09:00:00.000Z", - "key": 1528621200000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 74565.86466696904 - }, - "lower": { - "value": 863.4492510434609 - } - }, - { - "key_as_string": "2018-06-10T12:00:00.000Z", - "key": 1528632000000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 71304.14035324028 - }, - "lower": { - "value": 863.1958870174615 - } - }, - { - "key_as_string": "2018-06-10T15:00:00.000Z", - "key": 1528642800000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 72434.51561823535 - }, - "lower": { - "value": 862.6161500045084 - } - }, - { - "key_as_string": "2018-06-10T18:00:00.000Z", - "key": 1528653600000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 71664.49530601672 - }, - "lower": { - "value": 861.9526316405551 - } - }, - { - "key_as_string": "2018-06-10T21:00:00.000Z", - "key": 1528664400000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 71264.41362778837 - }, - "lower": { - "value": 861.1638627191994 - } - }, - { - "key_as_string": "2018-06-11T00:00:00.000Z", - "key": 1528675200000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 49087.805718943775 - }, - "lower": { - "value": 860.1741427701811 - } - }, - { - "key_as_string": "2018-06-11T03:00:00.000Z", - "key": 1528686000000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 49089.84051479006 - }, - "lower": { - "value": 862.4069845546885 - } - }, - { - "key_as_string": "2018-06-11T06:00:00.000Z", - "key": 1528696800000, - "doc_count": 60, - "anomaly_score": { - "value": 2.718289217634223 - }, - "upper": { - "value": 51834.13246654848 - }, - "lower": { - "value": 862.7834836635291 - } - }, - { - "key_as_string": "2018-06-11T09:00:00.000Z", - "key": 1528707600000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 51136.66870946708 - }, - "lower": { - "value": 863.5914163409548 - } - }, - { - "key_as_string": "2018-06-11T12:00:00.000Z", - "key": 1528718400000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 50968.889501405334 - }, - "lower": { - "value": 865.9550918866486 - } - }, - { - "key_as_string": "2018-06-11T15:00:00.000Z", - "key": 1528729200000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 51039.136906324355 - }, - "lower": { - "value": 866.5727896346234 - } - }, - { - "key_as_string": "2018-06-11T18:00:00.000Z", - "key": 1528740000000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 50450.44063039239 - }, - "lower": { - "value": 866.4824432593966 - } - }, - { - "key_as_string": "2018-06-11T21:00:00.000Z", - "key": 1528750800000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 49883.2079974308 - }, - "lower": { - "value": 869.2106974966512 - } - }, - { - "key_as_string": "2018-06-12T00:00:00.000Z", - "key": 1528761600000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 50019.796552072105 - }, - "lower": { - "value": 869.7908032042425 - } - }, - { - "key_as_string": "2018-06-12T03:00:00.000Z", - "key": 1528772400000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 50352.19806206938 - }, - "lower": { - "value": 872.0969808877924 - } - }, - { - "key_as_string": "2018-06-12T06:00:00.000Z", - "key": 1528783200000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 49893.959882267525 - }, - "lower": { - "value": 873.8327021716271 - } - }, - { - "key_as_string": "2018-06-12T09:00:00.000Z", - "key": 1528794000000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 49882.868445094966 - }, - "lower": { - "value": 875.6449156690691 - } - }, - { - "key_as_string": "2018-06-12T12:00:00.000Z", - "key": 1528804800000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 49766.53895122279 - }, - "lower": { - "value": 874.6294655070553 - } - }, - { - "key_as_string": "2018-06-12T15:00:00.000Z", - "key": 1528815600000, - "doc_count": 50, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 49424.25380462067 - }, - "lower": { - "value": 878.2077083935897 - } - }, - { - "key_as_string": "2018-06-12T18:00:00.000Z", - "key": 1528826400000, - "doc_count": 50, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 50079.967231416216 - }, - "lower": { - "value": 879.6702185935983 - } - }, - { - "key_as_string": "2018-06-12T21:00:00.000Z", - "key": 1528837200000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 49447.853072406695 - }, - "lower": { - "value": 880.9475908626134 - } - }, - { - "key_as_string": "2018-06-13T00:00:00.000Z", - "key": 1528848000000, - "doc_count": 51, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 49628.237099331986 - }, - "lower": { - "value": 883.2355577302953 - } - }, - { - "key_as_string": "2018-06-13T03:00:00.000Z", - "key": 1528858800000, - "doc_count": 50, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 49121.655177004985 - }, - "lower": { - "value": 885.0345875232065 - } - }, - { - "key_as_string": "2018-06-13T06:00:00.000Z", - "key": 1528869600000, - "doc_count": 50, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 49439.75445560749 - }, - "lower": { - "value": 886.8375619687324 - } - }, - { - "key_as_string": "2018-06-13T09:00:00.000Z", - "key": 1528880400000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 49095.91031920496 - }, - "lower": { - "value": 888.2875318407426 - } - }, - { - "key_as_string": "2018-06-13T12:00:00.000Z", - "key": 1528891200000, - "doc_count": 50, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 48584.97256737151 - }, - "lower": { - "value": 887.4807424937617 - } - }, - { - "key_as_string": "2018-06-13T15:00:00.000Z", - "key": 1528902000000, - "doc_count": 50, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 48401.4014885278 - }, - "lower": { - "value": 885.3782512926099 - } - }, - { - "key_as_string": "2018-06-13T18:00:00.000Z", - "key": 1528912800000, - "doc_count": 50, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 48303.662251777845 - }, - "lower": { - "value": 883.9626695681782 - } - }, - { - "key_as_string": "2018-06-13T21:00:00.000Z", - "key": 1528923600000, - "doc_count": 48, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 48079.01124458592 - }, - "lower": { - "value": 881.5723811019918 - } - }, - { - "key_as_string": "2018-06-14T00:00:00.000Z", - "key": 1528934400000, - "doc_count": 60, - "anomaly_score": { - "value": 66.42568 - }, - "upper": { - "value": 58715.507088231585 - }, - "lower": { - "value": 0 - } - }, - { - "key_as_string": "2018-06-14T03:00:00.000Z", - "key": 1528945200000, - "doc_count": 55, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 49868.15796918666 - }, - "lower": { - "value": 0 - } - }, - { - "key_as_string": "2018-06-14T06:00:00.000Z", - "key": 1528956000000, - "doc_count": 53, - "anomaly_score": { - "value": 0.060750193569274885 - }, - "upper": { - "value": 50310.902258087066 - }, - "lower": { - "value": 2003.2599232283408 - } - }, - { - "key_as_string": "2018-06-14T09:00:00.000Z", - "key": 1528966800000, - "doc_count": 62, - "anomaly_score": { - "value": 0.2831448638843196 - }, - "upper": { - "value": 51642.47679276076 - }, - "lower": { - "value": 1346.2241502014724 - } - }, - { - "key_as_string": "2018-06-14T12:00:00.000Z", - "key": 1528977600000, - "doc_count": 4, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 50550.64218576831 - }, - "lower": { - "value": 1317.070500756407 - } - } - ] - }, - "top_hits": { - "hits": { - "total": 4117, - "max_score": null, - "hits": [ - { - "_index": ".ml-anomalies-shared", - "_type": "doc", - "_id": "opbeans-node-request-high_mean_response_time_model_plot_1528170300000_900_0_29791_0", - "_score": null, - "_source": { - "bucket_span": 900 - }, - "sort": [ - 900 - ] - } - ] - } - } - } -} diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/__test__/timeseries_response.json b/x-pack/plugins/apm/server/lib/transactions/charts/__test__/timeseries_response.json deleted file mode 100644 index 38aa0237aebd6..0000000000000 --- a/x-pack/plugins/apm/server/lib/transactions/charts/__test__/timeseries_response.json +++ /dev/null @@ -1,2821 +0,0 @@ -{ - "took": 368, - "timed_out": false, - "_shards": { - "total": 90, - "successful": 90, - "skipped": 0, - "failed": 0 - }, - "hits": { - "total": 1297673, - "max_score": 0, - "hits": [] - }, - "aggregations": { - "transaction_results": { - "doc_count_error_upper_bound": 0, - "sum_other_doc_count": 0, - "buckets": [ - { - "key": "A Custom Bucket (that should be last)", - "doc_count": 0, - "timeseries": { "buckets": [] } - }, - { - "key": "HTTP 2xx", - "doc_count": 1127080, - "timeseries": { - "buckets": [ - { - "key_as_string": "2018-06-04T12:00:00.000Z", - "key": 1528113600000, - "doc_count": 16446 - }, - { - "key_as_string": "2018-06-04T15:00:00.000Z", - "key": 1528124400000, - "doc_count": 16292 - }, - { - "key_as_string": "2018-06-04T18:00:00.000Z", - "key": 1528135200000, - "doc_count": 16464 - }, - { - "key_as_string": "2018-06-04T21:00:00.000Z", - "key": 1528146000000, - "doc_count": 16497 - }, - { - "key_as_string": "2018-06-05T00:00:00.000Z", - "key": 1528156800000, - "doc_count": 16799 - }, - { - "key_as_string": "2018-06-05T03:00:00.000Z", - "key": 1528167600000, - "doc_count": 16561 - }, - { - "key_as_string": "2018-06-05T06:00:00.000Z", - "key": 1528178400000, - "doc_count": 16431 - }, - { - "key_as_string": "2018-06-05T09:00:00.000Z", - "key": 1528189200000, - "doc_count": 16383 - }, - { - "key_as_string": "2018-06-05T12:00:00.000Z", - "key": 1528200000000, - "doc_count": 16295 - }, - { - "key_as_string": "2018-06-05T15:00:00.000Z", - "key": 1528210800000, - "doc_count": 16702 - }, - { - "key_as_string": "2018-06-05T18:00:00.000Z", - "key": 1528221600000, - "doc_count": 16469 - }, - { - "key_as_string": "2018-06-05T21:00:00.000Z", - "key": 1528232400000, - "doc_count": 16466 - }, - { - "key_as_string": "2018-06-06T00:00:00.000Z", - "key": 1528243200000, - "doc_count": 16551 - }, - { - "key_as_string": "2018-06-06T03:00:00.000Z", - "key": 1528254000000, - "doc_count": 16675 - }, - { - "key_as_string": "2018-06-06T06:00:00.000Z", - "key": 1528264800000, - "doc_count": 16410 - }, - { - "key_as_string": "2018-06-06T09:00:00.000Z", - "key": 1528275600000, - "doc_count": 16247 - }, - { - "key_as_string": "2018-06-06T12:00:00.000Z", - "key": 1528286400000, - "doc_count": 15145 - }, - { - "key_as_string": "2018-06-06T15:00:00.000Z", - "key": 1528297200000, - "doc_count": 16178 - }, - { - "key_as_string": "2018-06-06T18:00:00.000Z", - "key": 1528308000000, - "doc_count": 16530 - }, - { - "key_as_string": "2018-06-06T21:00:00.000Z", - "key": 1528318800000, - "doc_count": 16211 - }, - { - "key_as_string": "2018-06-07T00:00:00.000Z", - "key": 1528329600000, - "doc_count": 16453 - }, - { - "key_as_string": "2018-06-07T03:00:00.000Z", - "key": 1528340400000, - "doc_count": 16503 - }, - { - "key_as_string": "2018-06-07T06:00:00.000Z", - "key": 1528351200000, - "doc_count": 16604 - }, - { - "key_as_string": "2018-06-07T09:00:00.000Z", - "key": 1528362000000, - "doc_count": 16522 - }, - { - "key_as_string": "2018-06-07T12:00:00.000Z", - "key": 1528372800000, - "doc_count": 16164 - }, - { - "key_as_string": "2018-06-07T15:00:00.000Z", - "key": 1528383600000, - "doc_count": 16520 - }, - { - "key_as_string": "2018-06-07T18:00:00.000Z", - "key": 1528394400000, - "doc_count": 16534 - }, - { - "key_as_string": "2018-06-07T21:00:00.000Z", - "key": 1528405200000, - "doc_count": 16311 - }, - { - "key_as_string": "2018-06-08T00:00:00.000Z", - "key": 1528416000000, - "doc_count": 16670 - }, - { - "key_as_string": "2018-06-08T03:00:00.000Z", - "key": 1528426800000, - "doc_count": 16192 - }, - { - "key_as_string": "2018-06-08T06:00:00.000Z", - "key": 1528437600000, - "doc_count": 16579 - }, - { - "key_as_string": "2018-06-08T09:00:00.000Z", - "key": 1528448400000, - "doc_count": 16330 - }, - { - "key_as_string": "2018-06-08T12:00:00.000Z", - "key": 1528459200000, - "doc_count": 16565 - }, - { - "key_as_string": "2018-06-08T15:00:00.000Z", - "key": 1528470000000, - "doc_count": 16543 - }, - { - "key_as_string": "2018-06-08T18:00:00.000Z", - "key": 1528480800000, - "doc_count": 16492 - }, - { - "key_as_string": "2018-06-08T21:00:00.000Z", - "key": 1528491600000, - "doc_count": 16404 - }, - { - "key_as_string": "2018-06-09T00:00:00.000Z", - "key": 1528502400000, - "doc_count": 4528 - }, - { - "key_as_string": "2018-06-09T03:00:00.000Z", - "key": 1528513200000, - "doc_count": 4557 - }, - { - "key_as_string": "2018-06-09T06:00:00.000Z", - "key": 1528524000000, - "doc_count": 4566 - }, - { - "key_as_string": "2018-06-09T09:00:00.000Z", - "key": 1528534800000, - "doc_count": 4586 - }, - { - "key_as_string": "2018-06-09T12:00:00.000Z", - "key": 1528545600000, - "doc_count": 4672 - }, - { - "key_as_string": "2018-06-09T15:00:00.000Z", - "key": 1528556400000, - "doc_count": 4685 - }, - { - "key_as_string": "2018-06-09T18:00:00.000Z", - "key": 1528567200000, - "doc_count": 4521 - }, - { - "key_as_string": "2018-06-09T21:00:00.000Z", - "key": 1528578000000, - "doc_count": 4612 - }, - { - "key_as_string": "2018-06-10T00:00:00.000Z", - "key": 1528588800000, - "doc_count": 4535 - }, - { - "key_as_string": "2018-06-10T03:00:00.000Z", - "key": 1528599600000, - "doc_count": 4606 - }, - { - "key_as_string": "2018-06-10T06:00:00.000Z", - "key": 1528610400000, - "doc_count": 4614 - }, - { - "key_as_string": "2018-06-10T09:00:00.000Z", - "key": 1528621200000, - "doc_count": 4507 - }, - { - "key_as_string": "2018-06-10T12:00:00.000Z", - "key": 1528632000000, - "doc_count": 4611 - }, - { - "key_as_string": "2018-06-10T15:00:00.000Z", - "key": 1528642800000, - "doc_count": 4587 - }, - { - "key_as_string": "2018-06-10T18:00:00.000Z", - "key": 1528653600000, - "doc_count": 4582 - }, - { - "key_as_string": "2018-06-10T21:00:00.000Z", - "key": 1528664400000, - "doc_count": 4615 - }, - { - "key_as_string": "2018-06-11T00:00:00.000Z", - "key": 1528675200000, - "doc_count": 16251 - }, - { - "key_as_string": "2018-06-11T03:00:00.000Z", - "key": 1528686000000, - "doc_count": 16825 - }, - { - "key_as_string": "2018-06-11T06:00:00.000Z", - "key": 1528696800000, - "doc_count": 16288 - }, - { - "key_as_string": "2018-06-11T09:00:00.000Z", - "key": 1528707600000, - "doc_count": 16492 - }, - { - "key_as_string": "2018-06-11T12:00:00.000Z", - "key": 1528718400000, - "doc_count": 16434 - }, - { - "key_as_string": "2018-06-11T15:00:00.000Z", - "key": 1528729200000, - "doc_count": 17003 - }, - { - "key_as_string": "2018-06-11T18:00:00.000Z", - "key": 1528740000000, - "doc_count": 16364 - }, - { - "key_as_string": "2018-06-11T21:00:00.000Z", - "key": 1528750800000, - "doc_count": 16645 - }, - { - "key_as_string": "2018-06-12T00:00:00.000Z", - "key": 1528761600000, - "doc_count": 16695 - }, - { - "key_as_string": "2018-06-12T03:00:00.000Z", - "key": 1528772400000, - "doc_count": 16498 - }, - { - "key_as_string": "2018-06-12T06:00:00.000Z", - "key": 1528783200000, - "doc_count": 16588 - }, - { - "key_as_string": "2018-06-12T09:00:00.000Z", - "key": 1528794000000, - "doc_count": 16685 - }, - { - "key_as_string": "2018-06-12T12:00:00.000Z", - "key": 1528804800000, - "doc_count": 16361 - }, - { - "key_as_string": "2018-06-12T15:00:00.000Z", - "key": 1528815600000, - "doc_count": 16658 - }, - { - "key_as_string": "2018-06-12T18:00:00.000Z", - "key": 1528826400000, - "doc_count": 16507 - }, - { - "key_as_string": "2018-06-12T21:00:00.000Z", - "key": 1528837200000, - "doc_count": 16418 - }, - { - "key_as_string": "2018-06-13T00:00:00.000Z", - "key": 1528848000000, - "doc_count": 16477 - }, - { - "key_as_string": "2018-06-13T03:00:00.000Z", - "key": 1528858800000, - "doc_count": 16755 - }, - { - "key_as_string": "2018-06-13T06:00:00.000Z", - "key": 1528869600000, - "doc_count": 16594 - }, - { - "key_as_string": "2018-06-13T09:00:00.000Z", - "key": 1528880400000, - "doc_count": 16812 - }, - { - "key_as_string": "2018-06-13T12:00:00.000Z", - "key": 1528891200000, - "doc_count": 16863 - }, - { - "key_as_string": "2018-06-13T15:00:00.000Z", - "key": 1528902000000, - "doc_count": 16655 - }, - { - "key_as_string": "2018-06-13T18:00:00.000Z", - "key": 1528912800000, - "doc_count": 16723 - }, - { - "key_as_string": "2018-06-13T21:00:00.000Z", - "key": 1528923600000, - "doc_count": 16577 - }, - { - "key_as_string": "2018-06-14T00:00:00.000Z", - "key": 1528934400000, - "doc_count": 15125 - }, - { - "key_as_string": "2018-06-14T03:00:00.000Z", - "key": 1528945200000, - "doc_count": 16432 - }, - { - "key_as_string": "2018-06-14T06:00:00.000Z", - "key": 1528956000000, - "doc_count": 16464 - }, - { - "key_as_string": "2018-06-14T09:00:00.000Z", - "key": 1528966800000, - "doc_count": 16369 - }, - { - "key_as_string": "2018-06-14T12:00:00.000Z", - "key": 1528977600000, - "doc_count": 0 - } - ] - } - }, - { - "key": "HTTP 5xx", - "doc_count": 82036, - "timeseries": { - "buckets": [ - { - "key_as_string": "2018-06-04T12:00:00.000Z", - "key": 1528113600000, - "doc_count": 1209 - }, - { - "key_as_string": "2018-06-04T15:00:00.000Z", - "key": 1528124400000, - "doc_count": 1203 - }, - { - "key_as_string": "2018-06-04T18:00:00.000Z", - "key": 1528135200000, - "doc_count": 1196 - }, - { - "key_as_string": "2018-06-04T21:00:00.000Z", - "key": 1528146000000, - "doc_count": 1230 - }, - { - "key_as_string": "2018-06-05T00:00:00.000Z", - "key": 1528156800000, - "doc_count": 1233 - }, - { - "key_as_string": "2018-06-05T03:00:00.000Z", - "key": 1528167600000, - "doc_count": 1272 - }, - { - "key_as_string": "2018-06-05T06:00:00.000Z", - "key": 1528178400000, - "doc_count": 1218 - }, - { - "key_as_string": "2018-06-05T09:00:00.000Z", - "key": 1528189200000, - "doc_count": 1217 - }, - { - "key_as_string": "2018-06-05T12:00:00.000Z", - "key": 1528200000000, - "doc_count": 1235 - }, - { - "key_as_string": "2018-06-05T15:00:00.000Z", - "key": 1528210800000, - "doc_count": 1249 - }, - { - "key_as_string": "2018-06-05T18:00:00.000Z", - "key": 1528221600000, - "doc_count": 1158 - }, - { - "key_as_string": "2018-06-05T21:00:00.000Z", - "key": 1528232400000, - "doc_count": 1215 - }, - { - "key_as_string": "2018-06-06T00:00:00.000Z", - "key": 1528243200000, - "doc_count": 1191 - }, - { - "key_as_string": "2018-06-06T03:00:00.000Z", - "key": 1528254000000, - "doc_count": 1235 - }, - { - "key_as_string": "2018-06-06T06:00:00.000Z", - "key": 1528264800000, - "doc_count": 1212 - }, - { - "key_as_string": "2018-06-06T09:00:00.000Z", - "key": 1528275600000, - "doc_count": 1180 - }, - { - "key_as_string": "2018-06-06T12:00:00.000Z", - "key": 1528286400000, - "doc_count": 1091 - }, - { - "key_as_string": "2018-06-06T15:00:00.000Z", - "key": 1528297200000, - "doc_count": 1176 - }, - { - "key_as_string": "2018-06-06T18:00:00.000Z", - "key": 1528308000000, - "doc_count": 1243 - }, - { - "key_as_string": "2018-06-06T21:00:00.000Z", - "key": 1528318800000, - "doc_count": 1208 - }, - { - "key_as_string": "2018-06-07T00:00:00.000Z", - "key": 1528329600000, - "doc_count": 1202 - }, - { - "key_as_string": "2018-06-07T03:00:00.000Z", - "key": 1528340400000, - "doc_count": 1288 - }, - { - "key_as_string": "2018-06-07T06:00:00.000Z", - "key": 1528351200000, - "doc_count": 1241 - }, - { - "key_as_string": "2018-06-07T09:00:00.000Z", - "key": 1528362000000, - "doc_count": 1215 - }, - { - "key_as_string": "2018-06-07T12:00:00.000Z", - "key": 1528372800000, - "doc_count": 1152 - }, - { - "key_as_string": "2018-06-07T15:00:00.000Z", - "key": 1528383600000, - "doc_count": 1241 - }, - { - "key_as_string": "2018-06-07T18:00:00.000Z", - "key": 1528394400000, - "doc_count": 1177 - }, - { - "key_as_string": "2018-06-07T21:00:00.000Z", - "key": 1528405200000, - "doc_count": 1243 - }, - { - "key_as_string": "2018-06-08T00:00:00.000Z", - "key": 1528416000000, - "doc_count": 1255 - }, - { - "key_as_string": "2018-06-08T03:00:00.000Z", - "key": 1528426800000, - "doc_count": 1189 - }, - { - "key_as_string": "2018-06-08T06:00:00.000Z", - "key": 1528437600000, - "doc_count": 1183 - }, - { - "key_as_string": "2018-06-08T09:00:00.000Z", - "key": 1528448400000, - "doc_count": 1215 - }, - { - "key_as_string": "2018-06-08T12:00:00.000Z", - "key": 1528459200000, - "doc_count": 1282 - }, - { - "key_as_string": "2018-06-08T15:00:00.000Z", - "key": 1528470000000, - "doc_count": 1177 - }, - { - "key_as_string": "2018-06-08T18:00:00.000Z", - "key": 1528480800000, - "doc_count": 1199 - }, - { - "key_as_string": "2018-06-08T21:00:00.000Z", - "key": 1528491600000, - "doc_count": 1234 - }, - { - "key_as_string": "2018-06-09T00:00:00.000Z", - "key": 1528502400000, - "doc_count": 284 - }, - { - "key_as_string": "2018-06-09T03:00:00.000Z", - "key": 1528513200000, - "doc_count": 307 - }, - { - "key_as_string": "2018-06-09T06:00:00.000Z", - "key": 1528524000000, - "doc_count": 283 - }, - { - "key_as_string": "2018-06-09T09:00:00.000Z", - "key": 1528534800000, - "doc_count": 303 - }, - { - "key_as_string": "2018-06-09T12:00:00.000Z", - "key": 1528545600000, - "doc_count": 326 - }, - { - "key_as_string": "2018-06-09T15:00:00.000Z", - "key": 1528556400000, - "doc_count": 269 - }, - { - "key_as_string": "2018-06-09T18:00:00.000Z", - "key": 1528567200000, - "doc_count": 297 - }, - { - "key_as_string": "2018-06-09T21:00:00.000Z", - "key": 1528578000000, - "doc_count": 278 - }, - { - "key_as_string": "2018-06-10T00:00:00.000Z", - "key": 1528588800000, - "doc_count": 289 - }, - { - "key_as_string": "2018-06-10T03:00:00.000Z", - "key": 1528599600000, - "doc_count": 272 - }, - { - "key_as_string": "2018-06-10T06:00:00.000Z", - "key": 1528610400000, - "doc_count": 279 - }, - { - "key_as_string": "2018-06-10T09:00:00.000Z", - "key": 1528621200000, - "doc_count": 238 - }, - { - "key_as_string": "2018-06-10T12:00:00.000Z", - "key": 1528632000000, - "doc_count": 288 - }, - { - "key_as_string": "2018-06-10T15:00:00.000Z", - "key": 1528642800000, - "doc_count": 258 - }, - { - "key_as_string": "2018-06-10T18:00:00.000Z", - "key": 1528653600000, - "doc_count": 264 - }, - { - "key_as_string": "2018-06-10T21:00:00.000Z", - "key": 1528664400000, - "doc_count": 296 - }, - { - "key_as_string": "2018-06-11T00:00:00.000Z", - "key": 1528675200000, - "doc_count": 1213 - }, - { - "key_as_string": "2018-06-11T03:00:00.000Z", - "key": 1528686000000, - "doc_count": 1254 - }, - { - "key_as_string": "2018-06-11T06:00:00.000Z", - "key": 1528696800000, - "doc_count": 1135 - }, - { - "key_as_string": "2018-06-11T09:00:00.000Z", - "key": 1528707600000, - "doc_count": 1240 - }, - { - "key_as_string": "2018-06-11T12:00:00.000Z", - "key": 1528718400000, - "doc_count": 1215 - }, - { - "key_as_string": "2018-06-11T15:00:00.000Z", - "key": 1528729200000, - "doc_count": 1239 - }, - { - "key_as_string": "2018-06-11T18:00:00.000Z", - "key": 1528740000000, - "doc_count": 1209 - }, - { - "key_as_string": "2018-06-11T21:00:00.000Z", - "key": 1528750800000, - "doc_count": 1208 - }, - { - "key_as_string": "2018-06-12T00:00:00.000Z", - "key": 1528761600000, - "doc_count": 1176 - }, - { - "key_as_string": "2018-06-12T03:00:00.000Z", - "key": 1528772400000, - "doc_count": 1207 - }, - { - "key_as_string": "2018-06-12T06:00:00.000Z", - "key": 1528783200000, - "doc_count": 1198 - }, - { - "key_as_string": "2018-06-12T09:00:00.000Z", - "key": 1528794000000, - "doc_count": 1165 - }, - { - "key_as_string": "2018-06-12T12:00:00.000Z", - "key": 1528804800000, - "doc_count": 1188 - }, - { - "key_as_string": "2018-06-12T15:00:00.000Z", - "key": 1528815600000, - "doc_count": 1245 - }, - { - "key_as_string": "2018-06-12T18:00:00.000Z", - "key": 1528826400000, - "doc_count": 1238 - }, - { - "key_as_string": "2018-06-12T21:00:00.000Z", - "key": 1528837200000, - "doc_count": 1283 - }, - { - "key_as_string": "2018-06-13T00:00:00.000Z", - "key": 1528848000000, - "doc_count": 1198 - }, - { - "key_as_string": "2018-06-13T03:00:00.000Z", - "key": 1528858800000, - "doc_count": 1172 - }, - { - "key_as_string": "2018-06-13T06:00:00.000Z", - "key": 1528869600000, - "doc_count": 1229 - }, - { - "key_as_string": "2018-06-13T09:00:00.000Z", - "key": 1528880400000, - "doc_count": 1239 - }, - { - "key_as_string": "2018-06-13T12:00:00.000Z", - "key": 1528891200000, - "doc_count": 1231 - }, - { - "key_as_string": "2018-06-13T15:00:00.000Z", - "key": 1528902000000, - "doc_count": 1248 - }, - { - "key_as_string": "2018-06-13T18:00:00.000Z", - "key": 1528912800000, - "doc_count": 1220 - }, - { - "key_as_string": "2018-06-13T21:00:00.000Z", - "key": 1528923600000, - "doc_count": 1224 - }, - { - "key_as_string": "2018-06-14T00:00:00.000Z", - "key": 1528934400000, - "doc_count": 1088 - }, - { - "key_as_string": "2018-06-14T03:00:00.000Z", - "key": 1528945200000, - "doc_count": 1235 - }, - { - "key_as_string": "2018-06-14T06:00:00.000Z", - "key": 1528956000000, - "doc_count": 1161 - }, - { - "key_as_string": "2018-06-14T09:00:00.000Z", - "key": 1528966800000, - "doc_count": 1183 - }, - { - "key_as_string": "2018-06-14T12:00:00.000Z", - "key": 1528977600000, - "doc_count": 0 - } - ] - } - }, - { - "key": "HTTP 4xx", - "doc_count": 81907, - "timeseries": { - "buckets": [ - { - "key_as_string": "2018-06-04T12:00:00.000Z", - "key": 1528113600000, - "doc_count": 1186 - }, - { - "key_as_string": "2018-06-04T15:00:00.000Z", - "key": 1528124400000, - "doc_count": 1213 - }, - { - "key_as_string": "2018-06-04T18:00:00.000Z", - "key": 1528135200000, - "doc_count": 1205 - }, - { - "key_as_string": "2018-06-04T21:00:00.000Z", - "key": 1528146000000, - "doc_count": 1162 - }, - { - "key_as_string": "2018-06-05T00:00:00.000Z", - "key": 1528156800000, - "doc_count": 1238 - }, - { - "key_as_string": "2018-06-05T03:00:00.000Z", - "key": 1528167600000, - "doc_count": 1191 - }, - { - "key_as_string": "2018-06-05T06:00:00.000Z", - "key": 1528178400000, - "doc_count": 1274 - }, - { - "key_as_string": "2018-06-05T09:00:00.000Z", - "key": 1528189200000, - "doc_count": 1234 - }, - { - "key_as_string": "2018-06-05T12:00:00.000Z", - "key": 1528200000000, - "doc_count": 1164 - }, - { - "key_as_string": "2018-06-05T15:00:00.000Z", - "key": 1528210800000, - "doc_count": 1233 - }, - { - "key_as_string": "2018-06-05T18:00:00.000Z", - "key": 1528221600000, - "doc_count": 1223 - }, - { - "key_as_string": "2018-06-05T21:00:00.000Z", - "key": 1528232400000, - "doc_count": 1216 - }, - { - "key_as_string": "2018-06-06T00:00:00.000Z", - "key": 1528243200000, - "doc_count": 1200 - }, - { - "key_as_string": "2018-06-06T03:00:00.000Z", - "key": 1528254000000, - "doc_count": 1237 - }, - { - "key_as_string": "2018-06-06T06:00:00.000Z", - "key": 1528264800000, - "doc_count": 1231 - }, - { - "key_as_string": "2018-06-06T09:00:00.000Z", - "key": 1528275600000, - "doc_count": 1182 - }, - { - "key_as_string": "2018-06-06T12:00:00.000Z", - "key": 1528286400000, - "doc_count": 1125 - }, - { - "key_as_string": "2018-06-06T15:00:00.000Z", - "key": 1528297200000, - "doc_count": 1243 - }, - { - "key_as_string": "2018-06-06T18:00:00.000Z", - "key": 1528308000000, - "doc_count": 1247 - }, - { - "key_as_string": "2018-06-06T21:00:00.000Z", - "key": 1528318800000, - "doc_count": 1163 - }, - { - "key_as_string": "2018-06-07T00:00:00.000Z", - "key": 1528329600000, - "doc_count": 1220 - }, - { - "key_as_string": "2018-06-07T03:00:00.000Z", - "key": 1528340400000, - "doc_count": 1202 - }, - { - "key_as_string": "2018-06-07T06:00:00.000Z", - "key": 1528351200000, - "doc_count": 1192 - }, - { - "key_as_string": "2018-06-07T09:00:00.000Z", - "key": 1528362000000, - "doc_count": 1248 - }, - { - "key_as_string": "2018-06-07T12:00:00.000Z", - "key": 1528372800000, - "doc_count": 1189 - }, - { - "key_as_string": "2018-06-07T15:00:00.000Z", - "key": 1528383600000, - "doc_count": 1230 - }, - { - "key_as_string": "2018-06-07T18:00:00.000Z", - "key": 1528394400000, - "doc_count": 1206 - }, - { - "key_as_string": "2018-06-07T21:00:00.000Z", - "key": 1528405200000, - "doc_count": 1190 - }, - { - "key_as_string": "2018-06-08T00:00:00.000Z", - "key": 1528416000000, - "doc_count": 1232 - }, - { - "key_as_string": "2018-06-08T03:00:00.000Z", - "key": 1528426800000, - "doc_count": 1171 - }, - { - "key_as_string": "2018-06-08T06:00:00.000Z", - "key": 1528437600000, - "doc_count": 1232 - }, - { - "key_as_string": "2018-06-08T09:00:00.000Z", - "key": 1528448400000, - "doc_count": 1253 - }, - { - "key_as_string": "2018-06-08T12:00:00.000Z", - "key": 1528459200000, - "doc_count": 1250 - }, - { - "key_as_string": "2018-06-08T15:00:00.000Z", - "key": 1528470000000, - "doc_count": 1167 - }, - { - "key_as_string": "2018-06-08T18:00:00.000Z", - "key": 1528480800000, - "doc_count": 1258 - }, - { - "key_as_string": "2018-06-08T21:00:00.000Z", - "key": 1528491600000, - "doc_count": 1148 - }, - { - "key_as_string": "2018-06-09T00:00:00.000Z", - "key": 1528502400000, - "doc_count": 284 - }, - { - "key_as_string": "2018-06-09T03:00:00.000Z", - "key": 1528513200000, - "doc_count": 240 - }, - { - "key_as_string": "2018-06-09T06:00:00.000Z", - "key": 1528524000000, - "doc_count": 273 - }, - { - "key_as_string": "2018-06-09T09:00:00.000Z", - "key": 1528534800000, - "doc_count": 295 - }, - { - "key_as_string": "2018-06-09T12:00:00.000Z", - "key": 1528545600000, - "doc_count": 281 - }, - { - "key_as_string": "2018-06-09T15:00:00.000Z", - "key": 1528556400000, - "doc_count": 300 - }, - { - "key_as_string": "2018-06-09T18:00:00.000Z", - "key": 1528567200000, - "doc_count": 264 - }, - { - "key_as_string": "2018-06-09T21:00:00.000Z", - "key": 1528578000000, - "doc_count": 260 - }, - { - "key_as_string": "2018-06-10T00:00:00.000Z", - "key": 1528588800000, - "doc_count": 279 - }, - { - "key_as_string": "2018-06-10T03:00:00.000Z", - "key": 1528599600000, - "doc_count": 259 - }, - { - "key_as_string": "2018-06-10T06:00:00.000Z", - "key": 1528610400000, - "doc_count": 291 - }, - { - "key_as_string": "2018-06-10T09:00:00.000Z", - "key": 1528621200000, - "doc_count": 248 - }, - { - "key_as_string": "2018-06-10T12:00:00.000Z", - "key": 1528632000000, - "doc_count": 311 - }, - { - "key_as_string": "2018-06-10T15:00:00.000Z", - "key": 1528642800000, - "doc_count": 277 - }, - { - "key_as_string": "2018-06-10T18:00:00.000Z", - "key": 1528653600000, - "doc_count": 279 - }, - { - "key_as_string": "2018-06-10T21:00:00.000Z", - "key": 1528664400000, - "doc_count": 275 - }, - { - "key_as_string": "2018-06-11T00:00:00.000Z", - "key": 1528675200000, - "doc_count": 1167 - }, - { - "key_as_string": "2018-06-11T03:00:00.000Z", - "key": 1528686000000, - "doc_count": 1270 - }, - { - "key_as_string": "2018-06-11T06:00:00.000Z", - "key": 1528696800000, - "doc_count": 1163 - }, - { - "key_as_string": "2018-06-11T09:00:00.000Z", - "key": 1528707600000, - "doc_count": 1155 - }, - { - "key_as_string": "2018-06-11T12:00:00.000Z", - "key": 1528718400000, - "doc_count": 1217 - }, - { - "key_as_string": "2018-06-11T15:00:00.000Z", - "key": 1528729200000, - "doc_count": 1227 - }, - { - "key_as_string": "2018-06-11T18:00:00.000Z", - "key": 1528740000000, - "doc_count": 1194 - }, - { - "key_as_string": "2018-06-11T21:00:00.000Z", - "key": 1528750800000, - "doc_count": 1153 - }, - { - "key_as_string": "2018-06-12T00:00:00.000Z", - "key": 1528761600000, - "doc_count": 1211 - }, - { - "key_as_string": "2018-06-12T03:00:00.000Z", - "key": 1528772400000, - "doc_count": 1203 - }, - { - "key_as_string": "2018-06-12T06:00:00.000Z", - "key": 1528783200000, - "doc_count": 1269 - }, - { - "key_as_string": "2018-06-12T09:00:00.000Z", - "key": 1528794000000, - "doc_count": 1197 - }, - { - "key_as_string": "2018-06-12T12:00:00.000Z", - "key": 1528804800000, - "doc_count": 1184 - }, - { - "key_as_string": "2018-06-12T15:00:00.000Z", - "key": 1528815600000, - "doc_count": 1176 - }, - { - "key_as_string": "2018-06-12T18:00:00.000Z", - "key": 1528826400000, - "doc_count": 1162 - }, - { - "key_as_string": "2018-06-12T21:00:00.000Z", - "key": 1528837200000, - "doc_count": 1270 - }, - { - "key_as_string": "2018-06-13T00:00:00.000Z", - "key": 1528848000000, - "doc_count": 1224 - }, - { - "key_as_string": "2018-06-13T03:00:00.000Z", - "key": 1528858800000, - "doc_count": 1255 - }, - { - "key_as_string": "2018-06-13T06:00:00.000Z", - "key": 1528869600000, - "doc_count": 1207 - }, - { - "key_as_string": "2018-06-13T09:00:00.000Z", - "key": 1528880400000, - "doc_count": 1206 - }, - { - "key_as_string": "2018-06-13T12:00:00.000Z", - "key": 1528891200000, - "doc_count": 1254 - }, - { - "key_as_string": "2018-06-13T15:00:00.000Z", - "key": 1528902000000, - "doc_count": 1216 - }, - { - "key_as_string": "2018-06-13T18:00:00.000Z", - "key": 1528912800000, - "doc_count": 1263 - }, - { - "key_as_string": "2018-06-13T21:00:00.000Z", - "key": 1528923600000, - "doc_count": 1277 - }, - { - "key_as_string": "2018-06-14T00:00:00.000Z", - "key": 1528934400000, - "doc_count": 1183 - }, - { - "key_as_string": "2018-06-14T03:00:00.000Z", - "key": 1528945200000, - "doc_count": 1221 - }, - { - "key_as_string": "2018-06-14T06:00:00.000Z", - "key": 1528956000000, - "doc_count": 1198 - }, - { - "key_as_string": "2018-06-14T09:00:00.000Z", - "key": 1528966800000, - "doc_count": 1214 - }, - { - "key_as_string": "2018-06-14T12:00:00.000Z", - "key": 1528977600000, - "doc_count": 0 - } - ] - } - }, - { - "key": "HTTP 3xx", - "doc_count": 6650, - "timeseries": { - "buckets": [ - { - "key_as_string": "2018-06-04T12:00:00.000Z", - "key": 1528113600000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-04T15:00:00.000Z", - "key": 1528124400000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-04T18:00:00.000Z", - "key": 1528135200000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-04T21:00:00.000Z", - "key": 1528146000000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-05T00:00:00.000Z", - "key": 1528156800000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-05T03:00:00.000Z", - "key": 1528167600000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-05T06:00:00.000Z", - "key": 1528178400000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-05T09:00:00.000Z", - "key": 1528189200000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-05T12:00:00.000Z", - "key": 1528200000000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-05T15:00:00.000Z", - "key": 1528210800000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-05T18:00:00.000Z", - "key": 1528221600000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-05T21:00:00.000Z", - "key": 1528232400000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-06T00:00:00.000Z", - "key": 1528243200000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-06T03:00:00.000Z", - "key": 1528254000000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-06T06:00:00.000Z", - "key": 1528264800000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-06T09:00:00.000Z", - "key": 1528275600000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-06T12:00:00.000Z", - "key": 1528286400000, - "doc_count": 4041 - }, - { - "key_as_string": "2018-06-06T15:00:00.000Z", - "key": 1528297200000, - "doc_count": 454 - }, - { - "key_as_string": "2018-06-06T18:00:00.000Z", - "key": 1528308000000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-06T21:00:00.000Z", - "key": 1528318800000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-07T00:00:00.000Z", - "key": 1528329600000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-07T03:00:00.000Z", - "key": 1528340400000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-07T06:00:00.000Z", - "key": 1528351200000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-07T09:00:00.000Z", - "key": 1528362000000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-07T12:00:00.000Z", - "key": 1528372800000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-07T15:00:00.000Z", - "key": 1528383600000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-07T18:00:00.000Z", - "key": 1528394400000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-07T21:00:00.000Z", - "key": 1528405200000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-08T00:00:00.000Z", - "key": 1528416000000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-08T03:00:00.000Z", - "key": 1528426800000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-08T06:00:00.000Z", - "key": 1528437600000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-08T09:00:00.000Z", - "key": 1528448400000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-08T12:00:00.000Z", - "key": 1528459200000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-08T15:00:00.000Z", - "key": 1528470000000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-08T18:00:00.000Z", - "key": 1528480800000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-08T21:00:00.000Z", - "key": 1528491600000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-09T00:00:00.000Z", - "key": 1528502400000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-09T03:00:00.000Z", - "key": 1528513200000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-09T06:00:00.000Z", - "key": 1528524000000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-09T09:00:00.000Z", - "key": 1528534800000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-09T12:00:00.000Z", - "key": 1528545600000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-09T15:00:00.000Z", - "key": 1528556400000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-09T18:00:00.000Z", - "key": 1528567200000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-09T21:00:00.000Z", - "key": 1528578000000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-10T00:00:00.000Z", - "key": 1528588800000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-10T03:00:00.000Z", - "key": 1528599600000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-10T06:00:00.000Z", - "key": 1528610400000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-10T09:00:00.000Z", - "key": 1528621200000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-10T12:00:00.000Z", - "key": 1528632000000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-10T15:00:00.000Z", - "key": 1528642800000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-10T18:00:00.000Z", - "key": 1528653600000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-10T21:00:00.000Z", - "key": 1528664400000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-11T00:00:00.000Z", - "key": 1528675200000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-11T03:00:00.000Z", - "key": 1528686000000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-11T06:00:00.000Z", - "key": 1528696800000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-11T09:00:00.000Z", - "key": 1528707600000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-11T12:00:00.000Z", - "key": 1528718400000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-11T15:00:00.000Z", - "key": 1528729200000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-11T18:00:00.000Z", - "key": 1528740000000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-11T21:00:00.000Z", - "key": 1528750800000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-12T00:00:00.000Z", - "key": 1528761600000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-12T03:00:00.000Z", - "key": 1528772400000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-12T06:00:00.000Z", - "key": 1528783200000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-12T09:00:00.000Z", - "key": 1528794000000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-12T12:00:00.000Z", - "key": 1528804800000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-12T15:00:00.000Z", - "key": 1528815600000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-12T18:00:00.000Z", - "key": 1528826400000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-12T21:00:00.000Z", - "key": 1528837200000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-13T00:00:00.000Z", - "key": 1528848000000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-13T03:00:00.000Z", - "key": 1528858800000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-13T06:00:00.000Z", - "key": 1528869600000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-13T09:00:00.000Z", - "key": 1528880400000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-13T12:00:00.000Z", - "key": 1528891200000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-13T15:00:00.000Z", - "key": 1528902000000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-13T18:00:00.000Z", - "key": 1528912800000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-13T21:00:00.000Z", - "key": 1528923600000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-14T00:00:00.000Z", - "key": 1528934400000, - "doc_count": 2155 - }, - { - "key_as_string": "2018-06-14T03:00:00.000Z", - "key": 1528945200000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-14T06:00:00.000Z", - "key": 1528956000000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-14T09:00:00.000Z", - "key": 1528966800000, - "doc_count": 0 - }, - { - "key_as_string": "2018-06-14T12:00:00.000Z", - "key": 1528977600000, - "doc_count": 0 - } - ] - } - } - ] - }, - "response_times": { - "buckets": [ - { - "key_as_string": "2018-06-04T12:00:00.000Z", - "key": 1528113600000, - "doc_count": 18841, - "pct": { - "values": { - "95.0": 82172.85648714812, - "99.0": 293866.3866666665 - } - }, - "avg": { - "value": 26310.63483891513 - } - }, - { - "key_as_string": "2018-06-04T15:00:00.000Z", - "key": 1528124400000, - "doc_count": 18708, - "pct": { - "values": { - "95.0": 80738.78571428556, - "99.0": 293257.27333333343 - } - }, - "avg": { - "value": 26193.277795595466 - } - }, - { - "key_as_string": "2018-06-04T18:00:00.000Z", - "key": 1528135200000, - "doc_count": 18865, - "pct": { - "values": { - "95.0": 77058.03529411761, - "99.0": 290195.8800000004 - } - }, - "avg": { - "value": 25291.787065995228 - } - }, - { - "key_as_string": "2018-06-04T21:00:00.000Z", - "key": 1528146000000, - "doc_count": 18889, - "pct": { - "values": { - "95.0": 77892.20721980717, - "99.0": 278548.1649999994 - } - }, - "avg": { - "value": 24690.306474667796 - } - }, - { - "key_as_string": "2018-06-05T00:00:00.000Z", - "key": 1528156800000, - "doc_count": 19270, - "pct": { - "values": { - "95.0": 77085.86687499998, - "99.0": 290701.8973333341 - } - }, - "avg": { - "value": 24809.8953814219 - } - }, - { - "key_as_string": "2018-06-05T03:00:00.000Z", - "key": 1528167600000, - "doc_count": 19024, - "pct": { - "values": { - "95.0": 80048.3462981744, - "99.0": 286839.5897777779 - } - }, - "avg": { - "value": 25460.0394764508 - } - }, - { - "key_as_string": "2018-06-05T06:00:00.000Z", - "key": 1528178400000, - "doc_count": 18923, - "pct": { - "values": { - "95.0": 84089.21370223971, - "99.0": 287979.5149999999 - } - }, - "avg": { - "value": 26360.440733498916 - } - }, - { - "key_as_string": "2018-06-05T09:00:00.000Z", - "key": 1528189200000, - "doc_count": 18834, - "pct": { - "values": { - "95.0": 84880.90143416924, - "99.0": 300107.5009999992 - } - }, - "avg": { - "value": 27050.95205479452 - } - }, - { - "key_as_string": "2018-06-05T12:00:00.000Z", - "key": 1528200000000, - "doc_count": 18694, - "pct": { - "values": { - "95.0": 84554.8884781166, - "99.0": 294402.2179999999 - } - }, - "avg": { - "value": 26555.857333903925 - } - }, - { - "key_as_string": "2018-06-05T15:00:00.000Z", - "key": 1528210800000, - "doc_count": 19184, - "pct": { - "values": { - "95.0": 81839.39583333326, - "99.0": 289849.459333332 - } - }, - "avg": { - "value": 26164.343359049206 - } - }, - { - "key_as_string": "2018-06-05T18:00:00.000Z", - "key": 1528221600000, - "doc_count": 18850, - "pct": { - "values": { - "95.0": 85993.55410163336, - "99.0": 296942.86299999955 - } - }, - "avg": { - "value": 26989.84546419098 - } - }, - { - "key_as_string": "2018-06-05T21:00:00.000Z", - "key": 1528232400000, - "doc_count": 18897, - "pct": { - "values": { - "95.0": 85001.44588628765, - "99.0": 292048.20571428596 - } - }, - "avg": { - "value": 26314.409430068266 - } - }, - { - "key_as_string": "2018-06-06T00:00:00.000Z", - "key": 1528243200000, - "doc_count": 18942, - "pct": { - "values": { - "95.0": 86980.16445312503, - "99.0": 299308.7371666667 - } - }, - "avg": { - "value": 27460.774575018477 - } - }, - { - "key_as_string": "2018-06-06T03:00:00.000Z", - "key": 1528254000000, - "doc_count": 19147, - "pct": { - "values": { - "95.0": 84961.8710743802, - "99.0": 292151.2377777781 - } - }, - "avg": { - "value": 26461.469107431974 - } - }, - { - "key_as_string": "2018-06-06T06:00:00.000Z", - "key": 1528264800000, - "doc_count": 18853, - "pct": { - "values": { - "95.0": 88906.54601889332, - "99.0": 302274.4192592592 - } - }, - "avg": { - "value": 27657.584946692834 - } - }, - { - "key_as_string": "2018-06-06T09:00:00.000Z", - "key": 1528275600000, - "doc_count": 18609, - "pct": { - "values": { - "95.0": 90198.34708994703, - "99.0": 299457.1612121209 - } - }, - "avg": { - "value": 27940.445967005213 - } - }, - { - "key_as_string": "2018-06-06T12:00:00.000Z", - "key": 1528286400000, - "doc_count": 21402, - "pct": { - "values": { - "95.0": 135627.71242424246, - "99.0": 350398.59259259375 - } - }, - "avg": { - "value": 34454.377581534434 - } - }, - { - "key_as_string": "2018-06-06T15:00:00.000Z", - "key": 1528297200000, - "doc_count": 19051, - "pct": { - "values": { - "95.0": 167037.1993837535, - "99.0": 421204.23333333334 - } - }, - "avg": { - "value": 44024.31809353839 - } - }, - { - "key_as_string": "2018-06-06T18:00:00.000Z", - "key": 1528308000000, - "doc_count": 19020, - "pct": { - "values": { - "95.0": 128293.12184873945, - "99.0": 368166.68976190523 - } - }, - "avg": { - "value": 36374.53333333333 - } - }, - { - "key_as_string": "2018-06-06T21:00:00.000Z", - "key": 1528318800000, - "doc_count": 18582, - "pct": { - "values": { - "95.0": 130653.54236263742, - "99.0": 367193.6128571426 - } - }, - "avg": { - "value": 36991.29442471209 - } - }, - { - "key_as_string": "2018-06-07T00:00:00.000Z", - "key": 1528329600000, - "doc_count": 18875, - "pct": { - "values": { - "95.0": 131630.8902645502, - "99.0": 375658.10190476174 - } - }, - "avg": { - "value": 37178.002701986756 - } - }, - { - "key_as_string": "2018-06-07T03:00:00.000Z", - "key": 1528340400000, - "doc_count": 18993, - "pct": { - "values": { - "95.0": 133581.33541666638, - "99.0": 368152.03822222137 - } - }, - "avg": { - "value": 37605.57078923814 - } - }, - { - "key_as_string": "2018-06-07T06:00:00.000Z", - "key": 1528351200000, - "doc_count": 19037, - "pct": { - "values": { - "95.0": 132697.92762266204, - "99.0": 365705.8319999995 - } - }, - "avg": { - "value": 37319.89767295267 - } - }, - { - "key_as_string": "2018-06-07T09:00:00.000Z", - "key": 1528362000000, - "doc_count": 18985, - "pct": { - "values": { - "95.0": 140003.6918918918, - "99.0": 380075.48533333326 - } - }, - "avg": { - "value": 38709.5041348433 - } - }, - { - "key_as_string": "2018-06-07T12:00:00.000Z", - "key": 1528372800000, - "doc_count": 18505, - "pct": { - "values": { - "95.0": 138149.5673529411, - "99.0": 375697.1923809518 - } - }, - "avg": { - "value": 38140.131856255066 - } - }, - { - "key_as_string": "2018-06-07T15:00:00.000Z", - "key": 1528383600000, - "doc_count": 18991, - "pct": { - "values": { - "95.0": 121872.37504835591, - "99.0": 351080.94111111073 - } - }, - "avg": { - "value": 34564.81091043125 - } - }, - { - "key_as_string": "2018-06-07T18:00:00.000Z", - "key": 1528394400000, - "doc_count": 18917, - "pct": { - "values": { - "95.0": 116378.03873517792, - "99.0": 339294.12799999997 - } - }, - "avg": { - "value": 33256.37743828302 - } - }, - { - "key_as_string": "2018-06-07T21:00:00.000Z", - "key": 1528405200000, - "doc_count": 18744, - "pct": { - "values": { - "95.0": 131545.40999999995, - "99.0": 378902.90649999987 - } - }, - "avg": { - "value": 37251.5625266752 - } - }, - { - "key_as_string": "2018-06-08T00:00:00.000Z", - "key": 1528416000000, - "doc_count": 19157, - "pct": { - "values": { - "95.0": 133111.25804878055, - "99.0": 384483.3233333327 - } - }, - "avg": { - "value": 38681.89084929791 - } - }, - { - "key_as_string": "2018-06-08T03:00:00.000Z", - "key": 1528426800000, - "doc_count": 18552, - "pct": { - "values": { - "95.0": 144821.9855278593, - "99.0": 394692.25000000105 - } - }, - "avg": { - "value": 40677.801045709355 - } - }, - { - "key_as_string": "2018-06-08T06:00:00.000Z", - "key": 1528437600000, - "doc_count": 18994, - "pct": { - "values": { - "95.0": 134737.3997727272, - "99.0": 403362.50399999996 - } - }, - "avg": { - "value": 39987.86453616932 - } - }, - { - "key_as_string": "2018-06-08T09:00:00.000Z", - "key": 1528448400000, - "doc_count": 18798, - "pct": { - "values": { - "95.0": 141206.57726666646, - "99.0": 396559.0274999993 - } - }, - "avg": { - "value": 41059.392914139804 - } - }, - { - "key_as_string": "2018-06-08T12:00:00.000Z", - "key": 1528459200000, - "doc_count": 19097, - "pct": { - "values": { - "95.0": 137731.8994082841, - "99.0": 371815.8320000008 - } - }, - "avg": { - "value": 39630.710111535845 - } - }, - { - "key_as_string": "2018-06-08T15:00:00.000Z", - "key": 1528470000000, - "doc_count": 18887, - "pct": { - "values": { - "95.0": 141476.23189033198, - "99.0": 405477.6133333326 - } - }, - "avg": { - "value": 41561.81331074284 - } - }, - { - "key_as_string": "2018-06-08T18:00:00.000Z", - "key": 1528480800000, - "doc_count": 18949, - "pct": { - "values": { - "95.0": 149636.31340909077, - "99.0": 413542.18133333366 - } - }, - "avg": { - "value": 43079.490738297536 - } - }, - { - "key_as_string": "2018-06-08T21:00:00.000Z", - "key": 1528491600000, - "doc_count": 18786, - "pct": { - "values": { - "95.0": 151934.55000000002, - "99.0": 424399.340000001 - } - }, - "avg": { - "value": 43925.39609283509 - } - }, - { - "key_as_string": "2018-06-09T00:00:00.000Z", - "key": 1528502400000, - "doc_count": 5096, - "pct": { - "values": { - "95.0": 82198.17857142858, - "99.0": 303815.9000000001 - } - }, - "avg": { - "value": 25821.91424646782 - } - }, - { - "key_as_string": "2018-06-09T03:00:00.000Z", - "key": 1528513200000, - "doc_count": 5104, - "pct": { - "values": { - "95.0": 85946.43199999983, - "99.0": 306305.0800000006 - } - }, - "avg": { - "value": 27343.60011755486 - } - }, - { - "key_as_string": "2018-06-09T06:00:00.000Z", - "key": 1528524000000, - "doc_count": 5122, - "pct": { - "values": { - "95.0": 78617.66249999996, - "99.0": 297521.94999999984 - } - }, - "avg": { - "value": 25249.95060523233 - } - }, - { - "key_as_string": "2018-06-09T09:00:00.000Z", - "key": 1528534800000, - "doc_count": 5184, - "pct": { - "values": { - "95.0": 79606.48333333322, - "99.0": 317938.0900000003 - } - }, - "avg": { - "value": 25492.77199074074 - } - }, - { - "key_as_string": "2018-06-09T12:00:00.000Z", - "key": 1528545600000, - "doc_count": 5279, - "pct": { - "values": { - "95.0": 76297.93999999986, - "99.0": 312262.3000000003 - } - }, - "avg": { - "value": 25991.647281682137 - } - }, - { - "key_as_string": "2018-06-09T15:00:00.000Z", - "key": 1528556400000, - "doc_count": 5254, - "pct": { - "values": { - "95.0": 80742.63333333324, - "99.0": 318428.8700000002 - } - }, - "avg": { - "value": 26273.31290445375 - } - }, - { - "key_as_string": "2018-06-09T18:00:00.000Z", - "key": 1528567200000, - "doc_count": 5082, - "pct": { - "values": { - "95.0": 81291.45969696966, - "99.0": 295421.4099999999 - } - }, - "avg": { - "value": 26234.98976780795 - } - }, - { - "key_as_string": "2018-06-09T21:00:00.000Z", - "key": 1528578000000, - "doc_count": 5150, - "pct": { - "values": { - "95.0": 73467.02500000004, - "99.0": 293067.86000000004 - } - }, - "avg": { - "value": 23494.54873786408 - } - }, - { - "key_as_string": "2018-06-10T00:00:00.000Z", - "key": 1528588800000, - "doc_count": 5103, - "pct": { - "values": { - "95.0": 69177.66999999993, - "99.0": 264935.71999999933 - } - }, - "avg": { - "value": 22008.80482069371 - } - }, - { - "key_as_string": "2018-06-10T03:00:00.000Z", - "key": 1528599600000, - "doc_count": 5137, - "pct": { - "values": { - "95.0": 71956.06111111109, - "99.0": 282795.0400000003 - } - }, - "avg": { - "value": 22828.136655635586 - } - }, - { - "key_as_string": "2018-06-10T06:00:00.000Z", - "key": 1528610400000, - "doc_count": 5184, - "pct": { - "values": { - "95.0": 68480.91142857139, - "99.0": 285390.8400000001 - } - }, - "avg": { - "value": 22138.7081404321 - } - }, - { - "key_as_string": "2018-06-10T09:00:00.000Z", - "key": 1528621200000, - "doc_count": 4993, - "pct": { - "values": { - "95.0": 68957.0999999999, - "99.0": 290402.24 - } - }, - "avg": { - "value": 22634.985579811735 - } - }, - { - "key_as_string": "2018-06-10T12:00:00.000Z", - "key": 1528632000000, - "doc_count": 5210, - "pct": { - "values": { - "95.0": 67489.50416666668, - "99.0": 293655.53 - } - }, - "avg": { - "value": 22202.780998080616 - } - }, - { - "key_as_string": "2018-06-10T15:00:00.000Z", - "key": 1528642800000, - "doc_count": 5122, - "pct": { - "values": { - "95.0": 71556.91249999998, - "99.0": 292723.56999999995 - } - }, - "avg": { - "value": 23084.082780163997 - } - }, - { - "key_as_string": "2018-06-10T18:00:00.000Z", - "key": 1528653600000, - "doc_count": 5125, - "pct": { - "values": { - "95.0": 72157.65128205132, - "99.0": 301051.32000000105 - } - }, - "avg": { - "value": 23109.666146341464 - } - }, - { - "key_as_string": "2018-06-10T21:00:00.000Z", - "key": 1528664400000, - "doc_count": 5186, - "pct": { - "values": { - "95.0": 76124.5625, - "99.0": 291322.0499999998 - } - }, - "avg": { - "value": 23306.89028152719 - } - }, - { - "key_as_string": "2018-06-11T00:00:00.000Z", - "key": 1528675200000, - "doc_count": 18631, - "pct": { - "values": { - "95.0": 141709.34661835746, - "99.0": 379855.2444444447 - } - }, - "avg": { - "value": 39341.022704095325 - } - }, - { - "key_as_string": "2018-06-11T03:00:00.000Z", - "key": 1528686000000, - "doc_count": 19349, - "pct": { - "values": { - "95.0": 132371.48641975303, - "99.0": 371175.2592000001 - } - }, - "avg": { - "value": 37467.17153341258 - } - }, - { - "key_as_string": "2018-06-11T06:00:00.000Z", - "key": 1528696800000, - "doc_count": 18586, - "pct": { - "values": { - "95.0": 186783.51503759398, - "99.0": 498378.4238888898 - } - }, - "avg": { - "value": 52457.50554180566 - } - }, - { - "key_as_string": "2018-06-11T09:00:00.000Z", - "key": 1528707600000, - "doc_count": 18887, - "pct": { - "values": { - "95.0": 99540.17819499348, - "99.0": 331118.6599999997 - } - }, - "avg": { - "value": 31327.95780166252 - } - }, - { - "key_as_string": "2018-06-11T12:00:00.000Z", - "key": 1528718400000, - "doc_count": 18866, - "pct": { - "values": { - "95.0": 95982.62454212455, - "99.0": 328101.3999999988 - } - }, - "avg": { - "value": 30695.334941163997 - } - }, - { - "key_as_string": "2018-06-11T15:00:00.000Z", - "key": 1528729200000, - "doc_count": 19469, - "pct": { - "values": { - "95.0": 89559.3525925925, - "99.0": 313951.54249999986 - } - }, - "avg": { - "value": 28895.042785967435 - } - }, - { - "key_as_string": "2018-06-11T18:00:00.000Z", - "key": 1528740000000, - "doc_count": 18767, - "pct": { - "values": { - "95.0": 95769.83153735634, - "99.0": 323340.5274074075 - } - }, - "avg": { - "value": 30649.363989982416 - } - }, - { - "key_as_string": "2018-06-11T21:00:00.000Z", - "key": 1528750800000, - "doc_count": 19006, - "pct": { - "values": { - "95.0": 94063.90833755062, - "99.0": 315055.5047619052 - } - }, - "avg": { - "value": 29802.63622014101 - } - }, - { - "key_as_string": "2018-06-12T00:00:00.000Z", - "key": 1528761600000, - "doc_count": 19082, - "pct": { - "values": { - "95.0": 96399.67269119772, - "99.0": 330070.03599999985 - } - }, - "avg": { - "value": 30759.03002829892 - } - }, - { - "key_as_string": "2018-06-12T03:00:00.000Z", - "key": 1528772400000, - "doc_count": 18908, - "pct": { - "values": { - "95.0": 96436.42520161276, - "99.0": 320531.54416666675 - } - }, - "avg": { - "value": 30399.76549608631 - } - }, - { - "key_as_string": "2018-06-12T06:00:00.000Z", - "key": 1528783200000, - "doc_count": 19055, - "pct": { - "values": { - "95.0": 91860.16988095238, - "99.0": 315137.16628571344 - } - }, - "avg": { - "value": 29421.610233534506 - } - }, - { - "key_as_string": "2018-06-12T09:00:00.000Z", - "key": 1528794000000, - "doc_count": 19047, - "pct": { - "values": { - "95.0": 105989.8333333334, - "99.0": 337251.4042424246 - } - }, - "avg": { - "value": 32641.679897096656 - } - }, - { - "key_as_string": "2018-06-12T12:00:00.000Z", - "key": 1528804800000, - "doc_count": 18733, - "pct": { - "values": { - "95.0": 97937.60342555979, - "99.0": 327054.9243636365 - } - }, - "avg": { - "value": 30621.65440666204 - } - }, - { - "key_as_string": "2018-06-12T15:00:00.000Z", - "key": 1528815600000, - "doc_count": 19079, - "pct": { - "values": { - "95.0": 98967.2249999999, - "99.0": 327653.0000000006 - } - }, - "avg": { - "value": 31039.60391005818 - } - }, - { - "key_as_string": "2018-06-12T18:00:00.000Z", - "key": 1528826400000, - "doc_count": 18907, - "pct": { - "values": { - "95.0": 97561.02469135808, - "99.0": 324505.1399999999 - } - }, - "avg": { - "value": 30954.760723541545 - } - }, - { - "key_as_string": "2018-06-12T21:00:00.000Z", - "key": 1528837200000, - "doc_count": 18971, - "pct": { - "values": { - "95.0": 102557.78813357186, - "99.0": 338040.3999999998 - } - }, - "avg": { - "value": 31902.050234568553 - } - }, - { - "key_as_string": "2018-06-13T00:00:00.000Z", - "key": 1528848000000, - "doc_count": 18899, - "pct": { - "values": { - "95.0": 100137.87578595306, - "99.0": 328600.5173333335 - } - }, - "avg": { - "value": 31594.350653473728 - } - }, - { - "key_as_string": "2018-06-13T03:00:00.000Z", - "key": 1528858800000, - "doc_count": 19182, - "pct": { - "values": { - "95.0": 98412.97120445351, - "99.0": 334060.93628571345 - } - }, - "avg": { - "value": 31343.87243248879 - } - }, - { - "key_as_string": "2018-06-13T06:00:00.000Z", - "key": 1528869600000, - "doc_count": 19030, - "pct": { - "values": { - "95.0": 101607.8328012912, - "99.0": 328569.4964999998 - } - }, - "avg": { - "value": 31200.14450867052 - } - }, - { - "key_as_string": "2018-06-13T09:00:00.000Z", - "key": 1528880400000, - "doc_count": 19257, - "pct": { - "values": { - "95.0": 92000.51368421057, - "99.0": 320227.32399999973 - } - }, - "avg": { - "value": 28560.946668743833 - } - }, - { - "key_as_string": "2018-06-13T12:00:00.000Z", - "key": 1528891200000, - "doc_count": 19348, - "pct": { - "values": { - "95.0": 78027.29473684198, - "99.0": 292019.2899999998 - } - }, - "avg": { - "value": 24700.216146371717 - } - }, - { - "key_as_string": "2018-06-13T15:00:00.000Z", - "key": 1528902000000, - "doc_count": 19119, - "pct": { - "values": { - "95.0": 80762.078801789, - "99.0": 297757.72666666657 - } - }, - "avg": { - "value": 25261.025210523563 - } - }, - { - "key_as_string": "2018-06-13T18:00:00.000Z", - "key": 1528912800000, - "doc_count": 19206, - "pct": { - "values": { - "95.0": 81160.83425925927, - "99.0": 308034.4466666669 - } - }, - "avg": { - "value": 26041.39789649068 - } - }, - { - "key_as_string": "2018-06-13T21:00:00.000Z", - "key": 1528923600000, - "doc_count": 19078, - "pct": { - "values": { - "95.0": 84215.58945578222, - "99.0": 301128.4895238093 - } - }, - "avg": { - "value": 26123.556295209142 - } - }, - { - "key_as_string": "2018-06-14T00:00:00.000Z", - "key": 1528934400000, - "doc_count": 19551, - "pct": { - "values": { - "95.0": 194188.21428571426, - "99.0": 447266.9 - } - }, - "avg": { - "value": 46231.36177177638 - } - }, - { - "key_as_string": "2018-06-14T03:00:00.000Z", - "key": 1528945200000, - "doc_count": 18888, - "pct": { - "values": { - "95.0": 172616.2293896504, - "99.0": 409147.332500001 - } - }, - "avg": { - "value": 45350.42005506141 - } - }, - { - "key_as_string": "2018-06-14T06:00:00.000Z", - "key": 1528956000000, - "doc_count": 18823, - "pct": { - "values": { - "95.0": 182653.81858220184, - "99.0": 423121.9773333328 - } - }, - "avg": { - "value": 48256.049354513096 - } - }, - { - "key_as_string": "2018-06-14T09:00:00.000Z", - "key": 1528966800000, - "doc_count": 18766, - "pct": { - "values": { - "95.0": 194970.75667682925, - "99.0": 473485.4199999998 - } - }, - "avg": { - "value": 52360.30017052116 - } - }, - { - "key_as_string": "2018-06-14T12:00:00.000Z", - "key": 1528977600000, - "doc_count": 0, - "pct": { - "values": { - "95.0": "NaN", - "99.0": "NaN" - } - }, - "avg": { - "value": null - } - } - ] - }, - "overall_avg_duration": { - "value": 32861.15660262639 - } - } -} diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/__snapshots__/get_buckets_with_initial_anomaly_bounds.test.ts.snap b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/__snapshots__/get_buckets_with_initial_anomaly_bounds.test.ts.snap new file mode 100644 index 0000000000000..072a819e75398 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/__snapshots__/get_buckets_with_initial_anomaly_bounds.test.ts.snap @@ -0,0 +1,46 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`get_buckets_with_initial_anomaly_bounds should return correct buckets 1`] = ` +Array [ + Object { + "anomalyScore": 0, + "lower": 17688.182675688193, + "upper": 50381.01051622894, + }, + Object { + "anomalyScore": null, + "lower": null, + "upper": null, + }, + Object { + "anomalyScore": null, + "lower": null, + "upper": null, + }, + Object { + "anomalyScore": 0, + "lower": 16034.081569306454, + "upper": 54158.77731018045, + }, + Object { + "anomalyScore": null, + "lower": null, + "upper": null, + }, + Object { + "anomalyScore": 0, + "lower": 16034.081569306454, + "upper": 54158.77731018045, + }, + Object { + "anomalyScore": null, + "lower": null, + "upper": null, + }, + Object { + "anomalyScore": 0, + "lower": 16034.081569306454, + "upper": 54158.77731018045, + }, +] +`; diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/__test__/__snapshots__/get_buckets_with_initial_anomaly_bounds.test.js.snap b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/__test__/__snapshots__/get_buckets_with_initial_anomaly_bounds.test.js.snap deleted file mode 100644 index 6809efff957af..0000000000000 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/__test__/__snapshots__/get_buckets_with_initial_anomaly_bounds.test.js.snap +++ /dev/null @@ -1,118 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`get_buckets_with_initial_anomaly_bounds should return correct buckets 1`] = ` -Array [ - Object { - "anomaly_score": Object { - "value": null, - }, - "doc_count": 0, - "key": 1530523000000, - "key_as_string": "2018-07-02T09:16:40.000Z", - "lower": Object { - "value": 17688.182675688193, - }, - "upper": Object { - "value": 50381.01051622894, - }, - }, - Object { - "anomaly_score": Object { - "value": null, - }, - "doc_count": 4, - "key": 1530523500000, - "key_as_string": "2018-07-02T09:25:00.000Z", - "lower": Object { - "value": null, - }, - "upper": Object { - "value": null, - }, - }, - Object { - "anomaly_score": Object { - "value": null, - }, - "doc_count": 0, - "key": 1530524000000, - "key_as_string": "2018-07-02T09:33:20.000Z", - "lower": Object { - "value": null, - }, - "upper": Object { - "value": null, - }, - }, - Object { - "anomaly_score": Object { - "value": 0, - }, - "doc_count": 2, - "key": 1530524500000, - "key_as_string": "2018-07-02T09:41:40.000Z", - "lower": Object { - "value": 16034.081569306454, - }, - "upper": Object { - "value": 54158.77731018045, - }, - }, - Object { - "anomaly_score": Object { - "value": null, - }, - "doc_count": 0, - "key": 1530525000000, - "key_as_string": "2018-07-02T09:50:00.000Z", - "lower": Object { - "value": null, - }, - "upper": Object { - "value": null, - }, - }, - Object { - "anomaly_score": Object { - "value": 0, - }, - "doc_count": 2, - "key": 1530525500000, - "key_as_string": "2018-07-02T09:58:20.000Z", - "lower": Object { - "value": 16034.081569306454, - }, - "upper": Object { - "value": 54158.77731018045, - }, - }, - Object { - "anomaly_score": Object { - "value": null, - }, - "doc_count": 0, - "key": 1530526000000, - "key_as_string": "2018-07-02T10:06:40.000Z", - "lower": Object { - "value": null, - }, - "upper": Object { - "value": null, - }, - }, - Object { - "anomaly_score": Object { - "value": 0, - }, - "doc_count": 2, - "key": 1530526500000, - "key_as_string": "2018-07-02T10:15:00.000Z", - "lower": Object { - "value": 16034.081569306454, - }, - "upper": Object { - "value": 54158.77731018045, - }, - }, -] -`; diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/__test__/mockData/firstBucketsResponse.json b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/__test__/mockData/firstBucketsResponse.json deleted file mode 100644 index 08851e34c86f2..0000000000000 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/__test__/mockData/firstBucketsResponse.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "took": 22, - "timed_out": false, - "_shards": { - "total": 5, - "successful": 5, - "skipped": 0, - "failed": 0 - }, - "hits": { - "total": 2, - "max_score": 0, - "hits": [] - }, - "aggregations": { - "ml_avg_response_times": { - "buckets": [ - { - "key_as_string": "2018-07-02T09:00:00.000Z", - "key": 1530522000000, - "doc_count": 0, - "anomaly_score": { - "value": null - }, - "upper": { - "value": null - }, - "lower": { - "value": null - } - }, - { - "key_as_string": "2018-07-02T09:08:20.000Z", - "key": 1530522500000, - "doc_count": 2, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 50381.01051622894 - }, - "lower": { - "value": 17688.182675688193 - } - }, - { - "key_as_string": "2018-07-02T09:16:40.000Z", - "key": 1530523000000, - "doc_count": 0, - "anomaly_score": { - "value": null - }, - "upper": { - "value": null - }, - "lower": { - "value": null - } - } - ] - }, - "top_hits": { - "hits": { - "total": 2, - "max_score": null, - "hits": [ - { - "_index": ".ml-anomalies-shared", - "_type": "doc", - "_id": "opbeans-node-request-high_mean_response_time_model_plot_1530522900000_900_0_29791_0", - "_score": null, - "_source": { - "bucket_span": 900 - }, - "sort": [ - 900 - ] - } - ] - } - } - } -} diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/__test__/mockData/mainBucketsResponse.json b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/__test__/mockData/mainBucketsResponse.json deleted file mode 100644 index 4c32ff9108ed1..0000000000000 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/__test__/mockData/mainBucketsResponse.json +++ /dev/null @@ -1,152 +0,0 @@ -{ - "took": 3, - "timed_out": false, - "_shards": { - "total": 5, - "successful": 5, - "skipped": 0, - "failed": 0 - }, - "hits": { - "total": 10, - "max_score": 0, - "hits": [] - }, - "aggregations": { - "ml_avg_response_times": { - "buckets": [ - { - "key_as_string": "2018-07-02T09:16:40.000Z", - "key": 1530523000000, - "doc_count": 0, - "anomaly_score": { - "value": null - }, - "upper": { - "value": null - }, - "lower": { - "value": null - } - }, - { - "key_as_string": "2018-07-02T09:25:00.000Z", - "key": 1530523500000, - "doc_count": 4, - "anomaly_score": { - "value": null - }, - "upper": { - "value": null - }, - "lower": { - "value": null - } - }, - { - "key_as_string": "2018-07-02T09:33:20.000Z", - "key": 1530524000000, - "doc_count": 0, - "anomaly_score": { - "value": null - }, - "upper": { - "value": null - }, - "lower": { - "value": null - } - }, - { - "key_as_string": "2018-07-02T09:41:40.000Z", - "key": 1530524500000, - "doc_count": 2, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 54158.77731018045 - }, - "lower": { - "value": 16034.081569306454 - } - }, - { - "key_as_string": "2018-07-02T09:50:00.000Z", - "key": 1530525000000, - "doc_count": 0, - "anomaly_score": { - "value": null - }, - "upper": { - "value": null - }, - "lower": { - "value": null - } - }, - { - "key_as_string": "2018-07-02T09:58:20.000Z", - "key": 1530525500000, - "doc_count": 2, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 54158.77731018045 - }, - "lower": { - "value": 16034.081569306454 - } - }, - { - "key_as_string": "2018-07-02T10:06:40.000Z", - "key": 1530526000000, - "doc_count": 0, - "anomaly_score": { - "value": null - }, - "upper": { - "value": null - }, - "lower": { - "value": null - } - }, - { - "key_as_string": "2018-07-02T10:15:00.000Z", - "key": 1530526500000, - "doc_count": 2, - "anomaly_score": { - "value": 0 - }, - "upper": { - "value": 54158.77731018045 - }, - "lower": { - "value": 16034.081569306454 - } - } - ] - }, - "top_hits": { - "hits": { - "total": 2, - "max_score": null, - "hits": [ - { - "_index": ".ml-anomalies-shared", - "_type": "doc", - "_id": - "opbeans-node-request-high_mean_response_time_model_plot_1530522900000_900_0_29791_0", - "_score": null, - "_source": { - "bucket_span": 900 - }, - "sort": [900] - } - ] - } - } - } -} diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_anomaly_aggs/__snapshots__/fetcher.test.ts.snap b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_anomaly_aggs/__snapshots__/fetcher.test.ts.snap new file mode 100644 index 0000000000000..f3436641f3f1a --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_anomaly_aggs/__snapshots__/fetcher.test.ts.snap @@ -0,0 +1,73 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`anomalyAggsFetcher when ES returns valid response should call client with correct query 1`] = ` +Array [ + Array [ + "search", + Object { + "body": Object { + "aggs": Object { + "ml_avg_response_times": Object { + "aggs": Object { + "anomaly_score": Object { + "max": Object { + "field": "anomaly_score", + }, + }, + "lower": Object { + "min": Object { + "field": "model_lower", + }, + }, + "upper": Object { + "max": Object { + "field": "model_upper", + }, + }, + }, + "date_histogram": Object { + "extended_bounds": Object { + "max": 1, + "min": 0, + }, + "field": "timestamp", + "interval": "myInterval", + "min_doc_count": 0, + }, + }, + "top_hits": Object { + "top_hits": Object { + "_source": Object { + "includes": Array [ + "bucket_span", + ], + }, + "size": 1, + "sort": Array [ + "bucket_span", + ], + }, + }, + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "range": Object { + "timestamp": Object { + "format": "epoch_millis", + "gte": 0, + "lte": 1, + }, + }, + }, + ], + }, + }, + "size": 0, + }, + "index": ".ml-anomalies-myservicename-mytransactiontype-high_mean_response_time", + }, + ], +] +`; diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_anomaly_aggs/__snapshots__/transform.test.ts.snap b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_anomaly_aggs/__snapshots__/transform.test.ts.snap new file mode 100644 index 0000000000000..1eeac5ae02e5e --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_anomaly_aggs/__snapshots__/transform.test.ts.snap @@ -0,0 +1,49 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`anomalyAggsTransform should match snapshot 1`] = ` +Object { + "bucketSize": 900, + "buckets": Array [ + Object { + "anomalyScore": null, + "lower": null, + "upper": null, + }, + Object { + "anomalyScore": null, + "lower": null, + "upper": null, + }, + Object { + "anomalyScore": null, + "lower": null, + "upper": null, + }, + Object { + "anomalyScore": 0, + "lower": 16034.081569306454, + "upper": 54158.77731018045, + }, + Object { + "anomalyScore": null, + "lower": null, + "upper": null, + }, + Object { + "anomalyScore": 0, + "lower": 16034.081569306454, + "upper": 54158.77731018045, + }, + Object { + "anomalyScore": null, + "lower": null, + "upper": null, + }, + Object { + "anomalyScore": 0, + "lower": 16034.081569306454, + "upper": 54158.77731018045, + }, + ], +} +`; diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_anomaly_aggs/fetcher.test.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_anomaly_aggs/fetcher.test.ts new file mode 100644 index 0000000000000..7eaf84be12e79 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_anomaly_aggs/fetcher.test.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { anomalyAggsFetcher, ESResponse } from './fetcher'; + +describe('anomalyAggsFetcher', () => { + describe('when ES returns valid response', () => { + let response: ESResponse; + let clientSpy: jest.Mock; + + beforeEach(async () => { + clientSpy = jest.fn().mockReturnValue('ES Response'); + response = await anomalyAggsFetcher({ + serviceName: 'myServiceName', + transactionType: 'myTransactionType', + intervalString: 'myInterval', + client: clientSpy, + start: 0, + end: 1 + }); + }); + + it('should call client with correct query', () => { + expect(clientSpy.mock.calls).toMatchSnapshot(); + }); + + it('should return correct response', () => { + expect(response).toBe('ES Response'); + }); + }); + + it('should swallow HTTP errors', () => { + const httpError = new Error('anomaly lookup failed') as any; + httpError.statusCode = 418; + const failClient = jest.fn(() => Promise.reject(httpError)); + + return expect( + anomalyAggsFetcher({ client: failClient } as any) + ).resolves.toEqual(null); + }); + + it('should throw other errors', () => { + const otherError = new Error('anomaly lookup ASPLODED') as any; + const failClient = jest.fn(() => Promise.reject(otherError)); + + return expect( + anomalyAggsFetcher({ + client: failClient + } as any) + ).rejects.toThrow(otherError); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_anomaly_aggs.js b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_anomaly_aggs/fetcher.ts similarity index 59% rename from x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_anomaly_aggs.js rename to x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_anomaly_aggs/fetcher.ts index 86fb84fd88871..eeb1fc32b8f68 100644 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_anomaly_aggs.js +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_anomaly_aggs/fetcher.ts @@ -4,14 +4,53 @@ * you may not use this file except in compliance with the Elastic License. */ -export async function getAnomalyAggs({ +import { AggregationSearchResponse } from 'elasticsearch'; +import { TopHits } from 'x-pack/plugins/apm/typings/elasticsearch'; +import { ESClient } from '../../../../helpers/setup_request'; + +export interface IOptions { + serviceName: string; + transactionType: string; + intervalString: string; + client: ESClient; + start: number; + end: number; +} + +interface Bucket { + key_as_string: string; + key: number; + doc_count: number; + anomaly_score: { + value: number | null; + }; + lower: { + value: number | null; + }; + upper: { + value: number | null; + }; +} + +interface Aggs { + ml_avg_response_times: { + buckets: Bucket[]; + }; + top_hits: TopHits<{ + bucket_span: number; + }>; +} + +export type ESResponse = AggregationSearchResponse | null; + +export async function anomalyAggsFetcher({ serviceName, transactionType, intervalString, client, start, end -}) { +}: IOptions): Promise { const params = { index: `.ml-anomalies-${serviceName}-${transactionType}-high_mean_response_time`.toLowerCase(), body: { @@ -60,12 +99,12 @@ export async function getAnomalyAggs({ }; try { - const resp = await client('search', params); - return resp.aggregations; - } catch (e) { - if (e.statusCode === 404) { + return await client('search', params); + } catch (err) { + const isHttpError = 'statusCode' in err; + if (isHttpError) { return null; } - throw e; + throw err; } } diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_anomaly_aggs/index.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_anomaly_aggs/index.ts new file mode 100644 index 0000000000000..c3df288527442 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_anomaly_aggs/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { anomalyAggsFetcher, IOptions } from './fetcher'; +import { anomalyAggsTransform } from './transform'; + +export async function getAnomalyAggs(options: IOptions) { + const response = await anomalyAggsFetcher(options); + return anomalyAggsTransform(response); +} diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_anomaly_aggs/transform.test.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_anomaly_aggs/transform.test.ts new file mode 100644 index 0000000000000..1b1dbf8848cfc --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_anomaly_aggs/transform.test.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { mainBucketsResponse } from '../mock-responses/mainBucketsResponse'; +import { anomalyAggsTransform } from './transform'; + +describe('anomalyAggsTransform', () => { + it('should return null if response is empty', () => { + expect(anomalyAggsTransform(null)).toBe(null); + }); + + it('should match snapshot', () => { + expect(anomalyAggsTransform(mainBucketsResponse)).toMatchSnapshot(); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_anomaly_aggs/transform.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_anomaly_aggs/transform.ts new file mode 100644 index 0000000000000..d6ebb3ba7a3a7 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_anomaly_aggs/transform.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { oc } from 'ts-optchain'; +import { ESResponse } from './fetcher'; + +export interface AvgAnomalyBucket { + anomalyScore: number | null; + lower: number | null; + upper: number | null; +} + +export function anomalyAggsTransform(response: ESResponse) { + if (!response) { + return null; + } + + const buckets = oc(response) + .aggregations.ml_avg_response_times.buckets([]) + .map(bucket => { + return { + anomalyScore: bucket.anomaly_score.value, + lower: bucket.lower.value, + upper: bucket.upper.value + }; + }); + + return { + buckets, + bucketSize: oc( + response + ).aggregations.top_hits.hits.hits[0]._source.bucket_span(0) + }; +} diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_buckets_with_initial_anomaly_bounds.js b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_buckets_with_initial_anomaly_bounds.js deleted file mode 100644 index 5d71559232445..0000000000000 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_buckets_with_initial_anomaly_bounds.js +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { last, get } from 'lodash'; -import { getAnomalyAggs } from './get_anomaly_aggs'; - -export async function getBucketWithInitialAnomalyBounds({ - serviceName, - transactionType, - client, - start, - mainBuckets, - anomalyBucketSpan -}) { - // abort if first bucket already has values for initial anomaly bounds - if (mainBuckets[0].lower.value || !anomalyBucketSpan) { - return mainBuckets; - } - - const newStart = start - anomalyBucketSpan * 1000; - const newEnd = start; - - const aggs = await getAnomalyAggs({ - serviceName, - transactionType, - intervalString: `${anomalyBucketSpan}s`, - client, - start: newStart, - end: newEnd - }); - - const firstBucketWithBounds = last( - get(aggs, 'ml_avg_response_times.buckets', []).filter( - bucket => bucket.lower.value - ) - ); - - return mainBuckets.map((bucket, i) => { - // replace first item - if (i === 0 && firstBucketWithBounds) { - return { - ...bucket, - upper: { value: firstBucketWithBounds.upper.value }, - lower: { value: firstBucketWithBounds.lower.value } - }; - } - return bucket; - }); -} diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/__test__/get_buckets_with_initial_anomaly_bounds.test.js b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_buckets_with_initial_anomaly_bounds.test.ts similarity index 55% rename from x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/__test__/get_buckets_with_initial_anomaly_bounds.test.js rename to x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_buckets_with_initial_anomaly_bounds.test.ts index e8b37ee490642..f1fb18e2a035a 100644 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/__test__/get_buckets_with_initial_anomaly_bounds.test.js +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_buckets_with_initial_anomaly_bounds.test.ts @@ -4,24 +4,34 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getBucketWithInitialAnomalyBounds } from '../get_buckets_with_initial_anomaly_bounds'; -import mainBucketsResponse from './mockData/mainBucketsResponse'; -import firstBucketsResponse from './mockData/firstBucketsResponse'; +import { getAnomalyAggs } from './get_anomaly_aggs'; +import { AvgAnomalyBucket } from './get_anomaly_aggs/transform'; +import { getBucketWithInitialAnomalyBounds } from './get_buckets_with_initial_anomaly_bounds'; +import { firstBucketsResponse } from './mock-responses/firstBucketsResponse'; +import { mainBucketsResponse } from './mock-responses/mainBucketsResponse'; describe('get_buckets_with_initial_anomaly_bounds', () => { - const mainBuckets = - mainBucketsResponse.aggregations.ml_avg_response_times.buckets; - let buckets; + let buckets: AvgAnomalyBucket[]; + let mainBuckets: AvgAnomalyBucket[]; beforeEach(async () => { + const response = await getAnomalyAggs({ + serviceName: 'myServiceName', + transactionType: 'myTransactionType', + intervalString: '', + client: () => mainBucketsResponse as any, + start: 0, + end: 1 + }); + + mainBuckets = response!.buckets; buckets = await getBucketWithInitialAnomalyBounds({ serviceName: 'myServiceName', transactionType: 'myTransactionType', - intervalString: '60s', start: 1530523322742, - client: () => firstBucketsResponse, - mainBuckets, - anomalyBucketSpan: 900 + client: () => firstBucketsResponse as any, + buckets: mainBuckets, + bucketSize: 900 }); }); diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_buckets_with_initial_anomaly_bounds.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_buckets_with_initial_anomaly_bounds.ts new file mode 100644 index 0000000000000..a56b656671672 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_buckets_with_initial_anomaly_bounds.ts @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { last } from 'lodash'; +import { ESClient } from '../../../helpers/setup_request'; +import { getAnomalyAggs } from './get_anomaly_aggs'; +import { AvgAnomalyBucket } from './get_anomaly_aggs/transform'; + +interface Props { + serviceName: string; + transactionType: string; + buckets: AvgAnomalyBucket[]; + bucketSize: number; + start: number; + client: ESClient; +} + +export async function getBucketWithInitialAnomalyBounds({ + serviceName, + transactionType, + buckets, + bucketSize, + start, + client +}: Props) { + // abort if first bucket already has values for initial anomaly bounds + if (buckets[0].lower || !bucketSize) { + return buckets; + } + + const newStart = start - bucketSize * 1000; + const newEnd = start; + + const aggs = await getAnomalyAggs({ + serviceName, + transactionType, + intervalString: `${bucketSize}s`, + client, + start: newStart, + end: newEnd + }); + + if (!aggs) { + return buckets; + } + + const firstBucketWithBounds = last( + aggs.buckets.filter(bucket => bucket.lower) + ); + + if (!firstBucketWithBounds) { + return buckets; + } + + return replaceFirstItem(buckets, firstBucketWithBounds); +} + +// copy array and replace first item +function replaceFirstItem(array: T[], value: T) { + const ret = array.slice(0); + ret[0] = value; + return ret; +} diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/__test__/get_avg_response_time_anomalies.test.js b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/index.test.ts similarity index 66% rename from x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/__test__/get_avg_response_time_anomalies.test.js rename to x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/index.test.ts index c44c5ffe4fc80..468c2c7e3a074 100644 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/__test__/get_avg_response_time_anomalies.test.js +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/index.test.ts @@ -4,9 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getAvgResponseTimeAnomalies } from '../get_avg_response_time_anomalies'; -import mainBucketsResponse from './mockData/mainBucketsResponse'; -import firstBucketsResponse from './mockData/firstBucketsResponse'; +import { getAvgResponseTimeAnomalies } from '.'; +import { firstBucketsResponse } from './mock-responses/firstBucketsResponse'; +import { mainBucketsResponse } from './mock-responses/mainBucketsResponse'; describe('get_avg_response_time_anomalies', () => { it('', async () => { @@ -23,32 +23,32 @@ describe('get_avg_response_time_anomalies', () => { end: 1528977600000, client: clientSpy, config: { - get: () => 'myIndex' + get: () => 'myIndex' as any } } }); expect(avgAnomalies).toEqual({ - bucketSpanAsMillis: 10800000, + bucketSizeAsMillis: 10800000, buckets: [ { - anomaly_score: null, + anomalyScore: 0, lower: 17688.182675688193, upper: 50381.01051622894 }, - { anomaly_score: null, lower: null, upper: null }, + { anomalyScore: null, lower: null, upper: null }, { - anomaly_score: 0, + anomalyScore: 0, lower: 16034.081569306454, upper: 54158.77731018045 }, - { anomaly_score: null, lower: null, upper: null }, + { anomalyScore: null, lower: null, upper: null }, { - anomaly_score: 0, + anomalyScore: 0, lower: 16034.081569306454, upper: 54158.77731018045 }, - { anomaly_score: null, lower: null, upper: null } + { anomalyScore: null, lower: null, upper: null } ] }); }); diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_avg_response_time_anomalies.js b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/index.ts similarity index 54% rename from x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_avg_response_time_anomalies.js rename to x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/index.ts index c6d411eb91884..31ee4a9d1a6d7 100644 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/get_avg_response_time_anomalies.js +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/index.ts @@ -4,23 +4,31 @@ * you may not use this file except in compliance with the Elastic License. */ -import { get } from 'lodash'; import { getBucketSize } from '../../../helpers/get_bucket_size'; +import { IOptions } from '../get_timeseries_data'; import { getAnomalyAggs } from './get_anomaly_aggs'; +import { AvgAnomalyBucket } from './get_anomaly_aggs/transform'; import { getBucketWithInitialAnomalyBounds } from './get_buckets_with_initial_anomaly_bounds'; +export interface IAvgAnomalies { + bucketSizeAsMillis: number; + buckets: AvgAnomalyBucket[]; +} + +export type IAvgAnomaliesResponse = IAvgAnomalies | undefined; + export async function getAvgResponseTimeAnomalies({ serviceName, transactionType, transactionName, setup -}) { +}: IOptions): Promise { const { start, end, client } = setup; const { intervalString, bucketSize } = getBucketSize(start, end, 'auto'); // don't fetch anomalies for transaction details page if (transactionName) { - return []; + return; } const aggs = await getAnomalyAggs({ @@ -33,42 +41,20 @@ export async function getAvgResponseTimeAnomalies({ }); if (!aggs) { - return { - message: 'ml index does not exist' - }; + return; } - const anomalyBucketSpan = get( - aggs, - 'top_hits.hits.hits[0]._source.bucket_span' - ); - - const mainBuckets = get(aggs, 'ml_avg_response_times.buckets', []).slice( - 1, - -1 - ); - - const bucketsWithInitialAnomalyBounds = await getBucketWithInitialAnomalyBounds( - { - serviceName, - transactionType, - client, - start, - mainBuckets, - anomalyBucketSpan - } - ); - - const buckets = bucketsWithInitialAnomalyBounds.map(bucket => { - return { - anomaly_score: bucket.anomaly_score.value, - lower: bucket.lower.value, - upper: bucket.upper.value - }; + const buckets = await getBucketWithInitialAnomalyBounds({ + serviceName, + transactionType, + buckets: aggs.buckets.slice(1, -1), + bucketSize: aggs.bucketSize, + start, + client }); return { - bucketSpanAsMillis: Math.max(bucketSize, anomalyBucketSpan) * 1000, - buckets + buckets, + bucketSizeAsMillis: Math.max(bucketSize, aggs.bucketSize) * 1000 }; } diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/mock-responses/firstBucketsResponse.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/mock-responses/firstBucketsResponse.ts new file mode 100644 index 0000000000000..d23807764af85 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/mock-responses/firstBucketsResponse.ts @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ESResponse } from '../get_anomaly_aggs/fetcher'; + +export const firstBucketsResponse: ESResponse = { + took: 22, + timed_out: false, + _shards: { + total: 5, + successful: 5, + skipped: 0, + failed: 0 + }, + hits: { + total: 2, + max_score: 0, + hits: [] + }, + aggregations: { + ml_avg_response_times: { + buckets: [ + { + key_as_string: '2018-07-02T09:00:00.000Z', + key: 1530522000000, + doc_count: 0, + anomaly_score: { + value: null + }, + upper: { + value: null + }, + lower: { + value: null + } + }, + { + key_as_string: '2018-07-02T09:08:20.000Z', + key: 1530522500000, + doc_count: 2, + anomaly_score: { + value: 0 + }, + upper: { + value: 50381.01051622894 + }, + lower: { + value: 17688.182675688193 + } + }, + { + key_as_string: '2018-07-02T09:16:40.000Z', + key: 1530523000000, + doc_count: 0, + anomaly_score: { + value: null + }, + upper: { + value: null + }, + lower: { + value: null + } + } + ] + }, + top_hits: { + hits: { + total: 2, + max_score: 0, + hits: [ + { + _index: '.ml-anomalies-shared', + _type: 'doc', + _id: + 'opbeans-node-request-high_mean_response_time_model_plot_1530522900000_900_0_29791_0', + _score: 0, + _source: { + bucket_span: 900 + } + } + ] + } + } + } +}; diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/mock-responses/mainBucketsResponse.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/mock-responses/mainBucketsResponse.ts new file mode 100644 index 0000000000000..21983541af24b --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_avg_response_time_anomalies/mock-responses/mainBucketsResponse.ts @@ -0,0 +1,159 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ESResponse } from '../get_anomaly_aggs/fetcher'; + +export const mainBucketsResponse: ESResponse = { + took: 3, + timed_out: false, + _shards: { + total: 5, + successful: 5, + skipped: 0, + failed: 0 + }, + hits: { + total: 10, + max_score: 0, + hits: [] + }, + aggregations: { + ml_avg_response_times: { + buckets: [ + { + key_as_string: '2018-07-02T09:16:40.000Z', + key: 1530523000000, + doc_count: 0, + anomaly_score: { + value: null + }, + upper: { + value: null + }, + lower: { + value: null + } + }, + { + key_as_string: '2018-07-02T09:25:00.000Z', + key: 1530523500000, + doc_count: 4, + anomaly_score: { + value: null + }, + upper: { + value: null + }, + lower: { + value: null + } + }, + { + key_as_string: '2018-07-02T09:33:20.000Z', + key: 1530524000000, + doc_count: 0, + anomaly_score: { + value: null + }, + upper: { + value: null + }, + lower: { + value: null + } + }, + { + key_as_string: '2018-07-02T09:41:40.000Z', + key: 1530524500000, + doc_count: 2, + anomaly_score: { + value: 0 + }, + upper: { + value: 54158.77731018045 + }, + lower: { + value: 16034.081569306454 + } + }, + { + key_as_string: '2018-07-02T09:50:00.000Z', + key: 1530525000000, + doc_count: 0, + anomaly_score: { + value: null + }, + upper: { + value: null + }, + lower: { + value: null + } + }, + { + key_as_string: '2018-07-02T09:58:20.000Z', + key: 1530525500000, + doc_count: 2, + anomaly_score: { + value: 0 + }, + upper: { + value: 54158.77731018045 + }, + lower: { + value: 16034.081569306454 + } + }, + { + key_as_string: '2018-07-02T10:06:40.000Z', + key: 1530526000000, + doc_count: 0, + anomaly_score: { + value: null + }, + upper: { + value: null + }, + lower: { + value: null + } + }, + { + key_as_string: '2018-07-02T10:15:00.000Z', + key: 1530526500000, + doc_count: 2, + anomaly_score: { + value: 0 + }, + upper: { + value: 54158.77731018045 + }, + lower: { + value: 16034.081569306454 + } + } + ] + }, + top_hits: { + hits: { + total: 2, + max_score: 0, + hits: [ + { + _index: '.ml-anomalies-shared', + _type: 'doc', + _id: + 'opbeans-node-request-high_mean_response_time_model_plot_1530522900000_900_0_29791_0', + _score: 0, + _source: { + bucket_span: 900 + } + } + ] + } + } + } +}; diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/__snapshots__/fetcher.test.ts.snap b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/__snapshots__/fetcher.test.ts.snap new file mode 100644 index 0000000000000..6e55d560a8931 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/__snapshots__/fetcher.test.ts.snap @@ -0,0 +1,93 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`timeseriesFetcher should call client with correct query 1`] = ` +Array [ + Array [ + "search", + Object { + "body": Object { + "aggs": Object { + "overall_avg_duration": Object { + "avg": Object { + "field": "transaction.duration.us", + }, + }, + "response_times": Object { + "aggs": Object { + "avg": Object { + "avg": Object { + "field": "transaction.duration.us", + }, + }, + "pct": Object { + "percentiles": Object { + "field": "transaction.duration.us", + "percents": Array [ + 95, + 99, + ], + }, + }, + }, + "date_histogram": Object { + "extended_bounds": Object { + "max": 1528977600000, + "min": 1528113600000, + }, + "field": "@timestamp", + "interval": "10800s", + "min_doc_count": 0, + }, + }, + "transaction_results": Object { + "aggs": Object { + "timeseries": Object { + "date_histogram": Object { + "extended_bounds": Object { + "max": 1528977600000, + "min": 1528113600000, + }, + "field": "@timestamp", + "interval": "10800s", + "min_doc_count": 0, + }, + }, + }, + "terms": Object { + "field": "transaction.result", + "missing": "transaction_result_missing", + }, + }, + }, + "query": Object { + "bool": Object { + "filter": Array [ + Object { + "term": Object { + "context.service.name": "myServiceName", + }, + }, + Object { + "term": Object { + "transaction.type": "myTransactionType", + }, + }, + Object { + "range": Object { + "@timestamp": Object { + "format": "epoch_millis", + "gte": 1528113600000, + "lte": 1528977600000, + }, + }, + }, + ], + }, + }, + "size": 0, + }, + "index": "myIndex", + }, + ], +] +`; diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/__snapshots__/transform.test.ts.snap b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/__snapshots__/transform.test.ts.snap new file mode 100644 index 0000000000000..d94348c4f8513 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/__snapshots__/transform.test.ts.snap @@ -0,0 +1,682 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`timeseriesTransformer should match snapshot 1`] = ` +Object { + "dates": Array [ + 1528124400000, + 1528135200000, + 1528146000000, + 1528156800000, + 1528167600000, + 1528178400000, + 1528189200000, + 1528200000000, + 1528210800000, + 1528221600000, + 1528232400000, + 1528243200000, + 1528254000000, + 1528264800000, + 1528275600000, + 1528286400000, + 1528297200000, + 1528308000000, + 1528318800000, + 1528329600000, + 1528340400000, + 1528351200000, + 1528362000000, + 1528372800000, + 1528383600000, + 1528394400000, + 1528405200000, + 1528416000000, + 1528426800000, + 1528437600000, + 1528448400000, + 1528459200000, + 1528470000000, + 1528480800000, + 1528491600000, + 1528502400000, + 1528513200000, + 1528524000000, + 1528534800000, + 1528545600000, + 1528556400000, + 1528567200000, + 1528578000000, + 1528588800000, + 1528599600000, + 1528610400000, + 1528621200000, + 1528632000000, + 1528642800000, + 1528653600000, + 1528664400000, + 1528675200000, + 1528686000000, + 1528696800000, + 1528707600000, + 1528718400000, + 1528729200000, + 1528740000000, + 1528750800000, + 1528761600000, + 1528772400000, + 1528783200000, + 1528794000000, + 1528804800000, + 1528815600000, + 1528826400000, + 1528837200000, + 1528848000000, + 1528858800000, + 1528869600000, + 1528880400000, + 1528891200000, + 1528902000000, + 1528912800000, + 1528923600000, + 1528934400000, + 1528945200000, + 1528956000000, + 1528966800000, + ], + "overallAvgDuration": 32861.15660262639, + "responseTimes": Object { + "avg": Array [ + 26193.277795595466, + 25291.787065995228, + 24690.306474667796, + 24809.8953814219, + 25460.0394764508, + 26360.440733498916, + 27050.95205479452, + 26555.857333903925, + 26164.343359049206, + 26989.84546419098, + 26314.409430068266, + 27460.774575018477, + 26461.469107431974, + 27657.584946692834, + 27940.445967005213, + 34454.377581534434, + 44024.31809353839, + 36374.53333333333, + 36991.29442471209, + 37178.002701986756, + 37605.57078923814, + 37319.89767295267, + 38709.5041348433, + 38140.131856255066, + 34564.81091043125, + 33256.37743828302, + 37251.5625266752, + 38681.89084929791, + 40677.801045709355, + 39987.86453616932, + 41059.392914139804, + 39630.710111535845, + 41561.81331074284, + 43079.490738297536, + 43925.39609283509, + 25821.91424646782, + 27343.60011755486, + 25249.95060523233, + 25492.77199074074, + 25991.647281682137, + 26273.31290445375, + 26234.98976780795, + 23494.54873786408, + 22008.80482069371, + 22828.136655635586, + 22138.7081404321, + 22634.985579811735, + 22202.780998080616, + 23084.082780163997, + 23109.666146341464, + 23306.89028152719, + 39341.022704095325, + 37467.17153341258, + 52457.50554180566, + 31327.95780166252, + 30695.334941163997, + 28895.042785967435, + 30649.363989982416, + 29802.63622014101, + 30759.03002829892, + 30399.76549608631, + 29421.610233534506, + 32641.679897096656, + 30621.65440666204, + 31039.60391005818, + 30954.760723541545, + 31902.050234568553, + 31594.350653473728, + 31343.87243248879, + 31200.14450867052, + 28560.946668743833, + 24700.216146371717, + 25261.025210523563, + 26041.39789649068, + 26123.556295209142, + 46231.36177177638, + 45350.42005506141, + 48256.049354513096, + 52360.30017052116, + ], + "avgAnomalies": undefined, + "p95": Array [ + 80738.78571428556, + 77058.03529411761, + 77892.20721980717, + 77085.86687499998, + 80048.3462981744, + 84089.21370223971, + 84880.90143416924, + 84554.8884781166, + 81839.39583333326, + 85993.55410163336, + 85001.44588628765, + 86980.16445312503, + 84961.8710743802, + 88906.54601889332, + 90198.34708994703, + 135627.71242424246, + 167037.1993837535, + 128293.12184873945, + 130653.54236263742, + 131630.8902645502, + 133581.33541666638, + 132697.92762266204, + 140003.6918918918, + 138149.5673529411, + 121872.37504835591, + 116378.03873517792, + 131545.40999999995, + 133111.25804878055, + 144821.9855278593, + 134737.3997727272, + 141206.57726666646, + 137731.8994082841, + 141476.23189033198, + 149636.31340909077, + 151934.55000000002, + 82198.17857142858, + 85946.43199999983, + 78617.66249999996, + 79606.48333333322, + 76297.93999999986, + 80742.63333333324, + 81291.45969696966, + 73467.02500000004, + 69177.66999999993, + 71956.06111111109, + 68480.91142857139, + 68957.0999999999, + 67489.50416666668, + 71556.91249999998, + 72157.65128205132, + 76124.5625, + 141709.34661835746, + 132371.48641975303, + 186783.51503759398, + 99540.17819499348, + 95982.62454212455, + 89559.3525925925, + 95769.83153735634, + 94063.90833755062, + 96399.67269119772, + 96436.42520161276, + 91860.16988095238, + 105989.8333333334, + 97937.60342555979, + 98967.2249999999, + 97561.02469135808, + 102557.78813357186, + 100137.87578595306, + 98412.97120445351, + 101607.8328012912, + 92000.51368421057, + 78027.29473684198, + 80762.078801789, + 81160.83425925927, + 84215.58945578222, + 194188.21428571426, + 172616.2293896504, + 182653.81858220184, + 194970.75667682925, + ], + "p99": Array [ + 293257.27333333343, + 290195.8800000004, + 278548.1649999994, + 290701.8973333341, + 286839.5897777779, + 287979.5149999999, + 300107.5009999992, + 294402.2179999999, + 289849.459333332, + 296942.86299999955, + 292048.20571428596, + 299308.7371666667, + 292151.2377777781, + 302274.4192592592, + 299457.1612121209, + 350398.59259259375, + 421204.23333333334, + 368166.68976190523, + 367193.6128571426, + 375658.10190476174, + 368152.03822222137, + 365705.8319999995, + 380075.48533333326, + 375697.1923809518, + 351080.94111111073, + 339294.12799999997, + 378902.90649999987, + 384483.3233333327, + 394692.25000000105, + 403362.50399999996, + 396559.0274999993, + 371815.8320000008, + 405477.6133333326, + 413542.18133333366, + 424399.340000001, + 303815.9000000001, + 306305.0800000006, + 297521.94999999984, + 317938.0900000003, + 312262.3000000003, + 318428.8700000002, + 295421.4099999999, + 293067.86000000004, + 264935.71999999933, + 282795.0400000003, + 285390.8400000001, + 290402.24, + 293655.53, + 292723.56999999995, + 301051.32000000105, + 291322.0499999998, + 379855.2444444447, + 371175.2592000001, + 498378.4238888898, + 331118.6599999997, + 328101.3999999988, + 313951.54249999986, + 323340.5274074075, + 315055.5047619052, + 330070.03599999985, + 320531.54416666675, + 315137.16628571344, + 337251.4042424246, + 327054.9243636365, + 327653.0000000006, + 324505.1399999999, + 338040.3999999998, + 328600.5173333335, + 334060.93628571345, + 328569.4964999998, + 320227.32399999973, + 292019.2899999998, + 297757.72666666657, + 308034.4466666669, + 301128.4895238093, + 447266.9, + 409147.332500001, + 423121.9773333328, + 473485.4199999998, + ], + }, + "totalHits": 1297673, + "tpmBuckets": Array [ + Object { + "avg": 70293.29113924051, + "key": "HTTP 2xx", + "values": Array [ + 81460, + 82320, + 82485, + 83995, + 82805, + 82155, + 81915, + 81475, + 83510, + 82345, + 82330, + 82755, + 83375, + 82050, + 81235, + 75725, + 80890, + 82650, + 81055, + 82265, + 82515, + 83020, + 82610, + 80820, + 82600, + 82670, + 81555, + 83350, + 80960, + 82895, + 81650, + 82825, + 82715, + 82460, + 82020, + 22640, + 22785, + 22830, + 22930, + 23360, + 23425, + 22605, + 23060, + 22675, + 23030, + 23070, + 22535, + 23055, + 22935, + 22910, + 23075, + 81255, + 84125, + 81440, + 82460, + 82170, + 85015, + 81820, + 83225, + 83475, + 82490, + 82940, + 83425, + 81805, + 83290, + 82535, + 82090, + 82385, + 83775, + 82970, + 84060, + 84315, + 83275, + 83615, + 82885, + 75625, + 82160, + 82320, + 81845, + ], + }, + Object { + "avg": 420.88607594936707, + "key": "HTTP 3xx", + "values": Array [ + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 20205, + 2270, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 10775, + 0, + 0, + 0, + ], + }, + Object { + "avg": 5108.9240506329115, + "key": "HTTP 4xx", + "values": Array [ + 6065, + 6025, + 5810, + 6190, + 5955, + 6370, + 6170, + 5820, + 6165, + 6115, + 6080, + 6000, + 6185, + 6155, + 5910, + 5625, + 6215, + 6235, + 5815, + 6100, + 6010, + 5960, + 6240, + 5945, + 6150, + 6030, + 5950, + 6160, + 5855, + 6160, + 6265, + 6250, + 5835, + 6290, + 5740, + 1420, + 1200, + 1365, + 1475, + 1405, + 1500, + 1320, + 1300, + 1395, + 1295, + 1455, + 1240, + 1555, + 1385, + 1395, + 1375, + 5835, + 6350, + 5815, + 5775, + 6085, + 6135, + 5970, + 5765, + 6055, + 6015, + 6345, + 5985, + 5920, + 5880, + 5810, + 6350, + 6120, + 6275, + 6035, + 6030, + 6270, + 6080, + 6315, + 6385, + 5915, + 6105, + 5990, + 6070, + ], + }, + Object { + "avg": 5115.632911392405, + "key": "HTTP 5xx", + "values": Array [ + 6015, + 5980, + 6150, + 6165, + 6360, + 6090, + 6085, + 6175, + 6245, + 5790, + 6075, + 5955, + 6175, + 6060, + 5900, + 5455, + 5880, + 6215, + 6040, + 6010, + 6440, + 6205, + 6075, + 5760, + 6205, + 5885, + 6215, + 6275, + 5945, + 5915, + 6075, + 6410, + 5885, + 5995, + 6170, + 1420, + 1535, + 1415, + 1515, + 1630, + 1345, + 1485, + 1390, + 1445, + 1360, + 1395, + 1190, + 1440, + 1290, + 1320, + 1480, + 6065, + 6270, + 5675, + 6200, + 6075, + 6195, + 6045, + 6040, + 5880, + 6035, + 5990, + 5825, + 5940, + 6225, + 6190, + 6415, + 5990, + 5860, + 6145, + 6195, + 6155, + 6240, + 6100, + 6120, + 5440, + 6175, + 5805, + 5915, + ], + }, + Object { + "avg": NaN, + "key": "A Custom Bucket (that should be last)", + "values": Array [], + }, + ], +} +`; diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts new file mode 100644 index 0000000000000..a756185eda701 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.test.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ESResponse, timeseriesFetcher } from './fetcher'; + +describe('timeseriesFetcher', () => { + let res: ESResponse; + let clientSpy: jest.Mock; + beforeEach(async () => { + clientSpy = jest.fn().mockResolvedValueOnce('ES response'); + + res = await timeseriesFetcher({ + serviceName: 'myServiceName', + transactionType: 'myTransactionType', + transactionName: undefined, + setup: { + start: 1528113600000, + end: 1528977600000, + client: clientSpy, + config: { + get: () => 'myIndex' as any + } + } + }); + }); + + it('should call client with correct query', () => { + expect(clientSpy.mock.calls).toMatchSnapshot(); + }); + + it('should return correct response', () => { + expect(res).toBe('ES response'); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data.js b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts similarity index 52% rename from x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data.js rename to x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts index 8a9c68c013336..7360114c1df8d 100644 --- a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data.js +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/fetcher.ts @@ -4,28 +4,70 @@ * you may not use this file except in compliance with the Elastic License. */ +import { AggregationSearchResponse } from 'elasticsearch'; +import { IOptions } from '.'; import { + SERVICE_NAME, TRANSACTION_DURATION, + TRANSACTION_NAME, TRANSACTION_RESULT, - SERVICE_NAME, - TRANSACTION_TYPE, - TRANSACTION_NAME -} from '../../../../common/constants'; -import { get, sortBy, round } from 'lodash'; -import mean from 'lodash.mean'; -import { getBucketSize } from '../../helpers/get_bucket_size'; -import { getAvgResponseTimeAnomalies } from './get_avg_response_time_anomalies/get_avg_response_time_anomalies'; + TRANSACTION_TYPE +} from '../../../../../common/constants'; +import { getBucketSize } from '../../../helpers/get_bucket_size'; + +interface ResponseTimeBucket { + key_as_string: string; + key: number; + doc_count: number; + avg: { + value: number | null; + }; + pct: { + values: { + '95.0': number | 'NaN'; + '99.0': number | 'NaN'; + }; + }; +} + +interface TransactionResultBucket { + key: string; + doc_count: number; + timeseries: { + buckets: Array<{ + key_as_string: string; + key: number; + doc_count: number; + }>; + }; +} + +interface Aggs { + response_times: { + buckets: ResponseTimeBucket[]; + }; + transaction_results: { + doc_count_error_upper_bound: number; + sum_other_doc_count: number; + buckets: TransactionResultBucket[]; + }; + overall_avg_duration: { + value: number; + }; +} + +export type ESResponse = AggregationSearchResponse; -export async function getTimeseriesData({ +export function timeseriesFetcher({ serviceName, transactionType, transactionName, setup -}) { +}: IOptions): Promise { const { start, end, esFilterQuery, client, config } = setup; - const { intervalString, bucketSize } = getBucketSize(start, end, 'auto'); + const { intervalString } = getBucketSize(start, end, 'auto'); - const params = { + const params: any = { index: config.get('apm_oss.transactionIndices'), body: { size: 0, @@ -105,69 +147,5 @@ export async function getTimeseriesData({ ]; } - const resp = await client('search', params); - const responseTimeBuckets = get( - resp, - 'aggregations.response_times.buckets', - [] - ).slice(1, -1); - - const transactionResultBuckets = get( - resp, - 'aggregations.transaction_results.buckets', - [] - ); - - const overallAvgDuration = get( - resp, - 'aggregations.overall_avg_duration.value' - ); - const dates = responseTimeBuckets.map(bucket => bucket.key); - - const responseTime = responseTimeBuckets.reduce( - (acc, bucket) => { - const { '95.0': p95, '99.0': p99 } = bucket.pct.values; - - acc.avg.push(bucket.avg.value); - acc.p95.push(p95 >= 0 ? p95 : null); - acc.p99.push(p99 >= 0 ? p99 : null); - return acc; - }, - { avg: [], p95: [], p99: [] } - ); - - const tpmBuckets = sortBy( - transactionResultBuckets.map(({ key, timeseries }) => { - const tpmValues = timeseries.buckets - .slice(1, -1) - .map(bucket => round(bucket.doc_count * (60 / bucketSize), 1)); - - return { - key, - avg: mean(tpmValues), - values: tpmValues - }; - }), - bucket => bucket.key.replace(/^HTTP (\d)xx$/, '00$1') // ensure that HTTP 3xx are sorted at the top - ); - - const avgResponseTimesAnomalies = await getAvgResponseTimeAnomalies({ - serviceName, - transactionType, - transactionName, - setup - }); - - return { - total_hits: resp.hits.total, - dates: dates, - response_times: { - avg: responseTime.avg, - p95: responseTime.p95, - p99: responseTime.p99, - avg_anomalies: avgResponseTimesAnomalies - }, - tpm_buckets: tpmBuckets, - overall_avg_duration: overallAvgDuration || 0 - }; + return client('search', params); } diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/index.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/index.ts new file mode 100644 index 0000000000000..8327193d82111 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/index.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getBucketSize } from '../../../helpers/get_bucket_size'; +import { Setup } from '../../../helpers/setup_request'; +import { getAvgResponseTimeAnomalies } from '../get_avg_response_time_anomalies'; +import { timeseriesFetcher } from './fetcher'; +import { timeseriesTransformer } from './transform'; + +export interface IOptions { + serviceName: string; + transactionType: string; + transactionName?: string; + setup: Setup; +} + +export async function getTimeseriesData(options: IOptions) { + const { start, end } = options.setup; + const { bucketSize } = getBucketSize(start, end, 'auto'); + + const avgAnomaliesResponse = await getAvgResponseTimeAnomalies(options); + const timeseriesResponse = await timeseriesFetcher(options); + return timeseriesTransformer({ + timeseriesResponse, + avgAnomaliesResponse, + bucketSize + }); +} diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/mock-responses/timeseries_response.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/mock-responses/timeseries_response.ts new file mode 100644 index 0000000000000..075ede23fb38e --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/mock-responses/timeseries_response.ts @@ -0,0 +1,2829 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ESResponse } from '../fetcher'; + +export const timeseriesResponse: ESResponse = { + took: 368, + timed_out: false, + _shards: { + total: 90, + successful: 90, + skipped: 0, + failed: 0 + }, + hits: { + total: 1297673, + max_score: 0, + hits: [] + }, + aggregations: { + transaction_results: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { + key: 'A Custom Bucket (that should be last)', + doc_count: 0, + timeseries: { buckets: [] } + }, + { + key: 'HTTP 2xx', + doc_count: 1127080, + timeseries: { + buckets: [ + { + key_as_string: '2018-06-04T12:00:00.000Z', + key: 1528113600000, + doc_count: 16446 + }, + { + key_as_string: '2018-06-04T15:00:00.000Z', + key: 1528124400000, + doc_count: 16292 + }, + { + key_as_string: '2018-06-04T18:00:00.000Z', + key: 1528135200000, + doc_count: 16464 + }, + { + key_as_string: '2018-06-04T21:00:00.000Z', + key: 1528146000000, + doc_count: 16497 + }, + { + key_as_string: '2018-06-05T00:00:00.000Z', + key: 1528156800000, + doc_count: 16799 + }, + { + key_as_string: '2018-06-05T03:00:00.000Z', + key: 1528167600000, + doc_count: 16561 + }, + { + key_as_string: '2018-06-05T06:00:00.000Z', + key: 1528178400000, + doc_count: 16431 + }, + { + key_as_string: '2018-06-05T09:00:00.000Z', + key: 1528189200000, + doc_count: 16383 + }, + { + key_as_string: '2018-06-05T12:00:00.000Z', + key: 1528200000000, + doc_count: 16295 + }, + { + key_as_string: '2018-06-05T15:00:00.000Z', + key: 1528210800000, + doc_count: 16702 + }, + { + key_as_string: '2018-06-05T18:00:00.000Z', + key: 1528221600000, + doc_count: 16469 + }, + { + key_as_string: '2018-06-05T21:00:00.000Z', + key: 1528232400000, + doc_count: 16466 + }, + { + key_as_string: '2018-06-06T00:00:00.000Z', + key: 1528243200000, + doc_count: 16551 + }, + { + key_as_string: '2018-06-06T03:00:00.000Z', + key: 1528254000000, + doc_count: 16675 + }, + { + key_as_string: '2018-06-06T06:00:00.000Z', + key: 1528264800000, + doc_count: 16410 + }, + { + key_as_string: '2018-06-06T09:00:00.000Z', + key: 1528275600000, + doc_count: 16247 + }, + { + key_as_string: '2018-06-06T12:00:00.000Z', + key: 1528286400000, + doc_count: 15145 + }, + { + key_as_string: '2018-06-06T15:00:00.000Z', + key: 1528297200000, + doc_count: 16178 + }, + { + key_as_string: '2018-06-06T18:00:00.000Z', + key: 1528308000000, + doc_count: 16530 + }, + { + key_as_string: '2018-06-06T21:00:00.000Z', + key: 1528318800000, + doc_count: 16211 + }, + { + key_as_string: '2018-06-07T00:00:00.000Z', + key: 1528329600000, + doc_count: 16453 + }, + { + key_as_string: '2018-06-07T03:00:00.000Z', + key: 1528340400000, + doc_count: 16503 + }, + { + key_as_string: '2018-06-07T06:00:00.000Z', + key: 1528351200000, + doc_count: 16604 + }, + { + key_as_string: '2018-06-07T09:00:00.000Z', + key: 1528362000000, + doc_count: 16522 + }, + { + key_as_string: '2018-06-07T12:00:00.000Z', + key: 1528372800000, + doc_count: 16164 + }, + { + key_as_string: '2018-06-07T15:00:00.000Z', + key: 1528383600000, + doc_count: 16520 + }, + { + key_as_string: '2018-06-07T18:00:00.000Z', + key: 1528394400000, + doc_count: 16534 + }, + { + key_as_string: '2018-06-07T21:00:00.000Z', + key: 1528405200000, + doc_count: 16311 + }, + { + key_as_string: '2018-06-08T00:00:00.000Z', + key: 1528416000000, + doc_count: 16670 + }, + { + key_as_string: '2018-06-08T03:00:00.000Z', + key: 1528426800000, + doc_count: 16192 + }, + { + key_as_string: '2018-06-08T06:00:00.000Z', + key: 1528437600000, + doc_count: 16579 + }, + { + key_as_string: '2018-06-08T09:00:00.000Z', + key: 1528448400000, + doc_count: 16330 + }, + { + key_as_string: '2018-06-08T12:00:00.000Z', + key: 1528459200000, + doc_count: 16565 + }, + { + key_as_string: '2018-06-08T15:00:00.000Z', + key: 1528470000000, + doc_count: 16543 + }, + { + key_as_string: '2018-06-08T18:00:00.000Z', + key: 1528480800000, + doc_count: 16492 + }, + { + key_as_string: '2018-06-08T21:00:00.000Z', + key: 1528491600000, + doc_count: 16404 + }, + { + key_as_string: '2018-06-09T00:00:00.000Z', + key: 1528502400000, + doc_count: 4528 + }, + { + key_as_string: '2018-06-09T03:00:00.000Z', + key: 1528513200000, + doc_count: 4557 + }, + { + key_as_string: '2018-06-09T06:00:00.000Z', + key: 1528524000000, + doc_count: 4566 + }, + { + key_as_string: '2018-06-09T09:00:00.000Z', + key: 1528534800000, + doc_count: 4586 + }, + { + key_as_string: '2018-06-09T12:00:00.000Z', + key: 1528545600000, + doc_count: 4672 + }, + { + key_as_string: '2018-06-09T15:00:00.000Z', + key: 1528556400000, + doc_count: 4685 + }, + { + key_as_string: '2018-06-09T18:00:00.000Z', + key: 1528567200000, + doc_count: 4521 + }, + { + key_as_string: '2018-06-09T21:00:00.000Z', + key: 1528578000000, + doc_count: 4612 + }, + { + key_as_string: '2018-06-10T00:00:00.000Z', + key: 1528588800000, + doc_count: 4535 + }, + { + key_as_string: '2018-06-10T03:00:00.000Z', + key: 1528599600000, + doc_count: 4606 + }, + { + key_as_string: '2018-06-10T06:00:00.000Z', + key: 1528610400000, + doc_count: 4614 + }, + { + key_as_string: '2018-06-10T09:00:00.000Z', + key: 1528621200000, + doc_count: 4507 + }, + { + key_as_string: '2018-06-10T12:00:00.000Z', + key: 1528632000000, + doc_count: 4611 + }, + { + key_as_string: '2018-06-10T15:00:00.000Z', + key: 1528642800000, + doc_count: 4587 + }, + { + key_as_string: '2018-06-10T18:00:00.000Z', + key: 1528653600000, + doc_count: 4582 + }, + { + key_as_string: '2018-06-10T21:00:00.000Z', + key: 1528664400000, + doc_count: 4615 + }, + { + key_as_string: '2018-06-11T00:00:00.000Z', + key: 1528675200000, + doc_count: 16251 + }, + { + key_as_string: '2018-06-11T03:00:00.000Z', + key: 1528686000000, + doc_count: 16825 + }, + { + key_as_string: '2018-06-11T06:00:00.000Z', + key: 1528696800000, + doc_count: 16288 + }, + { + key_as_string: '2018-06-11T09:00:00.000Z', + key: 1528707600000, + doc_count: 16492 + }, + { + key_as_string: '2018-06-11T12:00:00.000Z', + key: 1528718400000, + doc_count: 16434 + }, + { + key_as_string: '2018-06-11T15:00:00.000Z', + key: 1528729200000, + doc_count: 17003 + }, + { + key_as_string: '2018-06-11T18:00:00.000Z', + key: 1528740000000, + doc_count: 16364 + }, + { + key_as_string: '2018-06-11T21:00:00.000Z', + key: 1528750800000, + doc_count: 16645 + }, + { + key_as_string: '2018-06-12T00:00:00.000Z', + key: 1528761600000, + doc_count: 16695 + }, + { + key_as_string: '2018-06-12T03:00:00.000Z', + key: 1528772400000, + doc_count: 16498 + }, + { + key_as_string: '2018-06-12T06:00:00.000Z', + key: 1528783200000, + doc_count: 16588 + }, + { + key_as_string: '2018-06-12T09:00:00.000Z', + key: 1528794000000, + doc_count: 16685 + }, + { + key_as_string: '2018-06-12T12:00:00.000Z', + key: 1528804800000, + doc_count: 16361 + }, + { + key_as_string: '2018-06-12T15:00:00.000Z', + key: 1528815600000, + doc_count: 16658 + }, + { + key_as_string: '2018-06-12T18:00:00.000Z', + key: 1528826400000, + doc_count: 16507 + }, + { + key_as_string: '2018-06-12T21:00:00.000Z', + key: 1528837200000, + doc_count: 16418 + }, + { + key_as_string: '2018-06-13T00:00:00.000Z', + key: 1528848000000, + doc_count: 16477 + }, + { + key_as_string: '2018-06-13T03:00:00.000Z', + key: 1528858800000, + doc_count: 16755 + }, + { + key_as_string: '2018-06-13T06:00:00.000Z', + key: 1528869600000, + doc_count: 16594 + }, + { + key_as_string: '2018-06-13T09:00:00.000Z', + key: 1528880400000, + doc_count: 16812 + }, + { + key_as_string: '2018-06-13T12:00:00.000Z', + key: 1528891200000, + doc_count: 16863 + }, + { + key_as_string: '2018-06-13T15:00:00.000Z', + key: 1528902000000, + doc_count: 16655 + }, + { + key_as_string: '2018-06-13T18:00:00.000Z', + key: 1528912800000, + doc_count: 16723 + }, + { + key_as_string: '2018-06-13T21:00:00.000Z', + key: 1528923600000, + doc_count: 16577 + }, + { + key_as_string: '2018-06-14T00:00:00.000Z', + key: 1528934400000, + doc_count: 15125 + }, + { + key_as_string: '2018-06-14T03:00:00.000Z', + key: 1528945200000, + doc_count: 16432 + }, + { + key_as_string: '2018-06-14T06:00:00.000Z', + key: 1528956000000, + doc_count: 16464 + }, + { + key_as_string: '2018-06-14T09:00:00.000Z', + key: 1528966800000, + doc_count: 16369 + }, + { + key_as_string: '2018-06-14T12:00:00.000Z', + key: 1528977600000, + doc_count: 0 + } + ] + } + }, + { + key: 'HTTP 5xx', + doc_count: 82036, + timeseries: { + buckets: [ + { + key_as_string: '2018-06-04T12:00:00.000Z', + key: 1528113600000, + doc_count: 1209 + }, + { + key_as_string: '2018-06-04T15:00:00.000Z', + key: 1528124400000, + doc_count: 1203 + }, + { + key_as_string: '2018-06-04T18:00:00.000Z', + key: 1528135200000, + doc_count: 1196 + }, + { + key_as_string: '2018-06-04T21:00:00.000Z', + key: 1528146000000, + doc_count: 1230 + }, + { + key_as_string: '2018-06-05T00:00:00.000Z', + key: 1528156800000, + doc_count: 1233 + }, + { + key_as_string: '2018-06-05T03:00:00.000Z', + key: 1528167600000, + doc_count: 1272 + }, + { + key_as_string: '2018-06-05T06:00:00.000Z', + key: 1528178400000, + doc_count: 1218 + }, + { + key_as_string: '2018-06-05T09:00:00.000Z', + key: 1528189200000, + doc_count: 1217 + }, + { + key_as_string: '2018-06-05T12:00:00.000Z', + key: 1528200000000, + doc_count: 1235 + }, + { + key_as_string: '2018-06-05T15:00:00.000Z', + key: 1528210800000, + doc_count: 1249 + }, + { + key_as_string: '2018-06-05T18:00:00.000Z', + key: 1528221600000, + doc_count: 1158 + }, + { + key_as_string: '2018-06-05T21:00:00.000Z', + key: 1528232400000, + doc_count: 1215 + }, + { + key_as_string: '2018-06-06T00:00:00.000Z', + key: 1528243200000, + doc_count: 1191 + }, + { + key_as_string: '2018-06-06T03:00:00.000Z', + key: 1528254000000, + doc_count: 1235 + }, + { + key_as_string: '2018-06-06T06:00:00.000Z', + key: 1528264800000, + doc_count: 1212 + }, + { + key_as_string: '2018-06-06T09:00:00.000Z', + key: 1528275600000, + doc_count: 1180 + }, + { + key_as_string: '2018-06-06T12:00:00.000Z', + key: 1528286400000, + doc_count: 1091 + }, + { + key_as_string: '2018-06-06T15:00:00.000Z', + key: 1528297200000, + doc_count: 1176 + }, + { + key_as_string: '2018-06-06T18:00:00.000Z', + key: 1528308000000, + doc_count: 1243 + }, + { + key_as_string: '2018-06-06T21:00:00.000Z', + key: 1528318800000, + doc_count: 1208 + }, + { + key_as_string: '2018-06-07T00:00:00.000Z', + key: 1528329600000, + doc_count: 1202 + }, + { + key_as_string: '2018-06-07T03:00:00.000Z', + key: 1528340400000, + doc_count: 1288 + }, + { + key_as_string: '2018-06-07T06:00:00.000Z', + key: 1528351200000, + doc_count: 1241 + }, + { + key_as_string: '2018-06-07T09:00:00.000Z', + key: 1528362000000, + doc_count: 1215 + }, + { + key_as_string: '2018-06-07T12:00:00.000Z', + key: 1528372800000, + doc_count: 1152 + }, + { + key_as_string: '2018-06-07T15:00:00.000Z', + key: 1528383600000, + doc_count: 1241 + }, + { + key_as_string: '2018-06-07T18:00:00.000Z', + key: 1528394400000, + doc_count: 1177 + }, + { + key_as_string: '2018-06-07T21:00:00.000Z', + key: 1528405200000, + doc_count: 1243 + }, + { + key_as_string: '2018-06-08T00:00:00.000Z', + key: 1528416000000, + doc_count: 1255 + }, + { + key_as_string: '2018-06-08T03:00:00.000Z', + key: 1528426800000, + doc_count: 1189 + }, + { + key_as_string: '2018-06-08T06:00:00.000Z', + key: 1528437600000, + doc_count: 1183 + }, + { + key_as_string: '2018-06-08T09:00:00.000Z', + key: 1528448400000, + doc_count: 1215 + }, + { + key_as_string: '2018-06-08T12:00:00.000Z', + key: 1528459200000, + doc_count: 1282 + }, + { + key_as_string: '2018-06-08T15:00:00.000Z', + key: 1528470000000, + doc_count: 1177 + }, + { + key_as_string: '2018-06-08T18:00:00.000Z', + key: 1528480800000, + doc_count: 1199 + }, + { + key_as_string: '2018-06-08T21:00:00.000Z', + key: 1528491600000, + doc_count: 1234 + }, + { + key_as_string: '2018-06-09T00:00:00.000Z', + key: 1528502400000, + doc_count: 284 + }, + { + key_as_string: '2018-06-09T03:00:00.000Z', + key: 1528513200000, + doc_count: 307 + }, + { + key_as_string: '2018-06-09T06:00:00.000Z', + key: 1528524000000, + doc_count: 283 + }, + { + key_as_string: '2018-06-09T09:00:00.000Z', + key: 1528534800000, + doc_count: 303 + }, + { + key_as_string: '2018-06-09T12:00:00.000Z', + key: 1528545600000, + doc_count: 326 + }, + { + key_as_string: '2018-06-09T15:00:00.000Z', + key: 1528556400000, + doc_count: 269 + }, + { + key_as_string: '2018-06-09T18:00:00.000Z', + key: 1528567200000, + doc_count: 297 + }, + { + key_as_string: '2018-06-09T21:00:00.000Z', + key: 1528578000000, + doc_count: 278 + }, + { + key_as_string: '2018-06-10T00:00:00.000Z', + key: 1528588800000, + doc_count: 289 + }, + { + key_as_string: '2018-06-10T03:00:00.000Z', + key: 1528599600000, + doc_count: 272 + }, + { + key_as_string: '2018-06-10T06:00:00.000Z', + key: 1528610400000, + doc_count: 279 + }, + { + key_as_string: '2018-06-10T09:00:00.000Z', + key: 1528621200000, + doc_count: 238 + }, + { + key_as_string: '2018-06-10T12:00:00.000Z', + key: 1528632000000, + doc_count: 288 + }, + { + key_as_string: '2018-06-10T15:00:00.000Z', + key: 1528642800000, + doc_count: 258 + }, + { + key_as_string: '2018-06-10T18:00:00.000Z', + key: 1528653600000, + doc_count: 264 + }, + { + key_as_string: '2018-06-10T21:00:00.000Z', + key: 1528664400000, + doc_count: 296 + }, + { + key_as_string: '2018-06-11T00:00:00.000Z', + key: 1528675200000, + doc_count: 1213 + }, + { + key_as_string: '2018-06-11T03:00:00.000Z', + key: 1528686000000, + doc_count: 1254 + }, + { + key_as_string: '2018-06-11T06:00:00.000Z', + key: 1528696800000, + doc_count: 1135 + }, + { + key_as_string: '2018-06-11T09:00:00.000Z', + key: 1528707600000, + doc_count: 1240 + }, + { + key_as_string: '2018-06-11T12:00:00.000Z', + key: 1528718400000, + doc_count: 1215 + }, + { + key_as_string: '2018-06-11T15:00:00.000Z', + key: 1528729200000, + doc_count: 1239 + }, + { + key_as_string: '2018-06-11T18:00:00.000Z', + key: 1528740000000, + doc_count: 1209 + }, + { + key_as_string: '2018-06-11T21:00:00.000Z', + key: 1528750800000, + doc_count: 1208 + }, + { + key_as_string: '2018-06-12T00:00:00.000Z', + key: 1528761600000, + doc_count: 1176 + }, + { + key_as_string: '2018-06-12T03:00:00.000Z', + key: 1528772400000, + doc_count: 1207 + }, + { + key_as_string: '2018-06-12T06:00:00.000Z', + key: 1528783200000, + doc_count: 1198 + }, + { + key_as_string: '2018-06-12T09:00:00.000Z', + key: 1528794000000, + doc_count: 1165 + }, + { + key_as_string: '2018-06-12T12:00:00.000Z', + key: 1528804800000, + doc_count: 1188 + }, + { + key_as_string: '2018-06-12T15:00:00.000Z', + key: 1528815600000, + doc_count: 1245 + }, + { + key_as_string: '2018-06-12T18:00:00.000Z', + key: 1528826400000, + doc_count: 1238 + }, + { + key_as_string: '2018-06-12T21:00:00.000Z', + key: 1528837200000, + doc_count: 1283 + }, + { + key_as_string: '2018-06-13T00:00:00.000Z', + key: 1528848000000, + doc_count: 1198 + }, + { + key_as_string: '2018-06-13T03:00:00.000Z', + key: 1528858800000, + doc_count: 1172 + }, + { + key_as_string: '2018-06-13T06:00:00.000Z', + key: 1528869600000, + doc_count: 1229 + }, + { + key_as_string: '2018-06-13T09:00:00.000Z', + key: 1528880400000, + doc_count: 1239 + }, + { + key_as_string: '2018-06-13T12:00:00.000Z', + key: 1528891200000, + doc_count: 1231 + }, + { + key_as_string: '2018-06-13T15:00:00.000Z', + key: 1528902000000, + doc_count: 1248 + }, + { + key_as_string: '2018-06-13T18:00:00.000Z', + key: 1528912800000, + doc_count: 1220 + }, + { + key_as_string: '2018-06-13T21:00:00.000Z', + key: 1528923600000, + doc_count: 1224 + }, + { + key_as_string: '2018-06-14T00:00:00.000Z', + key: 1528934400000, + doc_count: 1088 + }, + { + key_as_string: '2018-06-14T03:00:00.000Z', + key: 1528945200000, + doc_count: 1235 + }, + { + key_as_string: '2018-06-14T06:00:00.000Z', + key: 1528956000000, + doc_count: 1161 + }, + { + key_as_string: '2018-06-14T09:00:00.000Z', + key: 1528966800000, + doc_count: 1183 + }, + { + key_as_string: '2018-06-14T12:00:00.000Z', + key: 1528977600000, + doc_count: 0 + } + ] + } + }, + { + key: 'HTTP 4xx', + doc_count: 81907, + timeseries: { + buckets: [ + { + key_as_string: '2018-06-04T12:00:00.000Z', + key: 1528113600000, + doc_count: 1186 + }, + { + key_as_string: '2018-06-04T15:00:00.000Z', + key: 1528124400000, + doc_count: 1213 + }, + { + key_as_string: '2018-06-04T18:00:00.000Z', + key: 1528135200000, + doc_count: 1205 + }, + { + key_as_string: '2018-06-04T21:00:00.000Z', + key: 1528146000000, + doc_count: 1162 + }, + { + key_as_string: '2018-06-05T00:00:00.000Z', + key: 1528156800000, + doc_count: 1238 + }, + { + key_as_string: '2018-06-05T03:00:00.000Z', + key: 1528167600000, + doc_count: 1191 + }, + { + key_as_string: '2018-06-05T06:00:00.000Z', + key: 1528178400000, + doc_count: 1274 + }, + { + key_as_string: '2018-06-05T09:00:00.000Z', + key: 1528189200000, + doc_count: 1234 + }, + { + key_as_string: '2018-06-05T12:00:00.000Z', + key: 1528200000000, + doc_count: 1164 + }, + { + key_as_string: '2018-06-05T15:00:00.000Z', + key: 1528210800000, + doc_count: 1233 + }, + { + key_as_string: '2018-06-05T18:00:00.000Z', + key: 1528221600000, + doc_count: 1223 + }, + { + key_as_string: '2018-06-05T21:00:00.000Z', + key: 1528232400000, + doc_count: 1216 + }, + { + key_as_string: '2018-06-06T00:00:00.000Z', + key: 1528243200000, + doc_count: 1200 + }, + { + key_as_string: '2018-06-06T03:00:00.000Z', + key: 1528254000000, + doc_count: 1237 + }, + { + key_as_string: '2018-06-06T06:00:00.000Z', + key: 1528264800000, + doc_count: 1231 + }, + { + key_as_string: '2018-06-06T09:00:00.000Z', + key: 1528275600000, + doc_count: 1182 + }, + { + key_as_string: '2018-06-06T12:00:00.000Z', + key: 1528286400000, + doc_count: 1125 + }, + { + key_as_string: '2018-06-06T15:00:00.000Z', + key: 1528297200000, + doc_count: 1243 + }, + { + key_as_string: '2018-06-06T18:00:00.000Z', + key: 1528308000000, + doc_count: 1247 + }, + { + key_as_string: '2018-06-06T21:00:00.000Z', + key: 1528318800000, + doc_count: 1163 + }, + { + key_as_string: '2018-06-07T00:00:00.000Z', + key: 1528329600000, + doc_count: 1220 + }, + { + key_as_string: '2018-06-07T03:00:00.000Z', + key: 1528340400000, + doc_count: 1202 + }, + { + key_as_string: '2018-06-07T06:00:00.000Z', + key: 1528351200000, + doc_count: 1192 + }, + { + key_as_string: '2018-06-07T09:00:00.000Z', + key: 1528362000000, + doc_count: 1248 + }, + { + key_as_string: '2018-06-07T12:00:00.000Z', + key: 1528372800000, + doc_count: 1189 + }, + { + key_as_string: '2018-06-07T15:00:00.000Z', + key: 1528383600000, + doc_count: 1230 + }, + { + key_as_string: '2018-06-07T18:00:00.000Z', + key: 1528394400000, + doc_count: 1206 + }, + { + key_as_string: '2018-06-07T21:00:00.000Z', + key: 1528405200000, + doc_count: 1190 + }, + { + key_as_string: '2018-06-08T00:00:00.000Z', + key: 1528416000000, + doc_count: 1232 + }, + { + key_as_string: '2018-06-08T03:00:00.000Z', + key: 1528426800000, + doc_count: 1171 + }, + { + key_as_string: '2018-06-08T06:00:00.000Z', + key: 1528437600000, + doc_count: 1232 + }, + { + key_as_string: '2018-06-08T09:00:00.000Z', + key: 1528448400000, + doc_count: 1253 + }, + { + key_as_string: '2018-06-08T12:00:00.000Z', + key: 1528459200000, + doc_count: 1250 + }, + { + key_as_string: '2018-06-08T15:00:00.000Z', + key: 1528470000000, + doc_count: 1167 + }, + { + key_as_string: '2018-06-08T18:00:00.000Z', + key: 1528480800000, + doc_count: 1258 + }, + { + key_as_string: '2018-06-08T21:00:00.000Z', + key: 1528491600000, + doc_count: 1148 + }, + { + key_as_string: '2018-06-09T00:00:00.000Z', + key: 1528502400000, + doc_count: 284 + }, + { + key_as_string: '2018-06-09T03:00:00.000Z', + key: 1528513200000, + doc_count: 240 + }, + { + key_as_string: '2018-06-09T06:00:00.000Z', + key: 1528524000000, + doc_count: 273 + }, + { + key_as_string: '2018-06-09T09:00:00.000Z', + key: 1528534800000, + doc_count: 295 + }, + { + key_as_string: '2018-06-09T12:00:00.000Z', + key: 1528545600000, + doc_count: 281 + }, + { + key_as_string: '2018-06-09T15:00:00.000Z', + key: 1528556400000, + doc_count: 300 + }, + { + key_as_string: '2018-06-09T18:00:00.000Z', + key: 1528567200000, + doc_count: 264 + }, + { + key_as_string: '2018-06-09T21:00:00.000Z', + key: 1528578000000, + doc_count: 260 + }, + { + key_as_string: '2018-06-10T00:00:00.000Z', + key: 1528588800000, + doc_count: 279 + }, + { + key_as_string: '2018-06-10T03:00:00.000Z', + key: 1528599600000, + doc_count: 259 + }, + { + key_as_string: '2018-06-10T06:00:00.000Z', + key: 1528610400000, + doc_count: 291 + }, + { + key_as_string: '2018-06-10T09:00:00.000Z', + key: 1528621200000, + doc_count: 248 + }, + { + key_as_string: '2018-06-10T12:00:00.000Z', + key: 1528632000000, + doc_count: 311 + }, + { + key_as_string: '2018-06-10T15:00:00.000Z', + key: 1528642800000, + doc_count: 277 + }, + { + key_as_string: '2018-06-10T18:00:00.000Z', + key: 1528653600000, + doc_count: 279 + }, + { + key_as_string: '2018-06-10T21:00:00.000Z', + key: 1528664400000, + doc_count: 275 + }, + { + key_as_string: '2018-06-11T00:00:00.000Z', + key: 1528675200000, + doc_count: 1167 + }, + { + key_as_string: '2018-06-11T03:00:00.000Z', + key: 1528686000000, + doc_count: 1270 + }, + { + key_as_string: '2018-06-11T06:00:00.000Z', + key: 1528696800000, + doc_count: 1163 + }, + { + key_as_string: '2018-06-11T09:00:00.000Z', + key: 1528707600000, + doc_count: 1155 + }, + { + key_as_string: '2018-06-11T12:00:00.000Z', + key: 1528718400000, + doc_count: 1217 + }, + { + key_as_string: '2018-06-11T15:00:00.000Z', + key: 1528729200000, + doc_count: 1227 + }, + { + key_as_string: '2018-06-11T18:00:00.000Z', + key: 1528740000000, + doc_count: 1194 + }, + { + key_as_string: '2018-06-11T21:00:00.000Z', + key: 1528750800000, + doc_count: 1153 + }, + { + key_as_string: '2018-06-12T00:00:00.000Z', + key: 1528761600000, + doc_count: 1211 + }, + { + key_as_string: '2018-06-12T03:00:00.000Z', + key: 1528772400000, + doc_count: 1203 + }, + { + key_as_string: '2018-06-12T06:00:00.000Z', + key: 1528783200000, + doc_count: 1269 + }, + { + key_as_string: '2018-06-12T09:00:00.000Z', + key: 1528794000000, + doc_count: 1197 + }, + { + key_as_string: '2018-06-12T12:00:00.000Z', + key: 1528804800000, + doc_count: 1184 + }, + { + key_as_string: '2018-06-12T15:00:00.000Z', + key: 1528815600000, + doc_count: 1176 + }, + { + key_as_string: '2018-06-12T18:00:00.000Z', + key: 1528826400000, + doc_count: 1162 + }, + { + key_as_string: '2018-06-12T21:00:00.000Z', + key: 1528837200000, + doc_count: 1270 + }, + { + key_as_string: '2018-06-13T00:00:00.000Z', + key: 1528848000000, + doc_count: 1224 + }, + { + key_as_string: '2018-06-13T03:00:00.000Z', + key: 1528858800000, + doc_count: 1255 + }, + { + key_as_string: '2018-06-13T06:00:00.000Z', + key: 1528869600000, + doc_count: 1207 + }, + { + key_as_string: '2018-06-13T09:00:00.000Z', + key: 1528880400000, + doc_count: 1206 + }, + { + key_as_string: '2018-06-13T12:00:00.000Z', + key: 1528891200000, + doc_count: 1254 + }, + { + key_as_string: '2018-06-13T15:00:00.000Z', + key: 1528902000000, + doc_count: 1216 + }, + { + key_as_string: '2018-06-13T18:00:00.000Z', + key: 1528912800000, + doc_count: 1263 + }, + { + key_as_string: '2018-06-13T21:00:00.000Z', + key: 1528923600000, + doc_count: 1277 + }, + { + key_as_string: '2018-06-14T00:00:00.000Z', + key: 1528934400000, + doc_count: 1183 + }, + { + key_as_string: '2018-06-14T03:00:00.000Z', + key: 1528945200000, + doc_count: 1221 + }, + { + key_as_string: '2018-06-14T06:00:00.000Z', + key: 1528956000000, + doc_count: 1198 + }, + { + key_as_string: '2018-06-14T09:00:00.000Z', + key: 1528966800000, + doc_count: 1214 + }, + { + key_as_string: '2018-06-14T12:00:00.000Z', + key: 1528977600000, + doc_count: 0 + } + ] + } + }, + { + key: 'HTTP 3xx', + doc_count: 6650, + timeseries: { + buckets: [ + { + key_as_string: '2018-06-04T12:00:00.000Z', + key: 1528113600000, + doc_count: 0 + }, + { + key_as_string: '2018-06-04T15:00:00.000Z', + key: 1528124400000, + doc_count: 0 + }, + { + key_as_string: '2018-06-04T18:00:00.000Z', + key: 1528135200000, + doc_count: 0 + }, + { + key_as_string: '2018-06-04T21:00:00.000Z', + key: 1528146000000, + doc_count: 0 + }, + { + key_as_string: '2018-06-05T00:00:00.000Z', + key: 1528156800000, + doc_count: 0 + }, + { + key_as_string: '2018-06-05T03:00:00.000Z', + key: 1528167600000, + doc_count: 0 + }, + { + key_as_string: '2018-06-05T06:00:00.000Z', + key: 1528178400000, + doc_count: 0 + }, + { + key_as_string: '2018-06-05T09:00:00.000Z', + key: 1528189200000, + doc_count: 0 + }, + { + key_as_string: '2018-06-05T12:00:00.000Z', + key: 1528200000000, + doc_count: 0 + }, + { + key_as_string: '2018-06-05T15:00:00.000Z', + key: 1528210800000, + doc_count: 0 + }, + { + key_as_string: '2018-06-05T18:00:00.000Z', + key: 1528221600000, + doc_count: 0 + }, + { + key_as_string: '2018-06-05T21:00:00.000Z', + key: 1528232400000, + doc_count: 0 + }, + { + key_as_string: '2018-06-06T00:00:00.000Z', + key: 1528243200000, + doc_count: 0 + }, + { + key_as_string: '2018-06-06T03:00:00.000Z', + key: 1528254000000, + doc_count: 0 + }, + { + key_as_string: '2018-06-06T06:00:00.000Z', + key: 1528264800000, + doc_count: 0 + }, + { + key_as_string: '2018-06-06T09:00:00.000Z', + key: 1528275600000, + doc_count: 0 + }, + { + key_as_string: '2018-06-06T12:00:00.000Z', + key: 1528286400000, + doc_count: 4041 + }, + { + key_as_string: '2018-06-06T15:00:00.000Z', + key: 1528297200000, + doc_count: 454 + }, + { + key_as_string: '2018-06-06T18:00:00.000Z', + key: 1528308000000, + doc_count: 0 + }, + { + key_as_string: '2018-06-06T21:00:00.000Z', + key: 1528318800000, + doc_count: 0 + }, + { + key_as_string: '2018-06-07T00:00:00.000Z', + key: 1528329600000, + doc_count: 0 + }, + { + key_as_string: '2018-06-07T03:00:00.000Z', + key: 1528340400000, + doc_count: 0 + }, + { + key_as_string: '2018-06-07T06:00:00.000Z', + key: 1528351200000, + doc_count: 0 + }, + { + key_as_string: '2018-06-07T09:00:00.000Z', + key: 1528362000000, + doc_count: 0 + }, + { + key_as_string: '2018-06-07T12:00:00.000Z', + key: 1528372800000, + doc_count: 0 + }, + { + key_as_string: '2018-06-07T15:00:00.000Z', + key: 1528383600000, + doc_count: 0 + }, + { + key_as_string: '2018-06-07T18:00:00.000Z', + key: 1528394400000, + doc_count: 0 + }, + { + key_as_string: '2018-06-07T21:00:00.000Z', + key: 1528405200000, + doc_count: 0 + }, + { + key_as_string: '2018-06-08T00:00:00.000Z', + key: 1528416000000, + doc_count: 0 + }, + { + key_as_string: '2018-06-08T03:00:00.000Z', + key: 1528426800000, + doc_count: 0 + }, + { + key_as_string: '2018-06-08T06:00:00.000Z', + key: 1528437600000, + doc_count: 0 + }, + { + key_as_string: '2018-06-08T09:00:00.000Z', + key: 1528448400000, + doc_count: 0 + }, + { + key_as_string: '2018-06-08T12:00:00.000Z', + key: 1528459200000, + doc_count: 0 + }, + { + key_as_string: '2018-06-08T15:00:00.000Z', + key: 1528470000000, + doc_count: 0 + }, + { + key_as_string: '2018-06-08T18:00:00.000Z', + key: 1528480800000, + doc_count: 0 + }, + { + key_as_string: '2018-06-08T21:00:00.000Z', + key: 1528491600000, + doc_count: 0 + }, + { + key_as_string: '2018-06-09T00:00:00.000Z', + key: 1528502400000, + doc_count: 0 + }, + { + key_as_string: '2018-06-09T03:00:00.000Z', + key: 1528513200000, + doc_count: 0 + }, + { + key_as_string: '2018-06-09T06:00:00.000Z', + key: 1528524000000, + doc_count: 0 + }, + { + key_as_string: '2018-06-09T09:00:00.000Z', + key: 1528534800000, + doc_count: 0 + }, + { + key_as_string: '2018-06-09T12:00:00.000Z', + key: 1528545600000, + doc_count: 0 + }, + { + key_as_string: '2018-06-09T15:00:00.000Z', + key: 1528556400000, + doc_count: 0 + }, + { + key_as_string: '2018-06-09T18:00:00.000Z', + key: 1528567200000, + doc_count: 0 + }, + { + key_as_string: '2018-06-09T21:00:00.000Z', + key: 1528578000000, + doc_count: 0 + }, + { + key_as_string: '2018-06-10T00:00:00.000Z', + key: 1528588800000, + doc_count: 0 + }, + { + key_as_string: '2018-06-10T03:00:00.000Z', + key: 1528599600000, + doc_count: 0 + }, + { + key_as_string: '2018-06-10T06:00:00.000Z', + key: 1528610400000, + doc_count: 0 + }, + { + key_as_string: '2018-06-10T09:00:00.000Z', + key: 1528621200000, + doc_count: 0 + }, + { + key_as_string: '2018-06-10T12:00:00.000Z', + key: 1528632000000, + doc_count: 0 + }, + { + key_as_string: '2018-06-10T15:00:00.000Z', + key: 1528642800000, + doc_count: 0 + }, + { + key_as_string: '2018-06-10T18:00:00.000Z', + key: 1528653600000, + doc_count: 0 + }, + { + key_as_string: '2018-06-10T21:00:00.000Z', + key: 1528664400000, + doc_count: 0 + }, + { + key_as_string: '2018-06-11T00:00:00.000Z', + key: 1528675200000, + doc_count: 0 + }, + { + key_as_string: '2018-06-11T03:00:00.000Z', + key: 1528686000000, + doc_count: 0 + }, + { + key_as_string: '2018-06-11T06:00:00.000Z', + key: 1528696800000, + doc_count: 0 + }, + { + key_as_string: '2018-06-11T09:00:00.000Z', + key: 1528707600000, + doc_count: 0 + }, + { + key_as_string: '2018-06-11T12:00:00.000Z', + key: 1528718400000, + doc_count: 0 + }, + { + key_as_string: '2018-06-11T15:00:00.000Z', + key: 1528729200000, + doc_count: 0 + }, + { + key_as_string: '2018-06-11T18:00:00.000Z', + key: 1528740000000, + doc_count: 0 + }, + { + key_as_string: '2018-06-11T21:00:00.000Z', + key: 1528750800000, + doc_count: 0 + }, + { + key_as_string: '2018-06-12T00:00:00.000Z', + key: 1528761600000, + doc_count: 0 + }, + { + key_as_string: '2018-06-12T03:00:00.000Z', + key: 1528772400000, + doc_count: 0 + }, + { + key_as_string: '2018-06-12T06:00:00.000Z', + key: 1528783200000, + doc_count: 0 + }, + { + key_as_string: '2018-06-12T09:00:00.000Z', + key: 1528794000000, + doc_count: 0 + }, + { + key_as_string: '2018-06-12T12:00:00.000Z', + key: 1528804800000, + doc_count: 0 + }, + { + key_as_string: '2018-06-12T15:00:00.000Z', + key: 1528815600000, + doc_count: 0 + }, + { + key_as_string: '2018-06-12T18:00:00.000Z', + key: 1528826400000, + doc_count: 0 + }, + { + key_as_string: '2018-06-12T21:00:00.000Z', + key: 1528837200000, + doc_count: 0 + }, + { + key_as_string: '2018-06-13T00:00:00.000Z', + key: 1528848000000, + doc_count: 0 + }, + { + key_as_string: '2018-06-13T03:00:00.000Z', + key: 1528858800000, + doc_count: 0 + }, + { + key_as_string: '2018-06-13T06:00:00.000Z', + key: 1528869600000, + doc_count: 0 + }, + { + key_as_string: '2018-06-13T09:00:00.000Z', + key: 1528880400000, + doc_count: 0 + }, + { + key_as_string: '2018-06-13T12:00:00.000Z', + key: 1528891200000, + doc_count: 0 + }, + { + key_as_string: '2018-06-13T15:00:00.000Z', + key: 1528902000000, + doc_count: 0 + }, + { + key_as_string: '2018-06-13T18:00:00.000Z', + key: 1528912800000, + doc_count: 0 + }, + { + key_as_string: '2018-06-13T21:00:00.000Z', + key: 1528923600000, + doc_count: 0 + }, + { + key_as_string: '2018-06-14T00:00:00.000Z', + key: 1528934400000, + doc_count: 2155 + }, + { + key_as_string: '2018-06-14T03:00:00.000Z', + key: 1528945200000, + doc_count: 0 + }, + { + key_as_string: '2018-06-14T06:00:00.000Z', + key: 1528956000000, + doc_count: 0 + }, + { + key_as_string: '2018-06-14T09:00:00.000Z', + key: 1528966800000, + doc_count: 0 + }, + { + key_as_string: '2018-06-14T12:00:00.000Z', + key: 1528977600000, + doc_count: 0 + } + ] + } + } + ] + }, + response_times: { + buckets: [ + { + key_as_string: '2018-06-04T12:00:00.000Z', + key: 1528113600000, + doc_count: 18841, + pct: { + values: { + '95.0': 82172.85648714812, + '99.0': 293866.3866666665 + } + }, + avg: { + value: 26310.63483891513 + } + }, + { + key_as_string: '2018-06-04T15:00:00.000Z', + key: 1528124400000, + doc_count: 18708, + pct: { + values: { + '95.0': 80738.78571428556, + '99.0': 293257.27333333343 + } + }, + avg: { + value: 26193.277795595466 + } + }, + { + key_as_string: '2018-06-04T18:00:00.000Z', + key: 1528135200000, + doc_count: 18865, + pct: { + values: { + '95.0': 77058.03529411761, + '99.0': 290195.8800000004 + } + }, + avg: { + value: 25291.787065995228 + } + }, + { + key_as_string: '2018-06-04T21:00:00.000Z', + key: 1528146000000, + doc_count: 18889, + pct: { + values: { + '95.0': 77892.20721980717, + '99.0': 278548.1649999994 + } + }, + avg: { + value: 24690.306474667796 + } + }, + { + key_as_string: '2018-06-05T00:00:00.000Z', + key: 1528156800000, + doc_count: 19270, + pct: { + values: { + '95.0': 77085.86687499998, + '99.0': 290701.8973333341 + } + }, + avg: { + value: 24809.8953814219 + } + }, + { + key_as_string: '2018-06-05T03:00:00.000Z', + key: 1528167600000, + doc_count: 19024, + pct: { + values: { + '95.0': 80048.3462981744, + '99.0': 286839.5897777779 + } + }, + avg: { + value: 25460.0394764508 + } + }, + { + key_as_string: '2018-06-05T06:00:00.000Z', + key: 1528178400000, + doc_count: 18923, + pct: { + values: { + '95.0': 84089.21370223971, + '99.0': 287979.5149999999 + } + }, + avg: { + value: 26360.440733498916 + } + }, + { + key_as_string: '2018-06-05T09:00:00.000Z', + key: 1528189200000, + doc_count: 18834, + pct: { + values: { + '95.0': 84880.90143416924, + '99.0': 300107.5009999992 + } + }, + avg: { + value: 27050.95205479452 + } + }, + { + key_as_string: '2018-06-05T12:00:00.000Z', + key: 1528200000000, + doc_count: 18694, + pct: { + values: { + '95.0': 84554.8884781166, + '99.0': 294402.2179999999 + } + }, + avg: { + value: 26555.857333903925 + } + }, + { + key_as_string: '2018-06-05T15:00:00.000Z', + key: 1528210800000, + doc_count: 19184, + pct: { + values: { + '95.0': 81839.39583333326, + '99.0': 289849.459333332 + } + }, + avg: { + value: 26164.343359049206 + } + }, + { + key_as_string: '2018-06-05T18:00:00.000Z', + key: 1528221600000, + doc_count: 18850, + pct: { + values: { + '95.0': 85993.55410163336, + '99.0': 296942.86299999955 + } + }, + avg: { + value: 26989.84546419098 + } + }, + { + key_as_string: '2018-06-05T21:00:00.000Z', + key: 1528232400000, + doc_count: 18897, + pct: { + values: { + '95.0': 85001.44588628765, + '99.0': 292048.20571428596 + } + }, + avg: { + value: 26314.409430068266 + } + }, + { + key_as_string: '2018-06-06T00:00:00.000Z', + key: 1528243200000, + doc_count: 18942, + pct: { + values: { + '95.0': 86980.16445312503, + '99.0': 299308.7371666667 + } + }, + avg: { + value: 27460.774575018477 + } + }, + { + key_as_string: '2018-06-06T03:00:00.000Z', + key: 1528254000000, + doc_count: 19147, + pct: { + values: { + '95.0': 84961.8710743802, + '99.0': 292151.2377777781 + } + }, + avg: { + value: 26461.469107431974 + } + }, + { + key_as_string: '2018-06-06T06:00:00.000Z', + key: 1528264800000, + doc_count: 18853, + pct: { + values: { + '95.0': 88906.54601889332, + '99.0': 302274.4192592592 + } + }, + avg: { + value: 27657.584946692834 + } + }, + { + key_as_string: '2018-06-06T09:00:00.000Z', + key: 1528275600000, + doc_count: 18609, + pct: { + values: { + '95.0': 90198.34708994703, + '99.0': 299457.1612121209 + } + }, + avg: { + value: 27940.445967005213 + } + }, + { + key_as_string: '2018-06-06T12:00:00.000Z', + key: 1528286400000, + doc_count: 21402, + pct: { + values: { + '95.0': 135627.71242424246, + '99.0': 350398.59259259375 + } + }, + avg: { + value: 34454.377581534434 + } + }, + { + key_as_string: '2018-06-06T15:00:00.000Z', + key: 1528297200000, + doc_count: 19051, + pct: { + values: { + '95.0': 167037.1993837535, + '99.0': 421204.23333333334 + } + }, + avg: { + value: 44024.31809353839 + } + }, + { + key_as_string: '2018-06-06T18:00:00.000Z', + key: 1528308000000, + doc_count: 19020, + pct: { + values: { + '95.0': 128293.12184873945, + '99.0': 368166.68976190523 + } + }, + avg: { + value: 36374.53333333333 + } + }, + { + key_as_string: '2018-06-06T21:00:00.000Z', + key: 1528318800000, + doc_count: 18582, + pct: { + values: { + '95.0': 130653.54236263742, + '99.0': 367193.6128571426 + } + }, + avg: { + value: 36991.29442471209 + } + }, + { + key_as_string: '2018-06-07T00:00:00.000Z', + key: 1528329600000, + doc_count: 18875, + pct: { + values: { + '95.0': 131630.8902645502, + '99.0': 375658.10190476174 + } + }, + avg: { + value: 37178.002701986756 + } + }, + { + key_as_string: '2018-06-07T03:00:00.000Z', + key: 1528340400000, + doc_count: 18993, + pct: { + values: { + '95.0': 133581.33541666638, + '99.0': 368152.03822222137 + } + }, + avg: { + value: 37605.57078923814 + } + }, + { + key_as_string: '2018-06-07T06:00:00.000Z', + key: 1528351200000, + doc_count: 19037, + pct: { + values: { + '95.0': 132697.92762266204, + '99.0': 365705.8319999995 + } + }, + avg: { + value: 37319.89767295267 + } + }, + { + key_as_string: '2018-06-07T09:00:00.000Z', + key: 1528362000000, + doc_count: 18985, + pct: { + values: { + '95.0': 140003.6918918918, + '99.0': 380075.48533333326 + } + }, + avg: { + value: 38709.5041348433 + } + }, + { + key_as_string: '2018-06-07T12:00:00.000Z', + key: 1528372800000, + doc_count: 18505, + pct: { + values: { + '95.0': 138149.5673529411, + '99.0': 375697.1923809518 + } + }, + avg: { + value: 38140.131856255066 + } + }, + { + key_as_string: '2018-06-07T15:00:00.000Z', + key: 1528383600000, + doc_count: 18991, + pct: { + values: { + '95.0': 121872.37504835591, + '99.0': 351080.94111111073 + } + }, + avg: { + value: 34564.81091043125 + } + }, + { + key_as_string: '2018-06-07T18:00:00.000Z', + key: 1528394400000, + doc_count: 18917, + pct: { + values: { + '95.0': 116378.03873517792, + '99.0': 339294.12799999997 + } + }, + avg: { + value: 33256.37743828302 + } + }, + { + key_as_string: '2018-06-07T21:00:00.000Z', + key: 1528405200000, + doc_count: 18744, + pct: { + values: { + '95.0': 131545.40999999995, + '99.0': 378902.90649999987 + } + }, + avg: { + value: 37251.5625266752 + } + }, + { + key_as_string: '2018-06-08T00:00:00.000Z', + key: 1528416000000, + doc_count: 19157, + pct: { + values: { + '95.0': 133111.25804878055, + '99.0': 384483.3233333327 + } + }, + avg: { + value: 38681.89084929791 + } + }, + { + key_as_string: '2018-06-08T03:00:00.000Z', + key: 1528426800000, + doc_count: 18552, + pct: { + values: { + '95.0': 144821.9855278593, + '99.0': 394692.25000000105 + } + }, + avg: { + value: 40677.801045709355 + } + }, + { + key_as_string: '2018-06-08T06:00:00.000Z', + key: 1528437600000, + doc_count: 18994, + pct: { + values: { + '95.0': 134737.3997727272, + '99.0': 403362.50399999996 + } + }, + avg: { + value: 39987.86453616932 + } + }, + { + key_as_string: '2018-06-08T09:00:00.000Z', + key: 1528448400000, + doc_count: 18798, + pct: { + values: { + '95.0': 141206.57726666646, + '99.0': 396559.0274999993 + } + }, + avg: { + value: 41059.392914139804 + } + }, + { + key_as_string: '2018-06-08T12:00:00.000Z', + key: 1528459200000, + doc_count: 19097, + pct: { + values: { + '95.0': 137731.8994082841, + '99.0': 371815.8320000008 + } + }, + avg: { + value: 39630.710111535845 + } + }, + { + key_as_string: '2018-06-08T15:00:00.000Z', + key: 1528470000000, + doc_count: 18887, + pct: { + values: { + '95.0': 141476.23189033198, + '99.0': 405477.6133333326 + } + }, + avg: { + value: 41561.81331074284 + } + }, + { + key_as_string: '2018-06-08T18:00:00.000Z', + key: 1528480800000, + doc_count: 18949, + pct: { + values: { + '95.0': 149636.31340909077, + '99.0': 413542.18133333366 + } + }, + avg: { + value: 43079.490738297536 + } + }, + { + key_as_string: '2018-06-08T21:00:00.000Z', + key: 1528491600000, + doc_count: 18786, + pct: { + values: { + '95.0': 151934.55000000002, + '99.0': 424399.340000001 + } + }, + avg: { + value: 43925.39609283509 + } + }, + { + key_as_string: '2018-06-09T00:00:00.000Z', + key: 1528502400000, + doc_count: 5096, + pct: { + values: { + '95.0': 82198.17857142858, + '99.0': 303815.9000000001 + } + }, + avg: { + value: 25821.91424646782 + } + }, + { + key_as_string: '2018-06-09T03:00:00.000Z', + key: 1528513200000, + doc_count: 5104, + pct: { + values: { + '95.0': 85946.43199999983, + '99.0': 306305.0800000006 + } + }, + avg: { + value: 27343.60011755486 + } + }, + { + key_as_string: '2018-06-09T06:00:00.000Z', + key: 1528524000000, + doc_count: 5122, + pct: { + values: { + '95.0': 78617.66249999996, + '99.0': 297521.94999999984 + } + }, + avg: { + value: 25249.95060523233 + } + }, + { + key_as_string: '2018-06-09T09:00:00.000Z', + key: 1528534800000, + doc_count: 5184, + pct: { + values: { + '95.0': 79606.48333333322, + '99.0': 317938.0900000003 + } + }, + avg: { + value: 25492.77199074074 + } + }, + { + key_as_string: '2018-06-09T12:00:00.000Z', + key: 1528545600000, + doc_count: 5279, + pct: { + values: { + '95.0': 76297.93999999986, + '99.0': 312262.3000000003 + } + }, + avg: { + value: 25991.647281682137 + } + }, + { + key_as_string: '2018-06-09T15:00:00.000Z', + key: 1528556400000, + doc_count: 5254, + pct: { + values: { + '95.0': 80742.63333333324, + '99.0': 318428.8700000002 + } + }, + avg: { + value: 26273.31290445375 + } + }, + { + key_as_string: '2018-06-09T18:00:00.000Z', + key: 1528567200000, + doc_count: 5082, + pct: { + values: { + '95.0': 81291.45969696966, + '99.0': 295421.4099999999 + } + }, + avg: { + value: 26234.98976780795 + } + }, + { + key_as_string: '2018-06-09T21:00:00.000Z', + key: 1528578000000, + doc_count: 5150, + pct: { + values: { + '95.0': 73467.02500000004, + '99.0': 293067.86000000004 + } + }, + avg: { + value: 23494.54873786408 + } + }, + { + key_as_string: '2018-06-10T00:00:00.000Z', + key: 1528588800000, + doc_count: 5103, + pct: { + values: { + '95.0': 69177.66999999993, + '99.0': 264935.71999999933 + } + }, + avg: { + value: 22008.80482069371 + } + }, + { + key_as_string: '2018-06-10T03:00:00.000Z', + key: 1528599600000, + doc_count: 5137, + pct: { + values: { + '95.0': 71956.06111111109, + '99.0': 282795.0400000003 + } + }, + avg: { + value: 22828.136655635586 + } + }, + { + key_as_string: '2018-06-10T06:00:00.000Z', + key: 1528610400000, + doc_count: 5184, + pct: { + values: { + '95.0': 68480.91142857139, + '99.0': 285390.8400000001 + } + }, + avg: { + value: 22138.7081404321 + } + }, + { + key_as_string: '2018-06-10T09:00:00.000Z', + key: 1528621200000, + doc_count: 4993, + pct: { + values: { + '95.0': 68957.0999999999, + '99.0': 290402.24 + } + }, + avg: { + value: 22634.985579811735 + } + }, + { + key_as_string: '2018-06-10T12:00:00.000Z', + key: 1528632000000, + doc_count: 5210, + pct: { + values: { + '95.0': 67489.50416666668, + '99.0': 293655.53 + } + }, + avg: { + value: 22202.780998080616 + } + }, + { + key_as_string: '2018-06-10T15:00:00.000Z', + key: 1528642800000, + doc_count: 5122, + pct: { + values: { + '95.0': 71556.91249999998, + '99.0': 292723.56999999995 + } + }, + avg: { + value: 23084.082780163997 + } + }, + { + key_as_string: '2018-06-10T18:00:00.000Z', + key: 1528653600000, + doc_count: 5125, + pct: { + values: { + '95.0': 72157.65128205132, + '99.0': 301051.32000000105 + } + }, + avg: { + value: 23109.666146341464 + } + }, + { + key_as_string: '2018-06-10T21:00:00.000Z', + key: 1528664400000, + doc_count: 5186, + pct: { + values: { + '95.0': 76124.5625, + '99.0': 291322.0499999998 + } + }, + avg: { + value: 23306.89028152719 + } + }, + { + key_as_string: '2018-06-11T00:00:00.000Z', + key: 1528675200000, + doc_count: 18631, + pct: { + values: { + '95.0': 141709.34661835746, + '99.0': 379855.2444444447 + } + }, + avg: { + value: 39341.022704095325 + } + }, + { + key_as_string: '2018-06-11T03:00:00.000Z', + key: 1528686000000, + doc_count: 19349, + pct: { + values: { + '95.0': 132371.48641975303, + '99.0': 371175.2592000001 + } + }, + avg: { + value: 37467.17153341258 + } + }, + { + key_as_string: '2018-06-11T06:00:00.000Z', + key: 1528696800000, + doc_count: 18586, + pct: { + values: { + '95.0': 186783.51503759398, + '99.0': 498378.4238888898 + } + }, + avg: { + value: 52457.50554180566 + } + }, + { + key_as_string: '2018-06-11T09:00:00.000Z', + key: 1528707600000, + doc_count: 18887, + pct: { + values: { + '95.0': 99540.17819499348, + '99.0': 331118.6599999997 + } + }, + avg: { + value: 31327.95780166252 + } + }, + { + key_as_string: '2018-06-11T12:00:00.000Z', + key: 1528718400000, + doc_count: 18866, + pct: { + values: { + '95.0': 95982.62454212455, + '99.0': 328101.3999999988 + } + }, + avg: { + value: 30695.334941163997 + } + }, + { + key_as_string: '2018-06-11T15:00:00.000Z', + key: 1528729200000, + doc_count: 19469, + pct: { + values: { + '95.0': 89559.3525925925, + '99.0': 313951.54249999986 + } + }, + avg: { + value: 28895.042785967435 + } + }, + { + key_as_string: '2018-06-11T18:00:00.000Z', + key: 1528740000000, + doc_count: 18767, + pct: { + values: { + '95.0': 95769.83153735634, + '99.0': 323340.5274074075 + } + }, + avg: { + value: 30649.363989982416 + } + }, + { + key_as_string: '2018-06-11T21:00:00.000Z', + key: 1528750800000, + doc_count: 19006, + pct: { + values: { + '95.0': 94063.90833755062, + '99.0': 315055.5047619052 + } + }, + avg: { + value: 29802.63622014101 + } + }, + { + key_as_string: '2018-06-12T00:00:00.000Z', + key: 1528761600000, + doc_count: 19082, + pct: { + values: { + '95.0': 96399.67269119772, + '99.0': 330070.03599999985 + } + }, + avg: { + value: 30759.03002829892 + } + }, + { + key_as_string: '2018-06-12T03:00:00.000Z', + key: 1528772400000, + doc_count: 18908, + pct: { + values: { + '95.0': 96436.42520161276, + '99.0': 320531.54416666675 + } + }, + avg: { + value: 30399.76549608631 + } + }, + { + key_as_string: '2018-06-12T06:00:00.000Z', + key: 1528783200000, + doc_count: 19055, + pct: { + values: { + '95.0': 91860.16988095238, + '99.0': 315137.16628571344 + } + }, + avg: { + value: 29421.610233534506 + } + }, + { + key_as_string: '2018-06-12T09:00:00.000Z', + key: 1528794000000, + doc_count: 19047, + pct: { + values: { + '95.0': 105989.8333333334, + '99.0': 337251.4042424246 + } + }, + avg: { + value: 32641.679897096656 + } + }, + { + key_as_string: '2018-06-12T12:00:00.000Z', + key: 1528804800000, + doc_count: 18733, + pct: { + values: { + '95.0': 97937.60342555979, + '99.0': 327054.9243636365 + } + }, + avg: { + value: 30621.65440666204 + } + }, + { + key_as_string: '2018-06-12T15:00:00.000Z', + key: 1528815600000, + doc_count: 19079, + pct: { + values: { + '95.0': 98967.2249999999, + '99.0': 327653.0000000006 + } + }, + avg: { + value: 31039.60391005818 + } + }, + { + key_as_string: '2018-06-12T18:00:00.000Z', + key: 1528826400000, + doc_count: 18907, + pct: { + values: { + '95.0': 97561.02469135808, + '99.0': 324505.1399999999 + } + }, + avg: { + value: 30954.760723541545 + } + }, + { + key_as_string: '2018-06-12T21:00:00.000Z', + key: 1528837200000, + doc_count: 18971, + pct: { + values: { + '95.0': 102557.78813357186, + '99.0': 338040.3999999998 + } + }, + avg: { + value: 31902.050234568553 + } + }, + { + key_as_string: '2018-06-13T00:00:00.000Z', + key: 1528848000000, + doc_count: 18899, + pct: { + values: { + '95.0': 100137.87578595306, + '99.0': 328600.5173333335 + } + }, + avg: { + value: 31594.350653473728 + } + }, + { + key_as_string: '2018-06-13T03:00:00.000Z', + key: 1528858800000, + doc_count: 19182, + pct: { + values: { + '95.0': 98412.97120445351, + '99.0': 334060.93628571345 + } + }, + avg: { + value: 31343.87243248879 + } + }, + { + key_as_string: '2018-06-13T06:00:00.000Z', + key: 1528869600000, + doc_count: 19030, + pct: { + values: { + '95.0': 101607.8328012912, + '99.0': 328569.4964999998 + } + }, + avg: { + value: 31200.14450867052 + } + }, + { + key_as_string: '2018-06-13T09:00:00.000Z', + key: 1528880400000, + doc_count: 19257, + pct: { + values: { + '95.0': 92000.51368421057, + '99.0': 320227.32399999973 + } + }, + avg: { + value: 28560.946668743833 + } + }, + { + key_as_string: '2018-06-13T12:00:00.000Z', + key: 1528891200000, + doc_count: 19348, + pct: { + values: { + '95.0': 78027.29473684198, + '99.0': 292019.2899999998 + } + }, + avg: { + value: 24700.216146371717 + } + }, + { + key_as_string: '2018-06-13T15:00:00.000Z', + key: 1528902000000, + doc_count: 19119, + pct: { + values: { + '95.0': 80762.078801789, + '99.0': 297757.72666666657 + } + }, + avg: { + value: 25261.025210523563 + } + }, + { + key_as_string: '2018-06-13T18:00:00.000Z', + key: 1528912800000, + doc_count: 19206, + pct: { + values: { + '95.0': 81160.83425925927, + '99.0': 308034.4466666669 + } + }, + avg: { + value: 26041.39789649068 + } + }, + { + key_as_string: '2018-06-13T21:00:00.000Z', + key: 1528923600000, + doc_count: 19078, + pct: { + values: { + '95.0': 84215.58945578222, + '99.0': 301128.4895238093 + } + }, + avg: { + value: 26123.556295209142 + } + }, + { + key_as_string: '2018-06-14T00:00:00.000Z', + key: 1528934400000, + doc_count: 19551, + pct: { + values: { + '95.0': 194188.21428571426, + '99.0': 447266.9 + } + }, + avg: { + value: 46231.36177177638 + } + }, + { + key_as_string: '2018-06-14T03:00:00.000Z', + key: 1528945200000, + doc_count: 18888, + pct: { + values: { + '95.0': 172616.2293896504, + '99.0': 409147.332500001 + } + }, + avg: { + value: 45350.42005506141 + } + }, + { + key_as_string: '2018-06-14T06:00:00.000Z', + key: 1528956000000, + doc_count: 18823, + pct: { + values: { + '95.0': 182653.81858220184, + '99.0': 423121.9773333328 + } + }, + avg: { + value: 48256.049354513096 + } + }, + { + key_as_string: '2018-06-14T09:00:00.000Z', + key: 1528966800000, + doc_count: 18766, + pct: { + values: { + '95.0': 194970.75667682925, + '99.0': 473485.4199999998 + } + }, + avg: { + value: 52360.30017052116 + } + }, + { + key_as_string: '2018-06-14T12:00:00.000Z', + key: 1528977600000, + doc_count: 0, + pct: { + values: { + '95.0': 'NaN', + '99.0': 'NaN' + } + }, + avg: { + value: null + } + } + ] + }, + overall_avg_duration: { + value: 32861.15660262639 + } + } +}; diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.test.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.test.ts new file mode 100644 index 0000000000000..ecf64e393b191 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.test.ts @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { first, last } from 'lodash'; +import { timeseriesResponse } from './mock-responses/timeseries_response'; +import { + getTpmBuckets, + TimeSeriesAPIResponse, + timeseriesTransformer +} from './transform'; + +describe('timeseriesTransformer', () => { + let res: TimeSeriesAPIResponse; + beforeEach(async () => { + res = await timeseriesTransformer({ + timeseriesResponse, + avgAnomaliesResponse: undefined, + bucketSize: 12 + }); + }); + + it('should not contain first and last bucket', () => { + const mockDates = timeseriesResponse.aggregations.transaction_results.buckets[0].timeseries.buckets.map( + bucket => bucket.key + ); + + expect(res.dates).not.toContain(first(mockDates)); + expect(res.dates).not.toContain(last(mockDates)); + expect(res.tpmBuckets[0].values).toHaveLength(res.dates.length); + }); + + it('should have correct order', () => { + expect(res.tpmBuckets.map(bucket => bucket.key)).toEqual([ + 'HTTP 2xx', + 'HTTP 3xx', + 'HTTP 4xx', + 'HTTP 5xx', + 'A Custom Bucket (that should be last)' + ]); + }); + + it('should match snapshot', () => { + expect(res).toMatchSnapshot(); + }); +}); + +describe('getTpmBuckets', () => { + it('should return response', () => { + const buckets = [ + { + key: 'HTTP 4xx', + doc_count: 300, + timeseries: { + buckets: [ + { + key_as_string: '', + key: 0, + doc_count: 0 + }, + { + key_as_string: '', + key: 1, + doc_count: 200 + }, + { + key_as_string: '', + key: 2, + doc_count: 300 + }, + { + key_as_string: '', + key: 3, + doc_count: 1337 + } + ] + } + }, + { + key: 'HTTP 5xx', + doc_count: 400, + timeseries: { + buckets: [ + { + key_as_string: '', + key: 0, + doc_count: 0 + }, + { + key_as_string: '', + key: 1, + doc_count: 500 + }, + { + key_as_string: '', + key: 2, + doc_count: 100 + }, + { + key_as_string: '', + key: 3, + doc_count: 1337 + } + ] + } + } + ]; + const bucketSize = 10; + expect(getTpmBuckets(buckets, bucketSize)).toEqual([ + { avg: 1500, key: 'HTTP 4xx', values: [1200, 1800] }, + { avg: 1800, key: 'HTTP 5xx', values: [3000, 600] } + ]); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts new file mode 100644 index 0000000000000..ff9b28239b521 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transactions/charts/get_timeseries_data/transform.ts @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { isNumber, round, sortBy } from 'lodash'; +import mean from 'lodash.mean'; +import { oc } from 'ts-optchain'; +import { IAvgAnomaliesResponse } from '../get_avg_response_time_anomalies'; +import { ESResponse } from './fetcher'; + +type MaybeNumber = number | null; + +export interface TimeSeriesAPIResponse { + totalHits: number; + dates: number[]; + responseTimes: { + avg: MaybeNumber[]; + p95: MaybeNumber[]; + p99: MaybeNumber[]; + avgAnomalies?: IAvgAnomaliesResponse; + }; + tpmBuckets: Array<{ + key: string; + avg: number; + values: number[]; + }>; + overallAvgDuration?: number; +} + +export function timeseriesTransformer({ + timeseriesResponse, + avgAnomaliesResponse, + bucketSize +}: { + timeseriesResponse: ESResponse; + avgAnomaliesResponse: IAvgAnomaliesResponse; + bucketSize: number; +}): TimeSeriesAPIResponse { + const aggs = timeseriesResponse.aggregations; + const overallAvgDuration = oc(aggs).overall_avg_duration.value(); + + const responseTimeBuckets = oc(aggs) + .response_times.buckets([]) + .slice(1, -1); + const dates = responseTimeBuckets.map(bucket => bucket.key); + const { avg, p95, p99 } = getResponseTime(responseTimeBuckets); + + const transactionResultBuckets = oc(aggs).transaction_results.buckets([]); + const tpmBuckets = getTpmBuckets(transactionResultBuckets, bucketSize); + + return { + totalHits: timeseriesResponse.hits.total, + dates, + responseTimes: { + avg, + p95, + p99, + avgAnomalies: avgAnomaliesResponse + }, + tpmBuckets, + overallAvgDuration + }; +} + +export function getTpmBuckets( + transactionResultBuckets: ESResponse['aggregations']['transaction_results']['buckets'], + bucketSize: number +) { + const buckets = transactionResultBuckets.map(({ key, timeseries }) => { + const tpmValues = timeseries.buckets + .slice(1, -1) + .map(bucket => round(bucket.doc_count * (60 / bucketSize), 1)); + + return { + key, + avg: mean(tpmValues), + values: tpmValues + }; + }); + + return sortBy( + buckets, + bucket => bucket.key.replace(/^HTTP (\d)xx$/, '00$1') // ensure that HTTP 3xx are sorted at the top + ); +} + +function getResponseTime( + responseTimeBuckets: ESResponse['aggregations']['response_times']['buckets'] +) { + return responseTimeBuckets.reduce( + (acc, bucket) => { + const { '95.0': p95, '99.0': p99 } = bucket.pct.values; + + acc.avg.push(bucket.avg.value); + acc.p95.push(isNumber(p95) ? p95 : null); + acc.p99.push(isNumber(p99) ? p99 : null); + return acc; + }, + { + avg: [] as MaybeNumber[], + p95: [] as MaybeNumber[], + p99: [] as MaybeNumber[] + } + ); +} diff --git a/x-pack/plugins/apm/server/lib/transactions/distribution/calculate_bucket_size.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/calculate_bucket_size.ts index 38025110730bf..cb0209f648b92 100644 --- a/x-pack/plugins/apm/server/lib/transactions/distribution/calculate_bucket_size.ts +++ b/x-pack/plugins/apm/server/lib/transactions/distribution/calculate_bucket_size.ts @@ -54,11 +54,16 @@ export async function calculateBucketSize( params.body.query.bool.filter.push(esFilterQuery); } - const resp = await client('search', params); + interface Aggs { + stats: { + max: number; + }; + } + + const resp = await client('search', params); const minBucketSize: number = config.get('xpack.apm.minimumBucketSize'); const bucketTargetCount: number = config.get('xpack.apm.bucketTargetCount'); - const max: number = resp.aggregations.stats.max; + const max = resp.aggregations.stats.max; const bucketSize = Math.floor(max / bucketTargetCount); - return bucketSize > minBucketSize ? bucketSize : minBucketSize; } diff --git a/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts similarity index 58% rename from x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets.ts rename to x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts index b552d7e3146e7..c7334fc233591 100644 --- a/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets.ts +++ b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/fetcher.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SearchParams, SearchResponse } from 'elasticsearch'; -import { oc } from 'ts-optchain'; +import { AggregationSearchResponse, SearchResponse } from 'elasticsearch'; import { SERVICE_NAME, TRACE_ID, @@ -13,27 +12,11 @@ import { TRANSACTION_ID, TRANSACTION_NAME, TRANSACTION_SAMPLED -} from '../../../../common/constants'; -import { Transaction } from '../../../../typings/Transaction'; -import { Setup } from '../../helpers/setup_request'; +} from 'x-pack/plugins/apm/common/constants'; +import { Setup } from 'x-pack/plugins/apm/server/lib/helpers/setup_request'; +import { Transaction } from 'x-pack/plugins/apm/typings/Transaction'; -export interface IBucket { - key: number; - count: number; - sample?: IBucketSample; -} - -interface IBucketSample { - traceId?: string; - transactionId?: string; -} - -interface IBucketsResponse { - totalHits: number; - buckets: IBucket[]; -} - -interface ESBucket { +interface Bucket { key: number; doc_count: number; sample: SearchResponse<{ @@ -44,16 +27,24 @@ interface ESBucket { }>; } -export async function getBuckets( +interface Aggs { + distribution: { + buckets: Bucket[]; + }; +} + +export type ESResponse = AggregationSearchResponse; + +export function bucketFetcher( serviceName: string, transactionName: string, bucketSize: number, setup: Setup -): Promise { +): Promise { const { start, end, esFilterQuery, client, config } = setup; - const bucketTargetCount: number = config.get('xpack.apm.bucketTargetCount'); - const params: SearchParams = { - index: config.get('apm_oss.transactionIndices'), + const bucketTargetCount = config.get('xpack.apm.bucketTargetCount'); + const params = { + index: config.get('apm_oss.transactionIndices'), body: { size: 0, query: { @@ -102,26 +93,5 @@ export async function getBuckets( params.body.query.bool.filter.push(esFilterQuery); } - const resp = await client('search', params); - const buckets = (resp.aggregations.distribution.buckets as ESBucket[]).map( - bucket => { - const sampleSource = oc(bucket).sample.hits.hits[0]._source(); - const isSampled = oc(sampleSource).transaction.sampled(false); - const sample = { - traceId: oc(sampleSource).trace.id(), - transactionId: oc(sampleSource).transaction.id() - }; - - return { - key: bucket.key, - count: bucket.doc_count, - sample: isSampled ? sample : undefined - }; - } - ); - - return { - totalHits: resp.hits.total, - buckets - }; + return client('search', params); } diff --git a/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts new file mode 100644 index 0000000000000..80ad247edf9f5 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/index.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Setup } from '../../../helpers/setup_request'; +import { bucketFetcher } from './fetcher'; +import { bucketTransformer } from './transform'; + +export async function getBuckets( + serviceName: string, + transactionName: string, + bucketSize: number, + setup: Setup +) { + const response = await bucketFetcher( + serviceName, + transactionName, + bucketSize, + setup + ); + + return bucketTransformer(response); +} diff --git a/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/transform.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/transform.ts new file mode 100644 index 0000000000000..1f8b76b54c2e1 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transactions/distribution/get_buckets/transform.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { isEmpty } from 'lodash'; +import { oc } from 'ts-optchain'; +import { ESResponse } from './fetcher'; + +export interface IBucket { + key: number; + count: number; + sample?: IBucketSample; +} + +interface IBucketSample { + traceId?: string; + transactionId?: string; +} + +interface IBucketsResponse { + totalHits: number; + buckets: IBucket[]; + defaultSample?: IBucketSample; +} + +function getDefaultSample(buckets: IBucket[]) { + const samples = buckets + .filter(bucket => bucket.count > 0 && bucket.sample) + .map(bucket => bucket.sample); + + if (isEmpty(samples)) { + return; + } + + const middleIndex = Math.floor(samples.length / 2); + return samples[middleIndex]; +} + +export function bucketTransformer(response: ESResponse): IBucketsResponse { + const buckets = response.aggregations.distribution.buckets.map(bucket => { + const sampleSource = oc(bucket).sample.hits.hits[0]._source(); + const isSampled = oc(sampleSource).transaction.sampled(false); + const sample = { + traceId: oc(sampleSource).trace.id(), + transactionId: oc(sampleSource).transaction.id() + }; + + return { + key: bucket.key, + count: bucket.doc_count, + sample: isSampled ? sample : undefined + }; + }); + + return { + totalHits: response.hits.total, + buckets, + defaultSample: getDefaultSample(buckets) + }; +} diff --git a/x-pack/plugins/apm/server/lib/transactions/distribution/get_distribution.ts b/x-pack/plugins/apm/server/lib/transactions/distribution/index.ts similarity index 59% rename from x-pack/plugins/apm/server/lib/transactions/distribution/get_distribution.ts rename to x-pack/plugins/apm/server/lib/transactions/distribution/index.ts index c3ba7d53c23e3..4ad022ef911ee 100644 --- a/x-pack/plugins/apm/server/lib/transactions/distribution/get_distribution.ts +++ b/x-pack/plugins/apm/server/lib/transactions/distribution/index.ts @@ -4,42 +4,30 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isEmpty } from 'lodash'; import { Setup } from '../../helpers/setup_request'; import { calculateBucketSize } from './calculate_bucket_size'; -import { getBuckets, IBucket } from './get_buckets'; +import { getBuckets } from './get_buckets'; +import { IBucket } from './get_buckets/transform'; -export interface IDistributionResponse { +export interface ITransactionDistributionAPIResponse { totalHits: number; buckets: IBucket[]; bucketSize: number; defaultSample?: IBucket['sample']; } -function getDefaultSample(buckets: IBucket[]) { - const samples = buckets - .filter(bucket => bucket.count > 0 && bucket.sample) - .map(bucket => bucket.sample); - - if (isEmpty(samples)) { - return; - } - - const middleIndex = Math.floor(samples.length / 2); - return samples[middleIndex]; -} - export async function getDistribution( serviceName: string, transactionName: string, setup: Setup -): Promise { +): Promise { const bucketSize = await calculateBucketSize( serviceName, transactionName, setup ); - const { buckets, totalHits } = await getBuckets( + + const { defaultSample, buckets, totalHits } = await getBuckets( serviceName, transactionName, bucketSize, @@ -50,6 +38,6 @@ export async function getDistribution( totalHits, buckets, bucketSize, - defaultSample: getDefaultSample(buckets) + defaultSample }; } diff --git a/x-pack/plugins/apm/server/lib/transactions/get_top_transactions.ts b/x-pack/plugins/apm/server/lib/transactions/get_top_transactions.ts deleted file mode 100644 index 059d3710b969a..0000000000000 --- a/x-pack/plugins/apm/server/lib/transactions/get_top_transactions.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { SearchParams, SearchResponse } from 'elasticsearch'; -import { get } from 'lodash'; -import { - PROCESSOR_EVENT, - SERVICE_NAME, - TRANSACTION_TYPE -} from '../../../common/constants'; -import { Transaction } from '../../../typings/Transaction'; -import { ITransactionGroup } from '../../../typings/TransactionGroup'; -import { Setup } from '../helpers/setup_request'; -import { - prepareTransactionGroups, - TRANSACTION_GROUP_AGGREGATES -} from '../helpers/transaction_group_query'; - -export async function getTopTransactions({ - setup, - transactionType, - serviceName -}: { - setup: Setup; - transactionType: string; - serviceName: string; -}): Promise { - const { start, end, esFilterQuery, client, config } = setup; - - const params: SearchParams = { - index: config.get('apm_oss.transactionIndices'), - body: { - size: 0, - query: { - bool: { - filter: [ - { term: { [SERVICE_NAME]: serviceName } }, - { term: { [TRANSACTION_TYPE]: transactionType } }, - { term: { [PROCESSOR_EVENT]: 'transaction' } }, - { - range: { - '@timestamp': { gte: start, lte: end, format: 'epoch_millis' } - } - } - ] - } - }, - aggs: TRANSACTION_GROUP_AGGREGATES - } - }; - - if (esFilterQuery) { - params.body.query.bool.filter.push(esFilterQuery); - } - - const response: SearchResponse = await client('search', params); - const buckets = get(response, 'aggregations.transactions.buckets', []); - - return prepareTransactionGroups({ buckets, start, end }); -} diff --git a/x-pack/plugins/apm/server/lib/transactions/get_top_transactions/index.ts b/x-pack/plugins/apm/server/lib/transactions/get_top_transactions/index.ts new file mode 100644 index 0000000000000..8b1fe99a0d3e7 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/transactions/get_top_transactions/index.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + PROCESSOR_EVENT, + SERVICE_NAME, + TRANSACTION_TYPE +} from 'x-pack/plugins/apm/common/constants'; +import { Setup } from 'x-pack/plugins/apm/server/lib/helpers/setup_request'; +import { getTransactionGroups } from '../../transaction_groups'; +import { ITransactionGroup } from '../../transaction_groups/transform'; + +export interface IOptions { + setup: Setup; + transactionType: string; + serviceName: string; +} + +export type TransactionListAPIResponse = ITransactionGroup[]; + +export async function getTopTransactions({ + setup, + transactionType, + serviceName +}: IOptions) { + const { start, end } = setup; + + const bodyQuery = { + bool: { + filter: [ + { term: { [SERVICE_NAME]: serviceName } }, + { term: { [TRANSACTION_TYPE]: transactionType } }, + { term: { [PROCESSOR_EVENT]: 'transaction' } }, + { + range: { + '@timestamp': { gte: start, lte: end, format: 'epoch_millis' } + } + } + ] + } + }; + + return getTransactionGroups(setup, bodyQuery); +} diff --git a/x-pack/plugins/apm/server/lib/transactions/get_transaction.ts b/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts similarity index 82% rename from x-pack/plugins/apm/server/lib/transactions/get_transaction.ts rename to x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts index 301e54d1f2a03..e88b17d4ef99c 100644 --- a/x-pack/plugins/apm/server/lib/transactions/get_transaction.ts +++ b/x-pack/plugins/apm/server/lib/transactions/get_transaction/index.ts @@ -4,21 +4,23 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SearchParams, SearchResponse } from 'elasticsearch'; +import { SearchParams } from 'elasticsearch'; import { oc } from 'ts-optchain'; import { Transaction } from 'x-pack/plugins/apm/typings/Transaction'; import { PROCESSOR_EVENT, TRACE_ID, TRANSACTION_ID -} from '../../../common/constants'; -import { Setup } from '../helpers/setup_request'; +} from '../../../../common/constants'; +import { Setup } from '../../helpers/setup_request'; + +export type TransactionAPIResponse = Transaction | null; export async function getTransaction( transactionId: string, traceId: string | undefined, setup: Setup -) { +): Promise { const { start, end, esFilterQuery, client, config } = setup; const params: SearchParams = { @@ -53,6 +55,6 @@ export async function getTransaction( params.body.query.bool.filter.push({ term: { [TRACE_ID]: traceId } }); } - const resp: SearchResponse = await client('search', params); + const resp = await client('search', params); return oc(resp).hits.hits[0]._source() || null; } diff --git a/x-pack/plugins/apm/server/lib/transactions/spans/get_spans.ts b/x-pack/plugins/apm/server/lib/transactions/spans/get_spans.ts index dc587dfd2f49b..e3c794c01a033 100644 --- a/x-pack/plugins/apm/server/lib/transactions/spans/get_spans.ts +++ b/x-pack/plugins/apm/server/lib/transactions/spans/get_spans.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SearchResponse } from 'elasticsearch'; import { Span } from 'x-pack/plugins/apm/typings/Span'; import { PROCESSOR_EVENT, @@ -14,11 +13,16 @@ import { } from '../../../../common/constants'; import { Setup } from '../../helpers/setup_request'; -export async function getSpans(transactionId: string, setup: Setup) { +export type SpanListAPIResponse = Span[]; + +export async function getSpans( + transactionId: string, + setup: Setup +): Promise { const { start, end, client, config } = setup; const params = { - index: config.get('apm_oss.spanIndices'), + index: config.get('apm_oss.spanIndices'), body: { size: 500, query: { @@ -50,6 +54,6 @@ export async function getSpans(transactionId: string, setup: Setup) { } }; - const resp: SearchResponse = await client('search', params); + const resp = await client('search', params); return resp.hits.hits.map(hit => hit._source); } diff --git a/x-pack/plugins/apm/server/routes/services.ts b/x-pack/plugins/apm/server/routes/services.ts index f45cd21a7e625..e5d225c739356 100644 --- a/x-pack/plugins/apm/server/routes/services.ts +++ b/x-pack/plugins/apm/server/routes/services.ts @@ -6,6 +6,11 @@ import Boom from 'boom'; import { Server } from 'hapi'; +import { + AgentName, + createApmTelementry, + storeApmTelemetry +} from '../lib/apm_telemetry'; import { withDefaultValidators } from '../lib/helpers/input_validation'; import { setupRequest } from '../lib/helpers/setup_request'; import { getService } from '../lib/services/get_service'; @@ -16,7 +21,6 @@ const pre = [{ method: setupRequest, assign: 'setup' }]; const defaultErrorHandler = (err: Error) => { // tslint:disable-next-line console.error(err.stack); - // @ts-ignore throw Boom.boomify(err, { statusCode: 400 }); }; @@ -30,9 +34,23 @@ export function initServicesApi(server: Server) { query: withDefaultValidators() } }, - handler: req => { + handler: async req => { const { setup } = req.pre; - return getServices(setup).catch(defaultErrorHandler); + + let serviceBucketList; + try { + serviceBucketList = await getServices(setup); + } catch (error) { + return defaultErrorHandler(error); + } + + // Store telemetry data derived from serviceBucketList + const apmTelemetry = createApmTelementry( + serviceBucketList.map(({ agentName }) => agentName as AgentName) + ); + storeApmTelemetry(server, apmTelemetry); + + return serviceBucketList; } }); diff --git a/x-pack/plugins/apm/server/routes/traces.ts b/x-pack/plugins/apm/server/routes/traces.ts index 7c291cf785128..f8a8a042286ca 100644 --- a/x-pack/plugins/apm/server/routes/traces.ts +++ b/x-pack/plugins/apm/server/routes/traces.ts @@ -16,7 +16,6 @@ const ROOT = '/api/apm/traces'; const defaultErrorHandler = (err: Error) => { // tslint:disable-next-line console.error(err.stack); - // @ts-ignore throw Boom.boomify(err, { statusCode: 400 }); }; diff --git a/x-pack/plugins/apm/server/routes/transactions.ts b/x-pack/plugins/apm/server/routes/transactions.ts index 01311f896ef8d..fa1104534c074 100644 --- a/x-pack/plugins/apm/server/routes/transactions.ts +++ b/x-pack/plugins/apm/server/routes/transactions.ts @@ -9,9 +9,8 @@ import { Server } from 'hapi'; import Joi from 'joi'; import { withDefaultValidators } from '../lib/helpers/input_validation'; import { setupRequest } from '../lib/helpers/setup_request'; -// @ts-ignore import { getTimeseriesData } from '../lib/transactions/charts/get_timeseries_data'; -import { getDistribution } from '../lib/transactions/distribution/get_distribution'; +import { getDistribution } from '../lib/transactions/distribution'; import { getTopTransactions } from '../lib/transactions/get_top_transactions'; import { getTransaction } from '../lib/transactions/get_transaction'; import { getSpans } from '../lib/transactions/spans/get_spans'; @@ -21,7 +20,6 @@ const ROOT = '/api/apm/services/{serviceName}/transactions'; const defaultErrorHandler = (err: Error) => { // tslint:disable-next-line console.error(err.stack); - // @ts-ignore throw Boom.boomify(err, { statusCode: 400 }); }; diff --git a/x-pack/plugins/apm/typings/APMDoc.ts b/x-pack/plugins/apm/typings/APMDoc.ts index dc8fffad9bc30..1ca520132ba61 100644 --- a/x-pack/plugins/apm/typings/APMDoc.ts +++ b/x-pack/plugins/apm/typings/APMDoc.ts @@ -14,6 +14,7 @@ export interface APMDocV1 { host: { name: string; }; + agent?: object; } export interface APMDocV2 extends APMDocV1 { @@ -46,6 +47,7 @@ export interface ContextService { name: string; version?: string; }; + [key: string]: unknown; } export interface Stackframe { @@ -70,7 +72,7 @@ export interface Stackframe { updated?: boolean; error?: string; }; - vars?: any; + vars?: unknown; orig?: { filename?: string; abs_path?: string; diff --git a/x-pack/plugins/apm/typings/Error.ts b/x-pack/plugins/apm/typings/Error.ts index a4bacfeff62ce..b422b1d801a80 100644 --- a/x-pack/plugins/apm/typings/Error.ts +++ b/x-pack/plugins/apm/typings/Error.ts @@ -31,7 +31,7 @@ export interface Error extends APMDocV1 { type?: string; code?: string; module?: string; - attributes?: any; + attributes?: unknown; handled?: boolean; stacktrace?: Stackframe[]; }; diff --git a/x-pack/plugins/apm/typings/Span.ts b/x-pack/plugins/apm/typings/Span.ts index b14a328a85988..5079d75c1d8a9 100644 --- a/x-pack/plugins/apm/typings/Span.ts +++ b/x-pack/plugins/apm/typings/Span.ts @@ -21,7 +21,7 @@ interface Processor { interface Context { db?: DbContext; service: ContextService; - [key: string]: any; + [key: string]: unknown; } export interface SpanV1 extends APMDocV1 { diff --git a/x-pack/plugins/apm/typings/Transaction.ts b/x-pack/plugins/apm/typings/Transaction.ts index 88ddcccff7de3..8498d47b00c01 100644 --- a/x-pack/plugins/apm/typings/Transaction.ts +++ b/x-pack/plugins/apm/typings/Transaction.ts @@ -21,19 +21,24 @@ interface ContextSystem { interface Context { process?: { pid: number; + [key: string]: unknown; }; service: ContextService; system?: ContextSystem; request: { url: { full: string; + [key: string]: string; }; method: string; + [key: string]: unknown; }; user?: { id: string; + username?: string; + email?: string; }; - [key: string]: any; + [key: string]: unknown; } interface Marks { diff --git a/x-pack/plugins/apm/typings/TransactionGroup.ts b/x-pack/plugins/apm/typings/TransactionGroup.ts deleted file mode 100644 index 3967d4d94d5cf..0000000000000 --- a/x-pack/plugins/apm/typings/TransactionGroup.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 { Transaction } from './Transaction'; - -export interface ITransactionGroup { - name: string; - sample: Transaction; - p95: number; - averageResponseTime: number; - transactionsPerMinute: number; - impact: number; -} diff --git a/x-pack/plugins/apm/typings/elasticsearch.ts b/x-pack/plugins/apm/typings/elasticsearch.ts index f3a536e8993ff..35b58795ed1ca 100644 --- a/x-pack/plugins/apm/typings/elasticsearch.ts +++ b/x-pack/plugins/apm/typings/elasticsearch.ts @@ -4,7 +4,23 @@ * you may not use this file except in compliance with the Elastic License. */ +import { SearchResponse } from 'elasticsearch'; + export interface TermsAggsBucket { key: string; doc_count: number; } + +type Omit = Pick>; +export type TopHits = Omit< + SearchResponse, + 'took' | 'timed_out' | '_shards' +>; + +declare module 'elasticsearch' { + // extending SearchResponse to be able to have typed aggregations + export interface AggregationSearchResponse + extends SearchResponse { + aggregations: U; + } +} diff --git a/x-pack/plugins/canvas/public/lib/create_handlers.js b/x-pack/plugins/apm/typings/lodash.mean.d.ts similarity index 72% rename from x-pack/plugins/canvas/public/lib/create_handlers.js rename to x-pack/plugins/apm/typings/lodash.mean.d.ts index 93247210eb291..c61f9adf9efd4 100644 --- a/x-pack/plugins/canvas/public/lib/create_handlers.js +++ b/x-pack/plugins/apm/typings/lodash.mean.d.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export function createHandlers(/*socket*/) { - return { - environment: 'client', - }; +declare module 'lodash.mean' { + function mean(numbers: number[]): number; + export = mean; } diff --git a/x-pack/plugins/apm/typings/numeral.d.ts b/x-pack/plugins/apm/typings/numeral.d.ts index d21595041f14d..f08f99f5ef11a 100644 --- a/x-pack/plugins/apm/typings/numeral.d.ts +++ b/x-pack/plugins/apm/typings/numeral.d.ts @@ -5,7 +5,7 @@ */ interface Numeral { - (value?: any): Numeral; + (value?: unknown): Numeral; format: (pattern: string) => string; } diff --git a/x-pack/plugins/apm/typings/waterfall.ts b/x-pack/plugins/apm/typings/waterfall.ts deleted file mode 100644 index d33cdcda65cc3..0000000000000 --- a/x-pack/plugins/apm/typings/waterfall.ts +++ /dev/null @@ -1,10 +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 { Span } from './Span'; -import { Transaction } from './Transaction'; - -export type WaterfallResponse = Array; diff --git a/x-pack/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts b/x-pack/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts index d987a18137116..e4b4e73afc18c 100644 --- a/x-pack/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts +++ b/x-pack/plugins/beats_management/public/lib/adapters/elasticsearch/rest.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +// @ts-ignore TODO type this +import { fromKueryExpression, toElasticsearchQuery } from '@kbn/es-query'; import { isEmpty } from 'lodash'; import { AutocompleteSuggestion, getAutocompleteProvider } from 'ui/autocomplete_providers'; -// @ts-ignore TODO type this -import { fromKueryExpression, toElasticsearchQuery } from 'ui/kuery'; import { RestAPIAdapter } from '../rest_api/adapter_types'; import { ElasticsearchAdapter } from './adapter_types'; diff --git a/x-pack/plugins/canvas/__tests__/fixtures/function_specs.js b/x-pack/plugins/canvas/__tests__/fixtures/function_specs.js index 433b5ec64b753..b03e5aaf5b78e 100644 --- a/x-pack/plugins/canvas/__tests__/fixtures/function_specs.js +++ b/x-pack/plugins/canvas/__tests__/fixtures/function_specs.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Fn } from '../../common/lib/fn'; +import { Fn } from '@kbn/interpreter/common'; import { functions as browserFns } from '../../canvas_plugin_src/functions/browser'; import { functions as commonFns } from '../../canvas_plugin_src/functions/common'; import { functions as serverFns } from '../../canvas_plugin_src/functions/server/src'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/as.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/as.js index c85cc9e0d5baf..48e4b1d8d8a49 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/as.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/as.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getType } from '../../../common/lib/get_type'; +import { getType } from '@kbn/interpreter/common'; export const asFn = () => ({ name: 'as', diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/clog.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/clog.js deleted file mode 100644 index db4cc4179762f..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/clog.js +++ /dev/null @@ -1,14 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const clog = () => ({ - name: 'clog', - help: 'Outputs the context to the console', - fn: context => { - console.log(context); //eslint-disable-line no-console - return context; - }, -}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/index.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/index.js index 410dbc60db952..f20c78bb1fa07 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/index.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/index.js @@ -11,7 +11,6 @@ import { asFn } from './as'; import { axisConfig } from './axisConfig'; import { compare } from './compare'; import { containerStyle } from './containerStyle'; -import { clog } from './clog'; import { context } from './context'; import { columns } from './columns'; import { csv } from './csv'; @@ -65,7 +64,6 @@ export const functions = [ any, asFn, axisConfig, - clog, columns, compare, containerStyle, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/mapColumn.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/mapColumn.js index db55780205296..825744858527f 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/mapColumn.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/mapColumn.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getType } from '../../../common/lib/get_type'; +import { getType } from '@kbn/interpreter/common'; export const mapColumn = () => ({ name: 'mapColumn', diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/get_flot_axis_config.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/get_flot_axis_config.js index 1a8ee7daf7370..6391b01c3ded6 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/get_flot_axis_config.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/plot/get_flot_axis_config.js @@ -5,7 +5,7 @@ */ import { get, map } from 'lodash'; -import { getType } from '../../../../common/lib/get_type'; +import { getType } from '@kbn/interpreter/common'; export const getFlotAxisConfig = (axis, argValue, { columns, ticks, font } = {}) => { if (!argValue || argValue.show === false) return { show: false }; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/staticColumn.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/staticColumn.js index 77580be49719a..4a5d06ac8c170 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/staticColumn.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/staticColumn.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getType } from '../../../common/lib/get_type'; +import { getType } from '@kbn/interpreter/common'; export const staticColumn = () => ({ name: 'staticColumn', diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/dropdown_filter/index.js b/x-pack/plugins/canvas/canvas_plugin_src/renderers/dropdown_filter/index.js index 863fa41a90be5..884981929a9ff 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/dropdown_filter/index.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/dropdown_filter/index.js @@ -7,7 +7,7 @@ import ReactDOM from 'react-dom'; import React from 'react'; import { get } from 'lodash'; -import { fromExpression, toExpression } from '../../../common/lib/ast'; +import { fromExpression, toExpression } from '@kbn/interpreter/common'; import { DropdownFilter } from './component'; export const dropdownFilter = () => ({ diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_filter/time_filter.js b/x-pack/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_filter/time_filter.js index d04a8f0b54d96..880afcd35f654 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_filter/time_filter.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/time_filter/components/time_filter/time_filter.js @@ -7,7 +7,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { get } from 'lodash'; -import { fromExpression } from '../../../../../common/lib/ast'; +import { fromExpression } from '@kbn/interpreter/common'; import { TimePicker } from '../time_picker'; import { TimePickerMini } from '../time_picker_mini'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/renderers/time_filter/index.js b/x-pack/plugins/canvas/canvas_plugin_src/renderers/time_filter/index.js index cdc4f563e1340..c74fc590cc3fe 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/renderers/time_filter/index.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/renderers/time_filter/index.js @@ -7,7 +7,7 @@ import ReactDOM from 'react-dom'; import React from 'react'; import { get, set } from 'lodash'; -import { fromExpression, toExpression } from '../../../common/lib/ast'; +import { fromExpression, toExpression } from '@kbn/interpreter/common'; import { TimeFilter } from './components/time_filter'; export const timeFilter = () => ({ diff --git a/x-pack/plugins/canvas/canvas_plugin_src/types/boolean.js b/x-pack/plugins/canvas/canvas_plugin_src/types/boolean.js deleted file mode 100644 index 697277a471fea..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/types/boolean.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const boolean = () => ({ - name: 'boolean', - from: { - null: () => false, - number: n => Boolean(n), - string: s => Boolean(s), - }, - to: { - render: value => { - const text = `${value}`; - return { - type: 'render', - as: 'text', - value: { text }, - }; - }, - datatable: value => ({ - type: 'datatable', - columns: [{ name: 'value', type: 'boolean' }], - rows: [{ value }], - }), - }, -}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/types/error.js b/x-pack/plugins/canvas/canvas_plugin_src/types/error.js deleted file mode 100644 index 51051c804db56..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/types/error.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const error = () => ({ - name: 'error', - to: { - render: input => { - const { error, info } = input; - return { - type: 'render', - as: 'error', - value: { - error, - info, - }, - }; - }, - }, -}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/types/filter.js b/x-pack/plugins/canvas/canvas_plugin_src/types/filter.js deleted file mode 100644 index 8627dd20bb89f..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/types/filter.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const filter = () => ({ - name: 'filter', - from: { - null: () => { - return { - type: 'filter', - // Any meta data you wish to pass along. - meta: {}, - // And filters. If you need an "or", create a filter type for it. - and: [], - }; - }, - }, -}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/types/image.js b/x-pack/plugins/canvas/canvas_plugin_src/types/image.js deleted file mode 100644 index f63d3f1b8b2aa..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/types/image.js +++ /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. - */ - -export const image = () => ({ - name: 'image', - to: { - render: input => { - return { - type: 'render', - as: 'image', - value: input, - }; - }, - }, -}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/types/index.js b/x-pack/plugins/canvas/canvas_plugin_src/types/index.js deleted file mode 100644 index 2e9a4fa02ef8e..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/types/index.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { boolean } from './boolean'; -import { datatable } from './datatable'; -import { error } from './error'; -import { filter } from './filter'; -import { image } from './image'; -import { nullType } from './null'; -import { number } from './number'; -import { pointseries } from './pointseries'; -import { render } from './render'; -import { shape } from './shape'; -import { string } from './string'; -import { style } from './style'; - -export const typeSpecs = [ - boolean, - datatable, - error, - filter, - image, - number, - nullType, - pointseries, - render, - shape, - string, - style, -]; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/types/number.js b/x-pack/plugins/canvas/canvas_plugin_src/types/number.js deleted file mode 100644 index 63ee587075fdd..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/types/number.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const number = () => ({ - name: 'number', - from: { - null: () => 0, - boolean: b => Number(b), - string: n => Number(n), - }, - to: { - render: value => { - const text = `${value}`; - return { - type: 'render', - as: 'text', - value: { text }, - }; - }, - datatable: value => ({ - type: 'datatable', - columns: [{ name: 'value', type: 'number' }], - rows: [{ value }], - }), - }, -}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/types/pointseries.js b/x-pack/plugins/canvas/canvas_plugin_src/types/pointseries.js deleted file mode 100644 index 1a00738620050..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/types/pointseries.js +++ /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. - */ - -export const pointseries = () => ({ - name: 'pointseries', - from: { - null: () => { - return { - type: 'pointseries', - rows: [], - columns: [], - }; - }, - }, - to: { - render: (pointseries, types) => { - const datatable = types.datatable.from(pointseries, types); - return { - type: 'render', - as: 'table', - value: { - datatable, - showHeader: true, - }, - }; - }, - }, -}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/types/register.js b/x-pack/plugins/canvas/canvas_plugin_src/types/register.js deleted file mode 100644 index e960dd0f6566a..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/types/register.js +++ /dev/null @@ -1,10 +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 'babel-polyfill'; -import { typeSpecs } from './index'; - -typeSpecs.forEach(canvas.register); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/types/render.js b/x-pack/plugins/canvas/canvas_plugin_src/types/render.js deleted file mode 100644 index 0f261f0398816..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/types/render.js +++ /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. - */ - -export const render = () => ({ - name: 'render', - from: { - '*': v => ({ - type: 'render', - as: 'debug', - value: v, - }), - }, -}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/types/shape.js b/x-pack/plugins/canvas/canvas_plugin_src/types/shape.js deleted file mode 100644 index 1b306b7b1c391..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/types/shape.js +++ /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. - */ - -export const shape = () => ({ - name: 'shape', - to: { - render: input => { - return { - type: 'render', - as: 'shape', - value: input, - }; - }, - }, -}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/types/string.js b/x-pack/plugins/canvas/canvas_plugin_src/types/string.js deleted file mode 100644 index c8d58aaaffbca..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/types/string.js +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const string = () => ({ - name: 'string', - from: { - null: () => '', - boolean: b => String(b), - number: n => String(n), - }, - to: { - render: text => { - return { - type: 'render', - as: 'text', - value: { text }, - }; - }, - datatable: value => ({ - type: 'datatable', - columns: [{ name: 'value', type: 'string' }], - rows: [{ value }], - }), - }, -}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/types/style.js b/x-pack/plugins/canvas/canvas_plugin_src/types/style.js deleted file mode 100644 index 62632c03231ad..0000000000000 --- a/x-pack/plugins/canvas/canvas_plugin_src/types/style.js +++ /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. - */ - -export const style = () => ({ - name: 'style', - from: { - null: () => { - return { - type: 'style', - spec: {}, - css: '', - }; - }, - }, -}); diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/datacolumn/index.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/datacolumn/index.js index 0540a14603460..d60b834931cb3 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/datacolumn/index.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/datacolumn/index.js @@ -9,8 +9,8 @@ import { compose, withPropsOnChange, withHandlers } from 'recompose'; import PropTypes from 'prop-types'; import { EuiSelect, EuiFlexItem, EuiFlexGroup } from '@elastic/eui'; import { sortBy } from 'lodash'; +import { getType } from '@kbn/interpreter/common'; import { createStatefulPropHoc } from '../../../../public/components/enhance/stateful_prop'; -import { getType } from '../../../../common/lib/get_type'; import { templateFromReactComponent } from '../../../../public/lib/template_from_react_component'; import { SimpleMathFunction } from './simple_math_function'; import { getFormObject } from './get_form_object'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js index d5540d050227f..5027dbbf3f566 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/image_upload/index.js @@ -16,10 +16,7 @@ import { EuiButton, EuiFieldText, } from '@elastic/eui'; - -// TODO: (clintandrewhall) This is a quick fix for #25342 -- we should figure out how to use the overall component. -import { Loading } from '../../../../public/components/loading/loading'; - +import { Loading } from '../../../../public/components/loading'; import { FileUpload } from '../../../../public/components/file_upload'; import { elasticOutline } from '../../../lib/elastic_outline'; import { resolveFromArgs } from '../../../../common/lib/resolve_dataurl'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette.js index e0f8e56df8b6f..2090b7d1dc585 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/palette.js @@ -7,8 +7,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import { get } from 'lodash'; +import { getType } from '@kbn/interpreter/common'; import { PalettePicker } from '../../../public/components/palette_picker'; -import { getType } from '../../../common/lib/get_type'; import { templateFromReactComponent } from '../../../public/lib/template_from_react_component'; const PaletteArgInput = ({ onValueChange, argValue, renderError }) => { diff --git a/x-pack/plugins/canvas/common/functions/to.js b/x-pack/plugins/canvas/common/functions/to.js index 6f15569c27a11..afc58b52b692c 100644 --- a/x-pack/plugins/canvas/common/functions/to.js +++ b/x-pack/plugins/canvas/common/functions/to.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { castProvider } from '../interpreter/cast'; +import { castProvider } from '@kbn/interpreter/common'; export const to = () => ({ name: 'to', diff --git a/x-pack/plugins/canvas/common/interpreter/create_error.js b/x-pack/plugins/canvas/common/interpreter/create_error.js deleted file mode 100644 index 5de9819330dbd..0000000000000 --- a/x-pack/plugins/canvas/common/interpreter/create_error.js +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const createError = err => ({ - type: 'error', - error: { - stack: process.env.NODE_ENV === 'production' ? undefined : err.stack, - message: typeof err === 'string' ? err : err.message, - }, -}); diff --git a/x-pack/plugins/canvas/common/lib/__tests__/arg.js b/x-pack/plugins/canvas/common/lib/__tests__/arg.js deleted file mode 100644 index f8badc67175ac..0000000000000 --- a/x-pack/plugins/canvas/common/lib/__tests__/arg.js +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from 'expect.js'; -import { Arg } from '../arg'; - -describe('Arg', () => { - it('sets required to false by default', () => { - const isOptional = new Arg({ - name: 'optional_me', - }); - expect(isOptional.required).to.equal(false); - - const isRequired = new Arg({ - name: 'require_me', - required: true, - }); - expect(isRequired.required).to.equal(true); - }); -}); diff --git a/x-pack/plugins/canvas/common/lib/__tests__/get_by_alias.js b/x-pack/plugins/canvas/common/lib/__tests__/get_by_alias.js deleted file mode 100644 index eaeeeade4cc59..0000000000000 --- a/x-pack/plugins/canvas/common/lib/__tests__/get_by_alias.js +++ /dev/null @@ -1,74 +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 'expect.js'; -import { getByAlias } from '../get_by_alias'; - -describe('getByAlias', () => { - const fnsObject = { - foo: { name: 'foo', aliases: ['f'] }, - bar: { name: 'bar', aliases: ['b'] }, - }; - - const fnsArray = [{ name: 'foo', aliases: ['f'] }, { name: 'bar', aliases: ['b'] }]; - - it('returns the function by name', () => { - expect(getByAlias(fnsObject, 'foo')).to.be(fnsObject.foo); - expect(getByAlias(fnsObject, 'bar')).to.be(fnsObject.bar); - expect(getByAlias(fnsArray, 'foo')).to.be(fnsArray[0]); - expect(getByAlias(fnsArray, 'bar')).to.be(fnsArray[1]); - }); - - it('returns the function by alias', () => { - expect(getByAlias(fnsObject, 'f')).to.be(fnsObject.foo); - expect(getByAlias(fnsObject, 'b')).to.be(fnsObject.bar); - expect(getByAlias(fnsArray, 'f')).to.be(fnsArray[0]); - expect(getByAlias(fnsArray, 'b')).to.be(fnsArray[1]); - }); - - it('returns the function by case-insensitive name', () => { - expect(getByAlias(fnsObject, 'FOO')).to.be(fnsObject.foo); - expect(getByAlias(fnsObject, 'BAR')).to.be(fnsObject.bar); - expect(getByAlias(fnsArray, 'FOO')).to.be(fnsArray[0]); - expect(getByAlias(fnsArray, 'BAR')).to.be(fnsArray[1]); - }); - - it('returns the function by case-insensitive alias', () => { - expect(getByAlias(fnsObject, 'F')).to.be(fnsObject.foo); - expect(getByAlias(fnsObject, 'B')).to.be(fnsObject.bar); - expect(getByAlias(fnsArray, 'F')).to.be(fnsArray[0]); - expect(getByAlias(fnsArray, 'B')).to.be(fnsArray[1]); - }); - - it('handles empty strings', () => { - const emptyStringFnsObject = { '': { name: '' } }; - const emptyStringAliasFnsObject = { foo: { name: 'foo', aliases: [''] } }; - expect(getByAlias(emptyStringFnsObject, '')).to.be(emptyStringFnsObject['']); - expect(getByAlias(emptyStringAliasFnsObject, '')).to.be(emptyStringAliasFnsObject.foo); - - const emptyStringFnsArray = [{ name: '' }]; - const emptyStringAliasFnsArray = [{ name: 'foo', aliases: [''] }]; - expect(getByAlias(emptyStringFnsArray, '')).to.be(emptyStringFnsArray[0]); - expect(getByAlias(emptyStringAliasFnsArray, '')).to.be(emptyStringAliasFnsArray[0]); - }); - - it('handles "undefined" strings', () => { - const undefinedFnsObject = { undefined: { name: 'undefined' } }; - const undefinedAliasFnsObject = { foo: { name: 'undefined', aliases: ['undefined'] } }; - expect(getByAlias(undefinedFnsObject, 'undefined')).to.be(undefinedFnsObject.undefined); - expect(getByAlias(undefinedAliasFnsObject, 'undefined')).to.be(undefinedAliasFnsObject.foo); - - const emptyStringFnsArray = [{ name: 'undefined' }]; - const emptyStringAliasFnsArray = [{ name: 'foo', aliases: ['undefined'] }]; - expect(getByAlias(emptyStringFnsArray, 'undefined')).to.be(emptyStringFnsArray[0]); - expect(getByAlias(emptyStringAliasFnsArray, 'undefined')).to.be(emptyStringAliasFnsArray[0]); - }); - - it('returns undefined if not found', () => { - expect(getByAlias(fnsObject, 'baz')).to.be(undefined); - expect(getByAlias(fnsArray, 'baz')).to.be(undefined); - }); -}); diff --git a/x-pack/plugins/canvas/common/lib/arg.js b/x-pack/plugins/canvas/common/lib/arg.js deleted file mode 100644 index 7713fcb342bc2..0000000000000 --- a/x-pack/plugins/canvas/common/lib/arg.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { includes } from 'lodash'; - -export function Arg(config) { - if (config.name === '_') throw Error('Arg names must not be _. Use it in aliases instead.'); - this.name = config.name; - this.required = config.required || false; - this.help = config.help || ''; - this.types = config.types || []; - this.default = config.default; - this.aliases = config.aliases || []; - this.multi = config.multi == null ? false : config.multi; - this.resolve = config.resolve == null ? true : config.resolve; - this.options = config.options || []; - this.accepts = type => { - if (!this.types.length) return true; - return includes(config.types, type); - }; -} diff --git a/x-pack/plugins/canvas/common/lib/autocomplete.js b/x-pack/plugins/canvas/common/lib/autocomplete.js index d87e199de4671..8be391aea884a 100644 --- a/x-pack/plugins/canvas/common/lib/autocomplete.js +++ b/x-pack/plugins/canvas/common/lib/autocomplete.js @@ -5,8 +5,7 @@ */ import { uniq } from 'lodash'; -import { parse } from './grammar'; -import { getByAlias } from './get_by_alias'; +import { parse, getByAlias } from '@kbn/interpreter/common'; const MARKER = 'CANVAS_SUGGESTION_MARKER'; diff --git a/x-pack/plugins/canvas/common/lib/get_by_alias.js b/x-pack/plugins/canvas/common/lib/get_by_alias.js deleted file mode 100644 index c9986a5024008..0000000000000 --- a/x-pack/plugins/canvas/common/lib/get_by_alias.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -/** - * This is used for looking up function/argument definitions. It looks through - * the given object/array for a case-insensitive match, which could be either the - * `name` itself, or something under the `aliases` property. - */ -export function getByAlias(specs, name) { - const lowerCaseName = name.toLowerCase(); - return Object.values(specs).find(({ name, aliases }) => { - if (name.toLowerCase() === lowerCaseName) return true; - return (aliases || []).some(alias => { - return alias.toLowerCase() === lowerCaseName; - }); - }); -} diff --git a/x-pack/plugins/canvas/common/lib/get_type.js b/x-pack/plugins/canvas/common/lib/get_type.js deleted file mode 100644 index 8d2b5a13cb283..0000000000000 --- a/x-pack/plugins/canvas/common/lib/get_type.js +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export function getType(node) { - if (node == null) return 'null'; - if (typeof node === 'object') { - if (!node.type) throw new Error('Objects must have a type propery'); - return node.type; - } - - return typeof node; -} diff --git a/x-pack/plugins/canvas/common/lib/index.js b/x-pack/plugins/canvas/common/lib/index.js index 5d56a5026590d..321a4abff44e0 100644 --- a/x-pack/plugins/canvas/common/lib/index.js +++ b/x-pack/plugins/canvas/common/lib/index.js @@ -5,8 +5,6 @@ */ export * from './datatable'; -export * from './arg'; -export * from './ast'; export * from './autocomplete'; export * from './constants'; export * from './dataurl'; @@ -14,15 +12,10 @@ export * from './errors'; export * from './expression_form_handlers'; export * from './fetch'; export * from './find_in_object'; -export * from './fn'; export * from './fonts'; -export * from './functions_registry'; -export * from './get_by_alias'; export * from './get_colors_from_palette'; export * from './get_field_type'; export * from './get_legend_config'; -export * from './get_type'; -export * from './grammar'; export * from './handlebars'; export * from './hex_to_rgb'; export * from './httpurl'; @@ -30,10 +23,6 @@ export * from './latest_change'; export * from './missing_asset'; export * from './palettes'; export * from './pivot_object_array'; -export * from './registry'; export * from './resolve_dataurl'; -export * from './serialize'; -export * from './type'; -export * from './types_registry'; export * from './unquote_string'; export * from './url'; diff --git a/x-pack/plugins/canvas/common/lib/serialize.js b/x-pack/plugins/canvas/common/lib/serialize.js deleted file mode 100644 index 0786f6f06b3a3..0000000000000 --- a/x-pack/plugins/canvas/common/lib/serialize.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { get, identity } from 'lodash'; -import { getType } from '../lib/get_type'; - -export function serializeProvider(types) { - return { - serialize: provider('serialize'), - deserialize: provider('deserialize'), - }; - - function provider(key) { - return context => { - const type = getType(context); - const typeDef = types[type]; - const fn = get(typeDef, key) || identity; - return fn(context); - }; - } -} diff --git a/x-pack/plugins/canvas/common/lib/types_registry.js b/x-pack/plugins/canvas/common/lib/types_registry.js deleted file mode 100644 index 3d2bb65e9fa0f..0000000000000 --- a/x-pack/plugins/canvas/common/lib/types_registry.js +++ /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 { Registry } from '../../common/lib/registry'; -import { Type } from '../../common/lib/type'; - -class TypesRegistry extends Registry { - wrapper(obj) { - return new Type(obj); - } -} - -export const typesRegistry = new TypesRegistry(); diff --git a/x-pack/plugins/canvas/index.js b/x-pack/plugins/canvas/index.js index b92e29341a14b..04c961bf23777 100644 --- a/x-pack/plugins/canvas/index.js +++ b/x-pack/plugins/canvas/index.js @@ -5,9 +5,11 @@ */ import { resolve } from 'path'; +import { pathsRegistry } from '@kbn/interpreter/common'; import init from './init'; import { mappings } from './server/mappings'; -import { CANVAS_APP } from './common/lib/constants'; +import { CANVAS_APP } from './common/lib'; +import { pluginPaths } from './plugin_paths'; export function canvas(kibana) { return new kibana.Plugin({ @@ -39,6 +41,9 @@ export function canvas(kibana) { }).default(); }, + preInit: () => { + pathsRegistry.registerAll(pluginPaths); + }, init, }); } diff --git a/x-pack/plugins/canvas/init.js b/x-pack/plugins/canvas/init.js index f4b60ba4b6560..3565db88ef92e 100644 --- a/x-pack/plugins/canvas/init.js +++ b/x-pack/plugins/canvas/init.js @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { functionsRegistry } from '@kbn/interpreter/common'; +import { populateServerRegistries } from '@kbn/interpreter/server'; import { routes } from './server/routes'; -import { functionsRegistry } from './common/lib'; import { commonFunctions } from './common/functions'; -import { populateServerRegistries } from './server/lib/server_registries'; import { registerCanvasUsageCollector } from './server/usage'; import { loadSampleData } from './server/sample_data'; diff --git a/x-pack/plugins/canvas/plugin_paths.js b/x-pack/plugins/canvas/plugin_paths.js new file mode 100644 index 0000000000000..9c9f5d1c49bde --- /dev/null +++ b/x-pack/plugins/canvas/plugin_paths.js @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { resolve } from 'path'; + +export const pluginPaths = { + serverFunctions: resolve(__dirname, 'canvas_plugin/functions/server'), + browserFunctions: resolve(__dirname, 'canvas_plugin/functions/browser'), + commonFunctions: resolve(__dirname, 'canvas_plugin/functions/common'), + elements: resolve(__dirname, 'canvas_plugin/elements'), + renderers: resolve(__dirname, 'canvas_plugin/renderers'), + interfaces: resolve(__dirname, 'canvas_plugin/interfaces'), + transformUIs: resolve(__dirname, 'canvas_plugin/uis/transforms'), + datasourceUIs: resolve(__dirname, 'canvas_plugin/uis/datasources'), + modelUIs: resolve(__dirname, 'canvas_plugin/uis/models'), + viewUIs: resolve(__dirname, 'canvas_plugin/uis/views'), + argumentUIs: resolve(__dirname, 'canvas_plugin/uis/arguments'), +}; diff --git a/x-pack/plugins/canvas/public/components/app/index.js b/x-pack/plugins/canvas/public/components/app/index.js index 5f633169604d6..f5fc65e029f36 100644 --- a/x-pack/plugins/canvas/public/components/app/index.js +++ b/x-pack/plugins/canvas/public/components/app/index.js @@ -4,15 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ +import { + createSocket, + initializeInterpreter, + populateBrowserRegistries, +} from '@kbn/interpreter/public'; import { connect } from 'react-redux'; import { compose, withProps } from 'recompose'; -import { createSocket } from '../../socket'; -import { initialize as initializeInterpreter } from '../../lib/interpreter'; -import { populateBrowserRegistries } from '../../lib/browser_registries'; import { getAppReady, getBasePath } from '../../state/selectors/app'; import { appReady, appError } from '../../state/actions/app'; -import { trackRouteChange } from './track_route_change'; +import { loadPrivateBrowserFunctions } from '../../lib/load_private_browser_functions'; +import { elementsRegistry } from '../../lib/elements_registry'; +import { renderFunctionsRegistry } from '../../lib/render_functions_registry'; +import { + argTypeRegistry, + datasourceRegistry, + modelRegistry, + transformRegistry, + viewRegistry, +} from '../../expression_types'; import { App as Component } from './app'; +import { trackRouteChange } from './track_route_change'; const mapStateToProps = state => { // appReady could be an error object @@ -24,13 +36,24 @@ const mapStateToProps = state => { }; }; +const types = { + elements: elementsRegistry, + renderers: renderFunctionsRegistry, + transformUIs: transformRegistry, + datasourceUIs: datasourceRegistry, + modelUIs: modelRegistry, + viewUIs: viewRegistry, + argumentUIs: argTypeRegistry, +}; + const mapDispatchToProps = dispatch => ({ // TODO: the correct socket path should come from upstream, using the constant here is not ideal setAppReady: basePath => async () => { try { // initialize the socket and interpreter await createSocket(basePath); - await populateBrowserRegistries(); + loadPrivateBrowserFunctions(); + await populateBrowserRegistries(types, basePath); await initializeInterpreter(); // set app state to ready diff --git a/x-pack/plugins/canvas/public/components/arg_form/advanced_failure.js b/x-pack/plugins/canvas/public/components/arg_form/advanced_failure.js index 2bd779de759d9..074314f4d2456 100644 --- a/x-pack/plugins/canvas/public/components/arg_form/advanced_failure.js +++ b/x-pack/plugins/canvas/public/components/arg_form/advanced_failure.js @@ -8,8 +8,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import { compose, withProps, withPropsOnChange } from 'recompose'; import { EuiForm, EuiTextArea, EuiButton, EuiButtonEmpty, EuiFormRow } from '@elastic/eui'; +import { fromExpression, toExpression } from '@kbn/interpreter/common'; import { createStatefulPropHoc } from '../../components/enhance/stateful_prop'; -import { fromExpression, toExpression } from '../../../common/lib/ast'; export const AdvancedFailureComponent = props => { const { diff --git a/x-pack/plugins/canvas/public/components/datasource/datasource_preview/index.js b/x-pack/plugins/canvas/public/components/datasource/datasource_preview/index.js index 8b21c38a5f6f7..7cb5a6f3c54fc 100644 --- a/x-pack/plugins/canvas/public/components/datasource/datasource_preview/index.js +++ b/x-pack/plugins/canvas/public/components/datasource/datasource_preview/index.js @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { interpretAst } from '@kbn/interpreter/public'; import { pure, compose, lifecycle, withState, branch, renderComponent } from 'recompose'; import { PropTypes } from 'prop-types'; import { Loading } from '../../loading'; -import { interpretAst } from '../../../lib/interpreter'; import { DatasourcePreview as Component } from './datasource_preview'; export const DatasourcePreview = compose( diff --git a/x-pack/plugins/canvas/public/components/element_content/element_content.js b/x-pack/plugins/canvas/public/components/element_content/element_content.js index 8eba2aa2ba438..cf885b347aad0 100644 --- a/x-pack/plugins/canvas/public/components/element_content/element_content.js +++ b/x-pack/plugins/canvas/public/components/element_content/element_content.js @@ -8,7 +8,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { pure, compose, branch, renderComponent } from 'recompose'; import Style from 'style-it'; -import { getType } from '../../../common/lib/get_type'; +import { getType } from '@kbn/interpreter/common'; import { Loading } from '../loading'; import { RenderWithFn } from '../render_with_fn'; import { ElementShareContainer } from '../element_share_container'; @@ -23,8 +23,7 @@ const branches = [ // no renderable or renderable config value, render loading branch(({ renderable, state }) => { return !state || !renderable; - }, renderComponent(Loading)), - + }, renderComponent(({ backgroundColor }) => )), // renderable is available, but no matching element is found, render invalid branch(({ renderable, renderFunction }) => { return renderable && getType(renderable) !== 'render' && !renderFunction; @@ -52,6 +51,7 @@ export const ElementContent = compose( // TODO: 'canvas__element' was added for BWC, It can be removed after a while className={'canvas__element canvasElement'} style={{ ...renderable.containerStyle, ...size }} + data-test-subj="canvasWorkpadPageElementContent" > ({ + backgroundColor: getPageById(state, getSelectedPage(state)).style.background, +}); + export const ElementContent = compose( + connect(mapStateToProps), withProps(({ renderable }) => ({ renderFunction: renderFunctionsRegistry.get(get(renderable, 'as')), })) diff --git a/x-pack/plugins/canvas/public/components/expression/index.js b/x-pack/plugins/canvas/public/components/expression/index.js index 18690529d4e80..cb4cc251fbbe1 100644 --- a/x-pack/plugins/canvas/public/components/expression/index.js +++ b/x-pack/plugins/canvas/public/components/expression/index.js @@ -15,9 +15,9 @@ import { branch, renderComponent, } from 'recompose'; +import { fromExpression } from '@kbn/interpreter/common'; import { getSelectedPage, getSelectedElement } from '../../state/selectors/workpad'; import { setExpression, flushContext } from '../../state/actions/elements'; -import { fromExpression } from '../../../common/lib/ast'; import { getFunctionDefinitions } from '../../lib/function_definitions'; import { getWindow } from '../../lib/get_window'; import { ElementNotSelected } from './element_not_selected'; diff --git a/x-pack/plugins/canvas/public/components/function_form_list/index.js b/x-pack/plugins/canvas/public/components/function_form_list/index.js index 8b6702d94340f..7f60a43835eba 100644 --- a/x-pack/plugins/canvas/public/components/function_form_list/index.js +++ b/x-pack/plugins/canvas/public/components/function_form_list/index.js @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ +import { interpretAst } from '@kbn/interpreter/public'; import { compose, withProps } from 'recompose'; import { get } from 'lodash'; +import { toExpression } from '@kbn/interpreter/common'; import { modelRegistry, viewRegistry, transformRegistry } from '../../expression_types'; -import { interpretAst } from '../../lib/interpreter'; -import { toExpression } from '../../../common/lib/ast'; import { FunctionFormList as Component } from './function_form_list'; function normalizeContext(chain) { diff --git a/x-pack/plugins/canvas/public/components/loading/index.js b/x-pack/plugins/canvas/public/components/loading/index.js index 9216cb55d83c2..81fedf3287184 100644 --- a/x-pack/plugins/canvas/public/components/loading/index.js +++ b/x-pack/plugins/canvas/public/components/loading/index.js @@ -4,12 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { connect } from 'react-redux'; -import { getSelectedPage, getPageById } from '../../state/selectors/workpad'; +import { pure } from 'recompose'; import { Loading as Component } from './loading'; -const mapStateToProps = state => ({ - backgroundColor: getPageById(state, getSelectedPage(state)).style.background, -}); - -export const Loading = connect(mapStateToProps)(Component); +export const Loading = pure(Component); diff --git a/x-pack/plugins/canvas/public/components/loading/loading.js b/x-pack/plugins/canvas/public/components/loading/loading.js index 6b108a7606e8a..7a4b2083d7e4c 100644 --- a/x-pack/plugins/canvas/public/components/loading/loading.js +++ b/x-pack/plugins/canvas/public/components/loading/loading.js @@ -41,11 +41,12 @@ export const Loading = ({ animated, text, backgroundColor }) => { Loading.propTypes = { animated: PropTypes.bool, - text: PropTypes.string, backgroundColor: PropTypes.string, + text: PropTypes.string, }; Loading.defaultProps = { animated: false, + backgroundColor: '#000000', text: '', }; diff --git a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.js b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.js index af69558f1d7ed..90e10103f3c68 100644 --- a/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.js +++ b/x-pack/plugins/canvas/public/components/workpad_loader/workpad_loader.js @@ -179,6 +179,7 @@ export class WorkpadLoader extends React.PureComponent { return ( diff --git a/x-pack/plugins/canvas/public/components/workpad_page/event_handlers.js b/x-pack/plugins/canvas/public/components/workpad_page/event_handlers.js index b6aa9c8da6328..6d95194e7af61 100644 --- a/x-pack/plugins/canvas/public/components/workpad_page/event_handlers.js +++ b/x-pack/plugins/canvas/public/components/workpad_page/event_handlers.js @@ -6,10 +6,11 @@ import { withHandlers } from 'recompose'; -const ancestorElement = (element, className) => { +const ancestorElement = element => { if (!element) return element; - do if (element.classList.contains(className)) return element; - while ((element = element.parentElement)); + // IE11 has no classList on SVG elements, but we're not interested in SVG elements + do if (element.classList && element.classList.contains('canvasPage')) return element; + while ((element = element.parentElement || element.parentNode)); // no IE11 SVG parentElement }; const localMousePosition = (box, clientX, clientY) => { @@ -27,47 +28,47 @@ const resetHandler = () => { const setupHandler = (commit, target) => { // Ancestor has to be identified on setup, rather than 1st interaction, otherwise events may be triggered on // DOM elements that had been removed: kibana-canvas github issue #1093 - const canvasPage = ancestorElement(target, 'canvasPage'); + const canvasPage = ancestorElement(target); if (!canvasPage) return; const canvasOrigin = canvasPage.getBoundingClientRect(); - window.onmousemove = ({ clientX, clientY, altKey, metaKey, shiftKey }) => { + window.onmousemove = ({ clientX, clientY, altKey, metaKey, shiftKey, ctrlKey }) => { const { x, y } = localMousePosition(canvasOrigin, clientX, clientY); - commit('cursorPosition', { x, y, altKey, metaKey, shiftKey }); + commit('cursorPosition', { x, y, altKey, metaKey, shiftKey, ctrlKey }); }; window.onmouseup = e => { e.stopPropagation(); - const { clientX, clientY, altKey, metaKey, shiftKey } = e; + const { clientX, clientY, altKey, metaKey, shiftKey, ctrlKey } = e; const { x, y } = localMousePosition(canvasOrigin, clientX, clientY); - commit('mouseEvent', { event: 'mouseUp', x, y, altKey, metaKey, shiftKey }); + commit('mouseEvent', { event: 'mouseUp', x, y, altKey, metaKey, shiftKey, ctrlKey }); resetHandler(); }; }; const handleMouseMove = ( commit, - { target, clientX, clientY, altKey, metaKey, shiftKey }, + { target, clientX, clientY, altKey, metaKey, shiftKey, ctrlKey }, isEditable ) => { // mouse move must be handled even before an initial click if (!window.onmousemove && isEditable) { const { x, y } = localMousePosition(target, clientX, clientY); setupHandler(commit, target); - commit('cursorPosition', { x, y, altKey, metaKey, shiftKey }); + commit('cursorPosition', { x, y, altKey, metaKey, shiftKey, ctrlKey }); } }; const handleMouseDown = (commit, e, isEditable) => { e.stopPropagation(); - const { target, clientX, clientY, button, altKey, metaKey, shiftKey } = e; + const { target, clientX, clientY, button, altKey, metaKey, shiftKey, ctrlKey } = e; if (button !== 0 || !isEditable) { resetHandler(); return; // left-click and edit mode only } - const ancestor = ancestorElement(target, 'canvasPage'); + const ancestor = ancestorElement(target); if (!ancestor) return; const { x, y } = localMousePosition(ancestor, clientX, clientY); setupHandler(commit, ancestor); - commit('mouseEvent', { event: 'mouseDown', x, y, altKey, metaKey, shiftKey }); + commit('mouseEvent', { event: 'mouseDown', x, y, altKey, metaKey, shiftKey, ctrlKey }); }; const keyCode = key => (key === 'Meta' ? 'MetaLeft' : 'Key' + key.toUpperCase()); @@ -96,6 +97,8 @@ const isNotTextInput = ({ tagName, type }) => { } }; +const modifierKey = key => ['KeyALT', 'KeyCONTROL'].indexOf(keyCode(key)) > -1; + const handleKeyDown = (commit, e, isEditable, remove) => { const { key, target } = e; @@ -103,7 +106,7 @@ const handleKeyDown = (commit, e, isEditable, remove) => { if (isNotTextInput(target) && (key === 'Backspace' || key === 'Delete')) { e.preventDefault(); remove(); - } else { + } else if (!modifierKey(key)) { commit('keyboardEvent', { event: 'keyDown', code: keyCode(key), // convert to standard event code @@ -113,7 +116,7 @@ const handleKeyDown = (commit, e, isEditable, remove) => { }; const handleKeyUp = (commit, { key }, isEditable) => { - if (isEditable) { + if (isEditable && !modifierKey(key)) { commit('keyboardEvent', { event: 'keyUp', code: keyCode(key), // convert to standard event code diff --git a/x-pack/plugins/canvas/public/components/workpad_page/workpad_page.js b/x-pack/plugins/canvas/public/components/workpad_page/workpad_page.js index fc46820aca56f..35ec5db959d76 100644 --- a/x-pack/plugins/canvas/public/components/workpad_page/workpad_page.js +++ b/x-pack/plugins/canvas/public/components/workpad_page/workpad_page.js @@ -72,6 +72,7 @@ export class WorkpadPage extends PureComponent {
({ name: 'filters', diff --git a/x-pack/plugins/canvas/public/lib/aeroelastic/gestures.js b/x-pack/plugins/canvas/public/lib/aeroelastic/gestures.js index 825fabb5d0350..e419380684157 100644 --- a/x-pack/plugins/canvas/public/lib/aeroelastic/gestures.js +++ b/x-pack/plugins/canvas/public/lib/aeroelastic/gestures.js @@ -6,6 +6,18 @@ const { select, selectReduce } = require('./state'); +// Only needed to shuffle some modifier keys for Apple keyboards as per vector editing software conventions, +// so it's OK that user agent strings are not reliable; in case it's spoofed, it'll just work with a slightly +// different modifier key map (also, there aren't a lot of alternatives for OS / hw / keyboard detection). +// It shouldn't fail in testing environments (node.js) either, where it can just return false, no need for +// actually getting the OS on the server side. +const appleKeyboard = Boolean( + window && + window.navigator && + window.navigator.userAgent && + window.navigator.userAgent.match('Macintosh|iPhone|iPad') +); + /** * Selectors directly from a state object * @@ -27,81 +39,18 @@ const mouseButtonEvent = select(action => (action.type === 'mouseEvent' ? action primaryUpdate ); -const keyboardEvent = select(action => (action.type === 'keyboardEvent' ? action.payload : null))( - primaryUpdate -); - -const keyInfoFromMouseEvents = select( - ({ type, payload: { altKey, metaKey, shiftKey } }) => - type === 'cursorPosition' || type === 'mouseEvent' ? { altKey, metaKey, shiftKey } : null +const keyFromMouse = select( + ({ type, payload: { altKey, metaKey, shiftKey, ctrlKey } }) => + type === 'cursorPosition' || type === 'mouseEvent' ? { altKey, metaKey, shiftKey, ctrlKey } : {} )(primaryUpdate); -const altTest = key => key.slice(0, 3).toLowerCase() === 'alt' || key === 'KeyALT'; -const metaTest = key => key.slice(0, 4).toLowerCase() === 'meta'; -const shiftTest = key => key === 'KeySHIFT' || key.slice(0, 5) === 'Shift'; -const deadKey1 = 'KeyDEAD'; -const deadKey2 = 'Key†'; - -// Key states (up vs down) from keyboard events are trivially only captured if there's a keyboard event, and that only -// happens if the user is interacting with the browser, and specifically, with the DOM subset that captures the keyboard -// event. It's also clear that all keys, and importantly, modifier keys (alt, meta etc.) can alter state while the user -// is not sending keyboard DOM events to the browser, eg. while using another tab or application. Similarly, an alt-tab -// switch away from the browser will cause the registration of an `Alt down`, but not an `Alt up`, because that happens -// in the switched-to application (https://github.com/elastic/kibana-canvas/issues/901). -// -// The solution is to also harvest modifier key (and in the future, maybe other key) statuses from mouse events, as these -// modifier keys typically alter behavior while a pointer gesture is going on, in this case now, relaxing or tightening -// snapping behavior. So we simply toggle the current key set up/down status (`lookup`) opportunistically. -// -// This function destructively modifies lookup, but could be made to work on immutable structures in the future. -const updateKeyLookupFromMouseEvent = (lookup, keyInfoFromMouseEvent) => { - Object.entries(keyInfoFromMouseEvent).forEach(([key, value]) => { - if (metaTest(key)) { - if (value) lookup.meta = true; - else delete lookup.meta; - } - if (altTest(key)) { - if (value) lookup.alt = true; - else delete lookup.alt; - } - if (shiftTest(key)) { - if (value) lookup.shift = true; - else delete lookup.shift; - } - }); - return lookup; -}; - -const pressedKeys = selectReduce((prevLookup, next, keyInfoFromMouseEvent) => { - const lookup = keyInfoFromMouseEvent - ? updateKeyLookupFromMouseEvent(prevLookup, keyInfoFromMouseEvent) - : prevLookup; - // these weird things get in when we alt-tab (or similar) etc. away and get back later: - delete lookup[deadKey1]; - delete lookup[deadKey2]; - if (!next) return { ...lookup }; - - let code = next.code; - if (altTest(next.code)) code = 'alt'; +const metaHeld = select(appleKeyboard ? e => e.metaKey : e => e.altKey)(keyFromMouse); +const optionHeld = select(appleKeyboard ? e => e.altKey : e => e.ctrlKey)(keyFromMouse); +const shiftHeld = select(e => e.shiftKey)(keyFromMouse); - if (metaTest(next.code)) code = 'meta'; - - if (shiftTest(next.code)) code = 'shift'; - - if (next.event === 'keyDown') { - return { ...lookup, [code]: true }; - } else { - /*eslint no-unused-vars: ["error", { "ignoreRestSiblings": true }]*/ - const { [code]: ignore, ...rest } = lookup; - return rest; - } -}, {})(keyboardEvent, keyInfoFromMouseEvents); - -const keyUp = select(keys => Object.keys(keys).length === 0)(pressedKeys); - -const metaHeld = select(lookup => Boolean(lookup.meta))(pressedKeys); -const optionHeld = select(lookup => Boolean(lookup.alt))(pressedKeys); -const shiftHeld = select(lookup => Boolean(lookup.shift))(pressedKeys); +// retaining this for now to avoid removing dependent inactive code `keyTransformGesture` from layout.js +// todo remove this, and `keyTransformGesture` from layout.js and do accessibility outside the layout engine +const pressedKeys = () => ({}); const cursorPosition = selectReduce((previous, position) => position || previous, { x: 0, y: 0 })( rawCursorPosition @@ -122,18 +71,7 @@ const mouseIsDown = selectReduce( false )(mouseButtonEvent); -const gestureEnd = selectReduce( - (prev, keyUp, mouseIsDown) => { - const inAction = !keyUp || mouseIsDown; - const ended = !inAction && prev.inAction; - return { ended, inAction }; - }, - { - ended: false, - inAction: false, - }, - d => d.ended -)(keyUp, mouseIsDown); +const gestureEnd = select(next => next && next.event === 'mouseUp')(mouseButtonEvent); /** * mouseButtonStateTransitions diff --git a/x-pack/plugins/canvas/public/lib/arg_helpers.js b/x-pack/plugins/canvas/public/lib/arg_helpers.js index e53e26b62dd15..5640a218170e8 100644 --- a/x-pack/plugins/canvas/public/lib/arg_helpers.js +++ b/x-pack/plugins/canvas/public/lib/arg_helpers.js @@ -5,7 +5,7 @@ */ import { includes } from 'lodash'; -import { getType } from '../../common/lib/get_type'; +import { getType } from '@kbn/interpreter/common'; /* diff --git a/x-pack/plugins/canvas/public/lib/browser_registries.js b/x-pack/plugins/canvas/public/lib/browser_registries.js deleted file mode 100644 index efceec04d6dce..0000000000000 --- a/x-pack/plugins/canvas/public/lib/browser_registries.js +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import chrome from 'ui/chrome'; -import $script from 'scriptjs'; -import { typesRegistry } from '../../common/lib/types_registry'; -import { - argTypeRegistry, - datasourceRegistry, - transformRegistry, - modelRegistry, - viewRegistry, -} from '../expression_types'; -import { elementsRegistry } from './elements_registry'; -import { renderFunctionsRegistry } from './render_functions_registry'; -import { functionsRegistry as browserFunctions } from './functions_registry'; -import { loadPrivateBrowserFunctions } from './load_private_browser_functions'; - -const registries = { - browserFunctions: browserFunctions, - commonFunctions: browserFunctions, - elements: elementsRegistry, - types: typesRegistry, - renderers: renderFunctionsRegistry, - transformUIs: transformRegistry, - datasourceUIs: datasourceRegistry, - modelUIs: modelRegistry, - viewUIs: viewRegistry, - argumentUIs: argTypeRegistry, -}; - -let resolve = null; -let called = false; - -const populatePromise = new Promise(_resolve => { - resolve = _resolve; -}); - -export const getBrowserRegistries = () => { - return populatePromise; -}; - -export const populateBrowserRegistries = () => { - if (called) throw new Error('function should only be called once per process'); - called = true; - - // loadPrivateBrowserFunctions is sync. No biggie. - loadPrivateBrowserFunctions(); - - const remainingTypes = Object.keys(registries); - const populatedTypes = {}; - - function loadType() { - const type = remainingTypes.pop(); - window.canvas = window.canvas || {}; - window.canvas.register = d => registries[type].register(d); - - // Load plugins one at a time because each needs a different loader function - // $script will only load each of these once, we so can call this as many times as we need? - const pluginPath = chrome.addBasePath(`/api/canvas/plugins?type=${type}`); - $script(pluginPath, () => { - populatedTypes[type] = registries[type]; - - if (remainingTypes.length) loadType(); - else resolve(populatedTypes); - }); - } - - if (remainingTypes.length) loadType(); - return populatePromise; -}; diff --git a/x-pack/plugins/canvas/public/lib/elements_registry.js b/x-pack/plugins/canvas/public/lib/elements_registry.js index 898fba183c9f5..5f91377be2f48 100644 --- a/x-pack/plugins/canvas/public/lib/elements_registry.js +++ b/x-pack/plugins/canvas/public/lib/elements_registry.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Registry } from '../../common/lib/registry'; +import { Registry } from '@kbn/interpreter/common'; import { Element } from './element'; class ElementsRegistry extends Registry { diff --git a/x-pack/plugins/canvas/public/lib/function_definitions.js b/x-pack/plugins/canvas/public/lib/function_definitions.js index c4bc16a4c94c3..40b122f2f6a03 100644 --- a/x-pack/plugins/canvas/public/lib/function_definitions.js +++ b/x-pack/plugins/canvas/public/lib/function_definitions.js @@ -5,8 +5,8 @@ */ import uniqBy from 'lodash.uniqby'; +import { getBrowserRegistries } from '@kbn/interpreter/public'; import { getServerFunctions } from '../state/selectors/app'; -import { getBrowserRegistries } from './browser_registries'; export async function getFunctionDefinitions(state) { const { browserFunctions } = await getBrowserRegistries(); diff --git a/x-pack/plugins/canvas/public/lib/functions_registry.js b/x-pack/plugins/canvas/public/lib/functions_registry.js index 3cc084d8ca66e..f90fe453e9a1c 100644 --- a/x-pack/plugins/canvas/public/lib/functions_registry.js +++ b/x-pack/plugins/canvas/public/lib/functions_registry.js @@ -5,4 +5,4 @@ */ // export the common registry here, so it's available in plugin public code -export { functionsRegistry } from '../../common/lib/functions_registry'; +export { functionsRegistry } from '@kbn/interpreter/common'; diff --git a/x-pack/plugins/canvas/public/lib/history_provider.js b/x-pack/plugins/canvas/public/lib/history_provider.js index 9b7f907ceeedf..65a5acc78526d 100644 --- a/x-pack/plugins/canvas/public/lib/history_provider.js +++ b/x-pack/plugins/canvas/public/lib/history_provider.js @@ -4,11 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import chrome from 'ui/chrome'; import lzString from 'lz-string'; -import { createBrowserHistory, createMemoryHistory, parsePath, createPath } from 'history'; -import { get } from 'lodash'; -import { APP_ROUTE } from '../../common/lib/constants'; +import { createMemoryHistory, parsePath, createPath } from 'history'; +import createHashStateHistory from 'history-extra'; import { getWindow } from './get_window'; function wrapHistoryInstance(history) { @@ -132,17 +130,7 @@ const instances = new WeakMap(); const getHistoryInstance = win => { // if no window object, use memory module if (typeof win === 'undefined' || !win.history) return createMemoryHistory(); - - const basePath = chrome.getBasePath(); - const basename = `${basePath}${APP_ROUTE}#/`; - - // hacky fix for initial page load so basename matches with the hash - if (win.location.hash === '') win.history.replaceState({}, '', `${basename}`); - - // if window object, create browser instance - return createBrowserHistory({ - basename, - }); + return createHashStateHistory(); }; export const historyProvider = (win = getWindow()) => { @@ -150,11 +138,6 @@ export const historyProvider = (win = getWindow()) => { const instance = instances.get(win); if (instance) return instance; - // temporary fix for search params before the hash; remove them via location redirect - // they can't be preserved given this upstream issue https://github.com/ReactTraining/history/issues/564 - if (get(win, 'location.search', '').length > 0) - win.location = `${chrome.getBasePath()}${APP_ROUTE}${win.location.hash}`; - // create and cache wrapped history instance const historyInstance = getHistoryInstance(win); const wrappedInstance = wrapHistoryInstance(historyInstance); diff --git a/x-pack/plugins/canvas/public/lib/parse_single_function_chain.js b/x-pack/plugins/canvas/public/lib/parse_single_function_chain.js index f8eec880af624..f9a560f2cfb12 100644 --- a/x-pack/plugins/canvas/public/lib/parse_single_function_chain.js +++ b/x-pack/plugins/canvas/public/lib/parse_single_function_chain.js @@ -5,7 +5,7 @@ */ import { get, mapValues, map } from 'lodash'; -import { fromExpression } from '../../common/lib/ast'; +import { fromExpression } from '@kbn/interpreter/common'; export function parseSingleFunctionChain(filterString) { const ast = fromExpression(filterString); diff --git a/x-pack/plugins/canvas/public/lib/render_functions_registry.js b/x-pack/plugins/canvas/public/lib/render_functions_registry.js index 3d040047aeb9a..0a4b09cd17ca2 100644 --- a/x-pack/plugins/canvas/public/lib/render_functions_registry.js +++ b/x-pack/plugins/canvas/public/lib/render_functions_registry.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Registry } from '../../common/lib/registry'; +import { Registry } from '@kbn/interpreter/common'; import { RenderFunction } from './render_function'; class RenderFunctionsRegistry extends Registry { diff --git a/x-pack/plugins/canvas/public/lib/run_interpreter.js b/x-pack/plugins/canvas/public/lib/run_interpreter.js index cc0d9a7544786..3c193130c1fe1 100644 --- a/x-pack/plugins/canvas/public/lib/run_interpreter.js +++ b/x-pack/plugins/canvas/public/lib/run_interpreter.js @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { fromExpression } from '../../common/lib/ast'; -import { getType } from '../../common/lib/get_type'; -import { interpretAst } from './interpreter'; +import { interpretAst } from '@kbn/interpreter/public'; +import { fromExpression, getType } from '@kbn/interpreter/common'; import { notify } from './notify'; /** diff --git a/x-pack/plugins/canvas/public/lib/transitions_registry.js b/x-pack/plugins/canvas/public/lib/transitions_registry.js index 8d2e421b8233c..e6418a56e100b 100644 --- a/x-pack/plugins/canvas/public/lib/transitions_registry.js +++ b/x-pack/plugins/canvas/public/lib/transitions_registry.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Registry } from '../../common/lib/registry'; +import { Registry } from '@kbn/interpreter/common'; import { Transition } from '../transitions/transition'; class TransitionsRegistry extends Registry { diff --git a/x-pack/plugins/canvas/public/lib/types_registry.js b/x-pack/plugins/canvas/public/lib/types_registry.js index c1f13b1ae4612..292e1547f482f 100644 --- a/x-pack/plugins/canvas/public/lib/types_registry.js +++ b/x-pack/plugins/canvas/public/lib/types_registry.js @@ -5,4 +5,4 @@ */ // export the common registry here, so it's available in plugin public code -export { typesRegistry } from '../../common/lib/types_registry'; +export { typesRegistry } from '@kbn/interpreter/common'; diff --git a/x-pack/plugins/canvas/public/state/actions/elements.js b/x-pack/plugins/canvas/public/state/actions/elements.js index fb82de32fc0ef..5224f1228417a 100644 --- a/x-pack/plugins/canvas/public/state/actions/elements.js +++ b/x-pack/plugins/canvas/public/state/actions/elements.js @@ -4,17 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ +import { interpretAst } from '@kbn/interpreter/public'; import { createAction } from 'redux-actions'; import { createThunk } from 'redux-thunks'; import { set, del } from 'object-path-immutable'; import { get, pick, cloneDeep, without } from 'lodash'; +import { toExpression, safeElementFromExpression } from '@kbn/interpreter/common'; import { getPages, getElementById, getSelectedPageIndex } from '../selectors/workpad'; import { getValue as getResolvedArgsValue } from '../selectors/resolved_args'; import { getDefaultElement } from '../defaults'; -import { toExpression, safeElementFromExpression } from '../../../common/lib/ast'; import { notify } from '../../lib/notify'; import { runInterpreter } from '../../lib/run_interpreter'; -import { interpretAst } from '../../lib/interpreter'; import { selectElement } from './transient'; import * as args from './resolved_args'; diff --git a/x-pack/plugins/canvas/public/state/selectors/workpad.js b/x-pack/plugins/canvas/public/state/selectors/workpad.js index 1db0128abab07..1e767b847c460 100644 --- a/x-pack/plugins/canvas/public/state/selectors/workpad.js +++ b/x-pack/plugins/canvas/public/state/selectors/workpad.js @@ -5,7 +5,7 @@ */ import { get, omit } from 'lodash'; -import { safeElementFromExpression } from '../../../common/lib/ast'; +import { safeElementFromExpression } from '@kbn/interpreter/common'; import { append } from '../../lib/modify_path'; import { getAssets } from './assets'; diff --git a/x-pack/plugins/canvas/scripts/peg_build.js b/x-pack/plugins/canvas/scripts/peg_build.js deleted file mode 100644 index 7b880c83b89a1..0000000000000 --- a/x-pack/plugins/canvas/scripts/peg_build.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -require('./_helpers').runGulpTask('canvas:peg:build'); diff --git a/x-pack/plugins/canvas/server/lib/feature_check.js b/x-pack/plugins/canvas/server/lib/feature_check.js deleted file mode 100644 index e9cec02923582..0000000000000 --- a/x-pack/plugins/canvas/server/lib/feature_check.js +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -// TODO: replace this when we use the method exposed by security https://github.com/elastic/kibana/pull/24616 -export const isSecurityEnabled = server => { - const kibanaSecurity = server.plugins.security; - const esSecurity = server.plugins.xpack_main.info.feature('security'); - - return kibanaSecurity && esSecurity.isAvailable() && esSecurity.isEnabled(); -}; diff --git a/x-pack/plugins/canvas/server/lib/get_plugin_paths.js b/x-pack/plugins/canvas/server/lib/get_plugin_paths.js deleted file mode 100644 index 02582e5f749cc..0000000000000 --- a/x-pack/plugins/canvas/server/lib/get_plugin_paths.js +++ /dev/null @@ -1,83 +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 path from 'path'; -import fs from 'fs'; -import { promisify } from 'util'; -import { flatten } from 'lodash'; -import { pluginPaths } from './plugin_paths'; - -const lstat = promisify(fs.lstat); -const readdir = promisify(fs.readdir); - -const canvasPluginDirectoryName = 'canvas_plugin'; - -const isDirectory = path => - lstat(path) - .then(stat => stat.isDirectory()) - .catch(() => false); - -const isDirname = (p, name) => path.basename(p) === name; - -const getKibanaPluginsPath = () => { - const basePluginPath = path.resolve(__dirname, '..', '..', '..', '..', '..'); - - // find the kibana path in dev mode - if (isDirname(basePluginPath, 'kibana')) return path.join(basePluginPath, 'plugins'); - - // find the kibana path in the build, which lives in node_modules and requires going 1 path up - const buildPluginPath = path.join(basePluginPath, '..'); - if (isDirname(basePluginPath, 'node_modules')) { - const pluginPath = path.join(buildPluginPath, 'plugins'); - return isDirectory(pluginPath) && pluginPath; - } - - return false; -}; - -// These must all exist -const paths = [ - path.resolve(__dirname, '..', '..', '..'), // Canvas core plugins - getKibanaPluginsPath(), // Kibana plugin directory -].filter(Boolean); - -export const getPluginPaths = type => { - const typePath = pluginPaths[type]; - if (!typePath) throw new Error(`Unknown type: ${type}`); - - async function findPlugins(directory) { - const isDir = await isDirectory(directory); - if (!isDir) return; - - const names = await readdir(directory); // Get names of everything in the directory - return names - .filter(name => name[0] !== '.') - .map(name => path.resolve(directory, name, canvasPluginDirectoryName, ...typePath)); - } - - return Promise.all(paths.map(findPlugins)) - .then(dirs => - dirs.reduce((list, dir) => { - if (!dir) return list; - return list.concat(dir); - }, []) - ) - .then(possibleCanvasPlugins => { - // Check how many are directories. If lstat fails it doesn't exist anyway. - return Promise.all( - // An array - possibleCanvasPlugins.map(pluginPath => isDirectory(pluginPath)) - ).then(isDirectory => possibleCanvasPlugins.filter((pluginPath, i) => isDirectory[i])); - }) - .then(canvasPluginDirectories => { - return Promise.all( - canvasPluginDirectories.map(dir => - // Get the full path of all files in the directory - readdir(dir).then(files => files.map(file => path.resolve(dir, file))) - ) - ).then(flatten); - }); -}; diff --git a/x-pack/plugins/canvas/server/lib/get_plugin_stream.js b/x-pack/plugins/canvas/server/lib/get_plugin_stream.js deleted file mode 100644 index 6a08e2beeff8e..0000000000000 --- a/x-pack/plugins/canvas/server/lib/get_plugin_stream.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import fs from 'fs'; -import ss from 'stream-stream'; -import { getPluginPaths } from './get_plugin_paths'; - -export const getPluginStream = type => { - const stream = ss({ - separator: '\n', - }); - - getPluginPaths(type).then(files => { - files.forEach(file => { - stream.write(fs.createReadStream(file)); - }); - stream.end(); - }); - - return stream; -}; diff --git a/x-pack/plugins/canvas/server/lib/get_request.js b/x-pack/plugins/canvas/server/lib/get_request.js deleted file mode 100644 index d55421e437fc4..0000000000000 --- a/x-pack/plugins/canvas/server/lib/get_request.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import boom from 'boom'; -import { API_ROUTE } from '../../common/lib/constants'; - -export function getRequest(server, { headers }) { - const url = `${API_ROUTE}/ping`; - - return server - .inject({ - method: 'POST', - url, - headers, - }) - .then(res => { - if (res.statusCode !== 200) { - if (process.env.NODE_ENV !== 'production') { - console.error( - new Error(`Auth request failed: [${res.statusCode}] ${res.result.message}`) - ); - } - throw boom.unauthorized('Failed to authenticate socket connection'); - } - - return res.request; - }); -} diff --git a/x-pack/plugins/canvas/server/lib/plugin_paths.js b/x-pack/plugins/canvas/server/lib/plugin_paths.js deleted file mode 100644 index cb90cc0c0f06c..0000000000000 --- a/x-pack/plugins/canvas/server/lib/plugin_paths.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const pluginPaths = { - serverFunctions: ['functions', 'server'], - browserFunctions: ['functions', 'browser'], - commonFunctions: ['functions', 'common'], - types: ['types'], - elements: ['elements'], - renderers: ['renderers'], - interfaces: ['interfaces'], - transformUIs: ['uis', 'transforms'], - datasourceUIs: ['uis', 'datasources'], - modelUIs: ['uis', 'models'], - viewUIs: ['uis', 'views'], - argumentUIs: ['uis', 'arguments'], -}; diff --git a/x-pack/plugins/canvas/server/lib/route_expression/server.js b/x-pack/plugins/canvas/server/lib/route_expression/server.js deleted file mode 100644 index b24e4cb7e5e41..0000000000000 --- a/x-pack/plugins/canvas/server/lib/route_expression/server.js +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { getServerRegistries } from '../server_registries'; -import { interpretProvider } from '../../../common/interpreter/interpret'; -import { createHandlers } from '../create_handlers'; - -export const server = async ({ onFunctionNotFound, server, request }) => { - const { serverFunctions, types } = await getServerRegistries(['serverFunctions', 'types']); - - return { - interpret: (ast, context) => { - const interpret = interpretProvider({ - types: types.toJS(), - functions: serverFunctions.toJS(), - handlers: createHandlers(request, server), - onFunctionNotFound, - }); - - return interpret(ast, context); - }, - getFunctions: () => Object.keys(serverFunctions.toJS()), - }; -}; diff --git a/x-pack/plugins/canvas/server/lib/route_expression/thread/babeled.js b/x-pack/plugins/canvas/server/lib/route_expression/thread/babeled.js deleted file mode 100644 index b7c1e83beb7c7..0000000000000 --- a/x-pack/plugins/canvas/server/lib/route_expression/thread/babeled.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -require('babel-register')({ - ignore: [ - // stolen from kibana/src/setup_node_env/babel_register/register.js - // ignore paths matching `/node_modules/{a}/{b}`, unless `a` - // is `x-pack` and `b` is not `node_modules` - /\/node_modules\/(?!x-pack\/(?!node_modules)([^\/]+))([^\/]+\/[^\/]+)/, - ], - babelrc: false, - presets: [require.resolve('@kbn/babel-preset/node_preset')], -}); - -require('./polyfill'); -require('./worker'); diff --git a/x-pack/plugins/canvas/server/lib/route_expression/thread/polyfill.js b/x-pack/plugins/canvas/server/lib/route_expression/thread/polyfill.js deleted file mode 100644 index be4983e9a37e8..0000000000000 --- a/x-pack/plugins/canvas/server/lib/route_expression/thread/polyfill.js +++ /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. - */ - -// taken from kibana/src/setup_node_env/babel_register/polyfill.js -// ... -// `babel-preset-env` looks for and rewrites the following import -// statement into a list of import statements based on the polyfills -// necessary for our target environment (the current version of node) -// but since it does that during compilation, `import 'babel-polyfill'` -// must be in a file that is loaded with `require()` AFTER `babel-register` -// is configured. -// -// This is why we have this single statement in it's own file and require -// it from ./babeled.js -import 'babel-polyfill'; diff --git a/x-pack/plugins/canvas/server/lib/server_registries.js b/x-pack/plugins/canvas/server/lib/server_registries.js deleted file mode 100644 index cff63a1138ea3..0000000000000 --- a/x-pack/plugins/canvas/server/lib/server_registries.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { typesRegistry } from '../../common/lib/types_registry'; -import { functionsRegistry as serverFunctions } from '../../common/lib/functions_registry'; -import { getPluginPaths } from './get_plugin_paths'; - -const registries = { - serverFunctions: serverFunctions, - commonFunctions: serverFunctions, - types: typesRegistry, -}; - -let resolve = null; -let called = false; - -const populatePromise = new Promise(_resolve => { - resolve = _resolve; -}); - -export const getServerRegistries = () => { - return populatePromise; -}; - -export const populateServerRegistries = types => { - if (called) throw new Error('function should only be called once per process'); - called = true; - if (!types || !types.length) throw new Error('types is required'); - - const remainingTypes = types; - const populatedTypes = {}; - - const loadType = () => { - const type = remainingTypes.pop(); - getPluginPaths(type).then(paths => { - global.canvas = global.canvas || {}; - global.canvas.register = d => registries[type].register(d); - - paths.forEach(path => { - require(path); - }); - - global.canvas = undefined; - populatedTypes[type] = registries[type]; - if (remainingTypes.length) loadType(); - else resolve(populatedTypes); - }); - }; - - if (remainingTypes.length) loadType(); - return populatePromise; -}; diff --git a/x-pack/plugins/canvas/server/routes/index.js b/x-pack/plugins/canvas/server/routes/index.js index ab2edfe86b56f..45f26a423fc84 100644 --- a/x-pack/plugins/canvas/server/routes/index.js +++ b/x-pack/plugins/canvas/server/routes/index.js @@ -5,15 +5,9 @@ */ import { workpad } from './workpad'; -import { socketApi } from './socket'; -import { translate } from './translate'; import { esFields } from './es_fields'; -import { plugins } from './plugins'; export function routes(server) { workpad(server); - socketApi(server); - translate(server); esFields(server); - plugins(server); } diff --git a/x-pack/plugins/canvas/server/routes/plugins.js b/x-pack/plugins/canvas/server/routes/plugins.js deleted file mode 100644 index be94ef52ac9e4..0000000000000 --- a/x-pack/plugins/canvas/server/routes/plugins.js +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { getPluginStream } from '../lib/get_plugin_stream'; -import { pluginPaths } from '../lib/plugin_paths'; - -export function plugins(server) { - server.route({ - method: 'GET', - path: '/api/canvas/plugins', - handler: function(request, h) { - const { type } = request.query; - - if (!pluginPaths[type]) return h.response({ error: 'Invalid type' }).code(400); - - return getPluginStream(type); - }, - config: { - auth: false, - }, - }); -} diff --git a/x-pack/plugins/canvas/server/routes/translate.js b/x-pack/plugins/canvas/server/routes/translate.js deleted file mode 100644 index 6125898a7dab9..0000000000000 --- a/x-pack/plugins/canvas/server/routes/translate.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { fromExpression, toExpression } from '../../common/lib/ast'; - -export function translate(server) { - /* - Get AST from expression - */ - server.route({ - method: 'GET', - path: '/api/canvas/ast', - handler: function(request, h) { - if (!request.query.expression) - return h.response({ error: '"expression" query is required' }).code(400); - return fromExpression(request.query.expression); - }, - }); - - server.route({ - method: 'POST', - path: '/api/canvas/expression', - handler: function(request, h) { - try { - return toExpression(request.payload); - } catch (e) { - return h.response({ error: e.message }).code(400); - } - }, - }); -} diff --git a/x-pack/plugins/canvas/server/usage/collector.js b/x-pack/plugins/canvas/server/usage/collector.js index a4e73ffe85071..a73ab069638b0 100644 --- a/x-pack/plugins/canvas/server/usage/collector.js +++ b/x-pack/plugins/canvas/server/usage/collector.js @@ -5,8 +5,8 @@ */ import { sum as arraySum, min as arrayMin, max as arrayMax, get } from 'lodash'; +import { fromExpression } from '@kbn/interpreter/common'; import { CANVAS_USAGE_TYPE, CANVAS_TYPE } from '../../common/lib/constants'; -import { fromExpression } from '../../common/lib/ast'; /* * @param ast: an ast that includes functions to track diff --git a/x-pack/plugins/canvas/tasks/helpers/babelhook.js b/x-pack/plugins/canvas/tasks/helpers/babelhook.js index ebae405457d3e..b25b3d3d699b2 100644 --- a/x-pack/plugins/canvas/tasks/helpers/babelhook.js +++ b/x-pack/plugins/canvas/tasks/helpers/babelhook.js @@ -14,10 +14,6 @@ const options = { [ 'mock-imports', [ - { - pattern: 'scriptjs', - location: resolve(__dirname, '..', 'mocks', 'noop'), - }, { pattern: 'ui/chrome', location: resolve(__dirname, '..', 'mocks', 'uiChrome'), @@ -30,10 +26,6 @@ const options = { pattern: 'ui/url/absolute_to_parsed_url', location: resolve(__dirname, '..', 'mocks', 'absoluteToParsedUrl'), }, - { - pattern: 'socket.io-client', - location: resolve(__dirname, '..', 'mocks', 'socketClient'), - }, { // ugly hack so that importing non-js files works, required for the function docs pattern: '.(less|png|svg)$', diff --git a/x-pack/plugins/canvas/tasks/helpers/webpack.plugins.js b/x-pack/plugins/canvas/tasks/helpers/webpack.plugins.js index c53eccd87bd97..0968417d5d260 100644 --- a/x-pack/plugins/canvas/tasks/helpers/webpack.plugins.js +++ b/x-pack/plugins/canvas/tasks/helpers/webpack.plugins.js @@ -10,92 +10,117 @@ const CopyWebpackPlugin = require('copy-webpack-plugin'); const sourceDir = path.resolve(__dirname, '../../canvas_plugin_src'); const buildDir = path.resolve(__dirname, '../../canvas_plugin'); -module.exports = { - entry: { - 'elements/all': path.join(sourceDir, 'elements/register.js'), - 'renderers/all': path.join(sourceDir, 'renderers/register.js'), - 'uis/transforms/all': path.join(sourceDir, 'uis/transforms/register.js'), - 'uis/models/all': path.join(sourceDir, 'uis/models/register.js'), - 'uis/views/all': path.join(sourceDir, 'uis/views/register.js'), - 'uis/datasources/all': path.join(sourceDir, 'uis/datasources/register.js'), - 'uis/arguments/all': path.join(sourceDir, 'uis/arguments/register.js'), - 'functions/browser/all': path.join(sourceDir, 'functions/browser/register.js'), - 'functions/common/all': path.join(sourceDir, 'functions/common/register.js'), - 'types/all': path.join(sourceDir, 'types/register.js'), - }, +export function getWebpackConfig({ devtool, watch } = {}) { + return { + watch, + devtool, - // there were problems with the node and web targets since this code is actually - // targetting both the browser and node.js. If there was a hybrid target we'd use - // it, but this seems to work either way. - target: 'webworker', - - output: { - path: buildDir, - filename: '[name].js', // Need long paths here. - libraryTarget: 'umd', - }, - - resolve: { - extensions: ['.js', '.json'], - mainFields: ['browser', 'main'], - }, + entry: { + 'elements/all': path.join(sourceDir, 'elements/register.js'), + 'renderers/all': path.join(sourceDir, 'renderers/register.js'), + 'uis/transforms/all': path.join(sourceDir, 'uis/transforms/register.js'), + 'uis/models/all': path.join(sourceDir, 'uis/models/register.js'), + 'uis/views/all': path.join(sourceDir, 'uis/views/register.js'), + 'uis/datasources/all': path.join(sourceDir, 'uis/datasources/register.js'), + 'uis/arguments/all': path.join(sourceDir, 'uis/arguments/register.js'), + 'functions/browser/all': path.join(sourceDir, 'functions/browser/register.js'), + 'functions/common/all': path.join(sourceDir, 'functions/common/register.js'), + }, - plugins: [ - function loaderFailHandler() { - // bails on error, including loader errors - // see https://github.com/webpack/webpack/issues/708, which does not fix loader errors - let isWatch = true; + // there were problems with the node and web targets since this code is actually + // targetting both the browser and node.js. If there was a hybrid target we'd use + // it, but this seems to work either way. + target: 'webworker', - this.plugin('run', function(compiler, callback) { - isWatch = false; - callback.call(compiler); - }); + output: { + path: buildDir, + filename: '[name].js', // Need long paths here. + libraryTarget: 'umd', + }, - this.plugin('done', function(stats) { - if (!stats.hasErrors()) return; - const errorMessage = stats.toString('errors-only'); - if (isWatch) console.error(errorMessage); - else throw new Error(errorMessage); - }); + resolve: { + extensions: ['.ts', '.tsx', '.js', '.json'], + mainFields: ['browser', 'main'], }, - new CopyWebpackPlugin([ - { - from: `${sourceDir}/functions/server/`, - to: `${buildDir}/functions/server/`, - ignore: '**/__tests__/**', - }, - ]), - ], - module: { - rules: [ - { - test: /\.js$/, - exclude: [/node_modules/], - loaders: 'babel-loader', - options: { - babelrc: false, - presets: [require.resolve('@kbn/babel-preset/webpack_preset')], - }, - }, - { - test: /\.(png|jpg|gif|jpeg|svg)$/, - loaders: ['url-loader'], - }, - { - test: /\.(css|scss)$/, - loaders: ['style-loader', 'css-loader', 'sass-loader'], + plugins: [ + function loaderFailHandler() { + // bails on error, including loader errors + // see https://github.com/webpack/webpack/issues/708, which does not fix loader errors + this.plugin('done', function(stats) { + if (!stats.hasErrors()) return; + const errorMessage = stats.toString('errors-only'); + if (watch) console.error(errorMessage); + else throw new Error(errorMessage); + }); }, + new CopyWebpackPlugin([ + { + from: `${sourceDir}/functions/server/`, + to: `${buildDir}/functions/server/`, + ignore: '**/__tests__/**', + }, + ]), ], - }, - node: { - // Don't replace built-in globals - __filename: false, - __dirname: false, - }, + module: { + rules: [ + { + test: /\.js$/, + exclude: [/node_modules/], + loaders: 'babel-loader', + options: { + babelrc: false, + presets: [require.resolve('@kbn/babel-preset/webpack_preset')], + }, + }, + { + test: /\.(png|jpg|gif|jpeg|svg)$/, + loaders: ['url-loader'], + }, + { + test: /\.(css|scss)$/, + loaders: ['style-loader', 'css-loader', 'sass-loader'], + }, + { + test: /\.tsx?$/, + include: sourceDir, + use: [ + { + loader: 'ts-loader', + options: { + transpileOnly: true, + experimentalWatchApi: true, + onlyCompileBundledFiles: true, + configFile: require.resolve('../../../../tsconfig.json'), + compilerOptions: { + sourceMap: Boolean(devtool), + }, + }, + }, + ], + }, + ], + }, - watchOptions: { - ignored: [/node_modules/], - }, -}; + node: { + // Don't replace built-in globals + __filename: false, + __dirname: false, + }, + + watchOptions: { + ignored: [/node_modules/], + }, + + stats: { + // when typescript doesn't do a full type check, as we have the ts-loader + // configured here, it does not have enough information to determine + // whether an imported name is a type or not, so when the name is then + // exported, typescript has no choice but to emit the export. Fortunately, + // the extraneous export should not be harmful, so we just suppress these warnings + // https://github.com/TypeStrong/ts-loader#transpileonly-boolean-defaultfalse + warningsFilter: /export .* was not found in/, + }, + }; +} diff --git a/x-pack/plugins/canvas/tasks/index.js b/x-pack/plugins/canvas/tasks/index.js index 9835f76f12609..2d529ce139187 100644 --- a/x-pack/plugins/canvas/tasks/index.js +++ b/x-pack/plugins/canvas/tasks/index.js @@ -5,14 +5,12 @@ */ import dev from './dev'; -import peg from './peg'; import plugins from './plugins'; import prepare from './prepare'; import test from './test'; export default function canvasTasks(gulp, gulpHelpers) { dev(gulp, gulpHelpers); - peg(gulp, gulpHelpers); plugins(gulp, gulpHelpers); prepare(gulp, gulpHelpers); test(gulp, gulpHelpers); diff --git a/x-pack/plugins/canvas/tasks/mocks/socketClient.js b/x-pack/plugins/canvas/tasks/mocks/socketClient.js deleted file mode 100644 index 93270c0801d8d..0000000000000 --- a/x-pack/plugins/canvas/tasks/mocks/socketClient.js +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -const noop = () => {}; - -// arguments: url, options -// https://github.com/socketio/socket.io-client/blob/master/docs/API.md#iourl-options -export default function mockIo() { - return { - on: noop, - emit: noop, - once: noop, - }; -} diff --git a/x-pack/plugins/canvas/tasks/peg.js b/x-pack/plugins/canvas/tasks/peg.js deleted file mode 100644 index 0d202c4ac79dd..0000000000000 --- a/x-pack/plugins/canvas/tasks/peg.js +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { resolve } from 'path'; - -const grammarDir = resolve(__dirname, '..', 'common', 'lib'); - -export default function pegTask(gulp, { pegjs }) { - gulp.task('canvas:peg:build', function() { - return gulp - .src(`${grammarDir}/*.peg`) - .pipe( - pegjs({ - format: 'commonjs', - allowedStartRules: ['expression', 'argument'], - }) - ) - .pipe(gulp.dest(grammarDir)); - }); - - gulp.task('canvas:peg:dev', function() { - gulp.watch(`${grammarDir}/*.peg`, ['peg']); - }); -} diff --git a/x-pack/plugins/canvas/tasks/plugins.js b/x-pack/plugins/canvas/tasks/plugins.js index 991b4d85c6ec5..0b8643880ffe5 100644 --- a/x-pack/plugins/canvas/tasks/plugins.js +++ b/x-pack/plugins/canvas/tasks/plugins.js @@ -8,7 +8,7 @@ import path from 'path'; // eslint-disable-next-line import/no-extraneous-dependencies import webpack from 'webpack'; import del from 'del'; -import webpackConfig from './helpers/webpack.plugins'; +import { getWebpackConfig } from './helpers/webpack.plugins'; const devtool = 'inline-cheap-module-source-map'; const buildDir = path.resolve(__dirname, '../canvas_plugin'); @@ -26,20 +26,20 @@ export default function pluginsTasks(gulp, { log, colors }) { }; gulp.task('canvas:plugins:build', function(done) { - del(buildDir).then(() => webpack({ ...webpackConfig, devtool }, onComplete(done))); + del(buildDir).then(() => webpack(getWebpackConfig({ devtool }), onComplete(done))); }); // eslint-disable-next-line no-unused-vars gulp.task('canvas:plugins:dev', function(done /* added to make gulp async */) { log(`${colors.green.bold('canvas:plugins')} Starting initial build, this will take a while`); del(buildDir).then(() => - webpack({ ...webpackConfig, devtool, watch: true }, (err, stats) => { + webpack(getWebpackConfig({ devtool, watch: true }), (err, stats) => { onComplete()(err, stats); }) ); }); gulp.task('canvas:plugins:build-prod', function(done) { - del(buildDir).then(() => webpack(webpackConfig, onComplete(done))); + del(buildDir).then(() => webpack(getWebpackConfig(), onComplete(done))); }); } diff --git a/x-pack/plugins/graph/public/app.js b/x-pack/plugins/graph/public/app.js index f7fa2f01bf288..77ae583ad259d 100644 --- a/x-pack/plugins/graph/public/app.js +++ b/x-pack/plugins/graph/public/app.js @@ -39,7 +39,7 @@ import { drillDownIconChoicesByClass } from './style_choices'; import { - outlinkEncoders + getOutlinkEncoders, } from './services/outlink_encoders'; const app = uiModules.get('app/graph'); @@ -96,11 +96,15 @@ uiRoutes .when('/workspace/:id', { template: appTemplate, resolve: { - savedWorkspace: function (savedGraphWorkspaces, courier, $route) { + savedWorkspace: function (savedGraphWorkspaces, courier, $route, i18n) { return savedGraphWorkspaces.get($route.current.params.id) .catch( function () { - toastNotifications.addDanger('Missing workspace'); + toastNotifications.addDanger( + i18n('xpack.graph.missingWorkspaceErrorMessage', { + defaultMessage: 'Missing workspace', + }) + ); } ); @@ -131,7 +135,7 @@ uiRoutes //======== Controller for basic UI ================== -app.controller('graphuiPlugin', function ($scope, $route, $interval, $http, kbnUrl, Private, Promise, confirmModal, kbnBaseUrl) { +app.controller('graphuiPlugin', function ($scope, $route, $interval, $http, kbnUrl, Private, Promise, confirmModal, kbnBaseUrl, i18n) { function handleSuccess(data) { return checkLicense(Private, Promise, kbnBaseUrl) @@ -151,7 +155,7 @@ app.controller('graphuiPlugin', function ($scope, $route, $interval, $http, kbnU $scope.colors = colorChoices; $scope.iconChoicesByClass = iconChoicesByClass; - $scope.outlinkEncoders = outlinkEncoders; + $scope.outlinkEncoders = getOutlinkEncoders(i18n); $scope.fields = []; $scope.canEditDrillDownUrls = chrome.getInjected('canEditDrillDownUrls'); @@ -290,9 +294,13 @@ app.controller('graphuiPlugin', function ($scope, $route, $interval, $http, kbnU const confirmModalOptions = { onConfirm: yesFn, onCancel: noFn, - confirmButtonText: 'Clear workspace' + confirmButtonText: i18n('xpack.graph.clearWorkspace.confirmButtonLabel', { + defaultMessage: 'Clear workspace', + }) }; - confirmModal('This will clear the workspace - are you sure?', confirmModalOptions); + confirmModal(i18n('xpack.graph.clearWorkspace.confirmText', { + defaultMessage: 'This will clear the workspace - are you sure?', + }), confirmModalOptions); } $scope.uiSelectIndex = function () { @@ -387,7 +395,11 @@ app.controller('graphuiPlugin', function ($scope, $route, $interval, $http, kbnU return $http.post('../api/graph/graphExplore', request) .then(function (resp) { if (resp.data.resp.timed_out) { - toastNotifications.addWarning('Exploration timed out'); + toastNotifications.addWarning( + i18n('xpack.graph.exploreGraph.timedOutWarningText', { + defaultMessage: 'Exploration timed out', + }) + ); } responseHandler(resp.data.resp); }) @@ -536,8 +548,15 @@ app.controller('graphuiPlugin', function ($scope, $route, $interval, $http, kbnU const found = $scope.newUrlTemplate.url.search(drillDownRegex) > -1; if (!found) { toastNotifications.addWarning({ - title: 'Invalid URL', - text: 'The URL must contain a {{gquery}} string', + title: i18n('xpack.graph.settings.drillDowns.invalidUrlWarningTitle', { + defaultMessage: 'Invalid URL', + }), + text: i18n('xpack.graph.settings.drillDowns.invalidUrlWarningText', { + defaultMessage: 'The URL must contain a {placeholder} string', + values: { + placeholder: '{{gquery}}' + } + }), }); return; } @@ -556,10 +575,18 @@ app.controller('graphuiPlugin', function ($scope, $route, $interval, $http, kbnU $scope.removeUrlTemplate = function (urlTemplate) { const i = $scope.urlTemplates.indexOf(urlTemplate); if (i != -1) { - confirmModal('Remove "' + urlTemplate.description + '" drill-down?', { - onConfirm: () => $scope.urlTemplates.splice(i, 1), - confirmButtonText: 'Remove drill-down' - }); + confirmModal( + i18n('xpack.graph.settings.drillDowns.removeConfirmText', { + defaultMessage: 'Remove "{urlTemplateDesciption}" drill-down?', + values: { urlTemplateDesciption: urlTemplate.description }, + }), + { + onConfirm: () => $scope.urlTemplates.splice(i, 1), + confirmButtonText: i18n('xpack.graph.settings.drillDowns.removeConfirmButtonLabel', { + defaultMessage: 'Remove drill-down', + }), + }, + ); } }; @@ -651,7 +678,9 @@ app.controller('graphuiPlugin', function ($scope, $route, $interval, $http, kbnU $scope.urlTemplates.push({ url: discoverUrl, - description: 'Raw documents', + description: i18n('xpack.graph.settings.drillDowns.defaultUrlTemplateTitle', { + defaultMessage: 'Raw documents', + }), encoder: $scope.outlinkEncoders[0] }); } @@ -730,30 +759,46 @@ app.controller('graphuiPlugin', function ($scope, $route, $interval, $http, kbnU $scope.topNavMenu = []; $scope.topNavMenu.push({ key: 'new', - description: 'New Workspace', - tooltip: 'Create a new workspace', + description: i18n('xpack.graph.topNavMenu.newWorkspaceAriaLabel', { + defaultMessage: 'New Workspace', + }), + tooltip: i18n('xpack.graph.topNavMenu.newWorkspaceTooltip', { + defaultMessage: 'Create a new workspace', + }), run: function () {canWipeWorkspace(function () {kbnUrl.change('/home', {}); }); }, }); if (!$scope.allSavingDisabled) { $scope.topNavMenu.push({ key: 'save', - description: 'Save Workspace', - tooltip: 'Save this workspace', + description: i18n('xpack.graph.topNavMenu.saveWorkspace.enabledAriaLabel', { + defaultMessage: 'Save Workspace', + }), + tooltip: i18n('xpack.graph.topNavMenu.saveWorkspace.enabledTooltip', { + defaultMessage: 'Save this workspace', + }), disableButton: function () {return $scope.selectedFields.length === 0;}, template: require('./templates/save_workspace.html') }); }else { $scope.topNavMenu.push({ key: 'save', - description: 'Save Workspace', - tooltip: 'No changes to saved workspaces are permitted by the current save policy', + description: i18n('xpack.graph.topNavMenu.saveWorkspace.disabledAriaLabel', { + defaultMessage: 'Save Workspace', + }), + tooltip: i18n('xpack.graph.topNavMenu.saveWorkspace.disabledTooltip', { + defaultMessage: 'No changes to saved workspaces are permitted by the current save policy', + }), disableButton: true }); } $scope.topNavMenu.push({ key: 'open', - description: 'Load Saved Workspace', - tooltip: 'Load a saved workspace', + description: i18n('xpack.graph.topNavMenu.loadWorkspaceAriaLabel', { + defaultMessage: 'Load Saved Workspace', + }), + tooltip: i18n('xpack.graph.topNavMenu.loadWorkspaceTooltip', { + defaultMessage: 'Load a saved workspace', + }), template: require('./templates/load_workspace.html') }); if (!$scope.allSavingDisabled) { @@ -762,35 +807,58 @@ app.controller('graphuiPlugin', function ($scope, $route, $interval, $http, kbnU disableButton: function () { return $route.current.locals === undefined || $route.current.locals.savedWorkspace === undefined; }, - description: 'Delete Saved Workspace', - tooltip: 'Delete this workspace', + description: i18n('xpack.graph.topNavMenu.deleteWorkspace.enabledAriaLabel', { + defaultMessage: 'Delete Saved Workspace', + }), + tooltip: i18n('xpack.graph.topNavMenu.deleteWorkspace.enabledAriaTooltip', { + defaultMessage: 'Delete this workspace', + }), run: function () { const title = $route.current.locals.savedWorkspace.title; function doDelete() { $route.current.locals.SavedWorkspacesProvider.delete($route.current.locals.savedWorkspace.id); kbnUrl.change('/home', {}); - toastNotifications.addSuccess(`Deleted '${title}'`); + toastNotifications.addSuccess( + i18n('xpack.graph.topNavMenu.deleteWorkspaceNotification', { + defaultMessage: `Deleted '{workspaceTitle}'`, + values: { workspaceTitle: title }, + }) + ); } const confirmModalOptions = { onConfirm: doDelete, - confirmButtonText: 'Delete workspace' + confirmButtonText: i18n('xpack.graph.topNavMenu.deleteWorkspace.confirmButtonLabel', { + defaultMessage: 'Delete workspace', + }), }; - confirmModal('Are you sure you want to delete the workspace ' + title + ' ?', confirmModalOptions); + confirmModal( + i18n('xpack.graph.topNavMenu.deleteWorkspace.confirmText', { + defaultMessage: 'Are you sure you want to delete the workspace {title} ?', + values: { title }, + }), + confirmModalOptions + ); } }); }else { $scope.topNavMenu.push({ key: 'delete', disableButton: true, - description: 'Delete Saved Workspace', - tooltip: 'No changes to saved workspaces are permitted by the current save policy' + description: i18n('xpack.graph.topNavMenu.deleteWorkspace.disabledAriaLabel', { + defaultMessage: 'Delete Saved Workspace', + }), + tooltip: i18n('xpack.graph.topNavMenu.deleteWorkspace.disabledTooltip', { + defaultMessage: 'No changes to saved workspaces are permitted by the current save policy', + }), }); } $scope.topNavMenu.push({ key: 'settings', disableButton: function () { return $scope.selectedIndex === null; }, - description: 'Settings', + description: i18n('xpack.graph.topNavMenu.settingsAriaLabel', { + defaultMessage: 'Settings', + }), template: require('./templates/settings.html') }); @@ -829,7 +897,12 @@ app.controller('graphuiPlugin', function ($scope, $route, $interval, $http, kbnU } }); if(!savedObjectIndexPattern) { - toastNotifications.addDanger(`'Missing index pattern ${wsObj.indexPattern}`); + toastNotifications.addDanger( + i18n('xpack.graph.loadWorkspace.missingIndexPatternErrorMessage', { + defaultMessage: `'Missing index pattern {indexPattern}`, + values: { indexPattern: wsObj.indexPattern }, + }) + ); return; } @@ -946,7 +1019,9 @@ app.controller('graphuiPlugin', function ($scope, $route, $interval, $http, kbnU if ($scope.allSavingDisabled) { // It should not be possible to navigate to this function if allSavingDisabled is set // but adding check here as a safeguard. - toastNotifications.addWarning('Saving is disabled'); + toastNotifications.addWarning( + i18n('xpack.graph.saveWorkspace.disabledWarning', { defaultMessage: 'Saving is disabled' }) + ); return; } initWorkspaceIfRequired(); @@ -1034,10 +1109,15 @@ app.controller('graphuiPlugin', function ($scope, $route, $interval, $http, kbnU $scope.kbnTopNav.close('save'); $scope.userHasConfirmedSaveWorkspaceData = false; //reset flag if (id) { - const title = `Saved "${$scope.savedWorkspace.title}"`; + const title = i18n('xpack.graph.saveWorkspace.successNotificationTitle', { + defaultMessage: 'Saved "{workspaceTitle}"', + values: { workspaceTitle: $scope.savedWorkspace.title }, + }); let text; if (!canSaveData && $scope.workspace.nodes.length > 0) { - text = 'The configuration was saved, but the data was not saved'; + text = i18n('xpack.graph.saveWorkspace.successNotification.noDataSavedText', { + defaultMessage: 'The configuration was saved, but the data was not saved', + }); } toastNotifications.addSuccess({ diff --git a/x-pack/plugins/graph/public/register_feature.js b/x-pack/plugins/graph/public/register_feature.js index d780614972562..c147982dc081c 100644 --- a/x-pack/plugins/graph/public/register_feature.js +++ b/x-pack/plugins/graph/public/register_feature.js @@ -8,11 +8,13 @@ import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory } from 'ui/registry/feature_catalogue'; -FeatureCatalogueRegistryProvider.register(() => { +FeatureCatalogueRegistryProvider.register((i18n) => { return { id: 'graph', title: 'Graph', - description: 'Surface and analyze relevant relationships in your Elasticsearch data.', + description: i18n('xpack.graph.pluginDescription', { + defaultMessage: 'Surface and analyze relevant relationships in your Elasticsearch data.', + }), icon: 'graphApp', path: '/app/graph', showOnHomePage: true, diff --git a/x-pack/plugins/graph/public/services/outlink_encoders.js b/x-pack/plugins/graph/public/services/outlink_encoders.js index e32fd090ff13d..cfc9d745cf8e8 100644 --- a/x-pack/plugins/graph/public/services/outlink_encoders.js +++ b/x-pack/plugins/graph/public/services/outlink_encoders.js @@ -6,38 +6,58 @@ import rison from 'rison-node'; -export const outlinkEncoders = [{ +export const getOutlinkEncoders = i18n => [{ id: 'esq-rison-loose', - title: 'elasticsearch OR query (rison encoded)', - description: 'rison-encoded JSON, minimum_should_match=1, compatible with most Kibana URLs', + title: i18n('xpack.graph.outlinkEncoders.esqRisonLooseTitle', { + defaultMessage: 'elasticsearch OR query (rison encoded)', + }), + description: i18n('xpack.graph.outlinkEncoders.esqRisonLooseDescription', { + defaultMessage: 'rison-encoded JSON, minimum_should_match=1, compatible with most Kibana URLs', + }), encode: function (workspace) { return encodeURIComponent(rison.encode(workspace.getQuery(workspace.getSelectedOrAllNodes(), true))); } }, { id: 'esq-rison', - title: 'elasticsearch AND query (rison encoded)', - description: 'rison-encoded JSON, minimum_should_match=2, compatible with most Kibana URLs', + title: i18n('xpack.graph.outlinkEncoders.esqRisonTitle', { + defaultMessage: 'elasticsearch AND query (rison encoded)', + }), + description: i18n('xpack.graph.outlinkEncoders.esqRisonDescription', { + defaultMessage: 'rison-encoded JSON, minimum_should_match=2, compatible with most Kibana URLs', + }), encode: function (workspace) { return encodeURIComponent(rison.encode(workspace.getQuery(workspace.getSelectedOrAllNodes()))); } }, { id: 'esq-similar-rison', - title: 'elasticsearch more like this query (rison encoded)', - description: 'rison-encoded JSON, "like this but not this" type query to find missing docs', + title: i18n('xpack.graph.outlinkEncoders.esqSimilarRisonTitle', { + defaultMessage: 'elasticsearch more like this query (rison encoded)', + }), + description: i18n('xpack.graph.outlinkEncoders.esqSimilarRisonDescription', { + defaultMessage: 'rison-encoded JSON, "like this but not this" type query to find missing docs', + }), encode: function (workspace) { return encodeURIComponent(rison.encode(workspace.getLikeThisButNotThisQuery(workspace.getSelectedOrAllNodes()))); } }, { id: 'esq-plain', - title: 'elasticsearch query (plain encoding)', - description: 'JSON encoded using standard url encoding', + title: i18n('xpack.graph.outlinkEncoders.esqPlainTitle', { + defaultMessage: 'elasticsearch query (plain encoding)', + }), + description: i18n('xpack.graph.outlinkEncoders.esqPlainDescription', { + defaultMessage: 'JSON encoded using standard url encoding', + }), encode: function (workspace) { return encodeURIComponent(JSON.stringify(workspace.getQuery(workspace.getSelectedOrAllNodes()))); } }, { id: 'text-plain', - title: 'plain text', - description: 'Text of selected vertex labels as a plain url-encoded string', + title: i18n('xpack.graph.outlinkEncoders.textPlainTitle', { + defaultMessage: 'plain text', + }), + description: i18n('xpack.graph.outlinkEncoders.textPlainDescription', { + defaultMessage: 'Text of selected vertex labels as a plain url-encoded string', + }), encode: function (workspace) { let q = ''; const nodes = workspace.getSelectedOrAllNodes(); @@ -52,8 +72,12 @@ export const outlinkEncoders = [{ } }, { id: 'text-lucene', - title: 'Lucene-escaped text', - description: 'Text of selected vertex labels with any Lucene special characters encoded', + title: i18n('xpack.graph.outlinkEncoders.textLuceneTitle', { + defaultMessage: 'Lucene-escaped text', + }), + description: i18n('xpack.graph.outlinkEncoders.textLuceneDescription', { + defaultMessage: 'Text of selected vertex labels with any Lucene special characters encoded', + }), encode: function (workspace) { let q = ''; const nodes = workspace.getSelectedOrAllNodes(); diff --git a/x-pack/plugins/graph/public/templates/index.html b/x-pack/plugins/graph/public/templates/index.html index 7548c60c9d0eb..20e54d4f569e5 100644 --- a/x-pack/plugins/graph/public/templates/index.html +++ b/x-pack/plugins/graph/public/templates/index.html @@ -5,18 +5,36 @@
- {{ savedWorkspace.lastSavedTitle || 'New Graph Workspace' }} + {{ savedWorkspace.lastSavedTitle }} +
- + @@ -27,9 +45,18 @@ - - @@ -39,7 +66,12 @@
-
@@ -51,14 +83,23 @@
- +
- +
@@ -68,13 +109,21 @@
- +
-
@@ -94,7 +143,12 @@
- +
- +
- +
-

Controls the number of terms returned each search step.

+

- Shift-clicking the field icons in the menu bar - provides a quick way to toggle this number to zero and back + + {{ + ::'xpack.graph.queryConfig.maxTermsHelpText' | i18n: { + defaultMessage: 'Shift-clicking the field icons in the menu bar provides a quick way to toggle this number to zero and back' + } + }}
@@ -137,9 +208,12 @@
- +
@@ -222,86 +296,122 @@