diff --git a/.bazelignore b/.bazelignore new file mode 100644 index 0000000000000..b71007690f1cf --- /dev/null +++ b/.bazelignore @@ -0,0 +1,15 @@ +# Bazel does not support wildcards like .gitignore +# Issues are opened for to include that feature but not available yet +# https://github.com/bazelbuild/bazel/issues/7093 +# https://github.com/bazelbuild/bazel/issues/8106 +.ci +.git +.github +.idea +.teamcity +.yarn-local-mirror +bazel-cache +bazel-dist +build +node_modules +target diff --git a/.bazeliskversion b/.bazeliskversion new file mode 100644 index 0000000000000..661e7aeadf36f --- /dev/null +++ b/.bazeliskversion @@ -0,0 +1 @@ +1.7.3 diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 0000000000000..fd469d1203a82 --- /dev/null +++ b/.bazelrc @@ -0,0 +1,9 @@ +# Inspired on from https://raw.githubusercontent.com/bazelbuild/rules_nodejs/master/.bazelrc +# Import shared settings first so we can override below +import %workspace%/.bazelrc.common + +# Remote cache settings for local env +# build --remote_cache=https://storage.googleapis.com/kibana-bazel-cache +# build --incompatible_remote_results_ignore_disk=true +# build --remote_accept_cached=true +# build --remote_upload_local_results=false diff --git a/.bazelrc.common b/.bazelrc.common new file mode 100644 index 0000000000000..a53d1b8072483 --- /dev/null +++ b/.bazelrc.common @@ -0,0 +1,118 @@ +# Inspired on from https://raw.githubusercontent.com/bazelbuild/rules_nodejs/master/common.bazelrc +# Common Bazel settings for JavaScript/NodeJS workspaces +# This rc file is automatically discovered when Bazel is run in this workspace, +# see https://docs.bazel.build/versions/master/guide.html#bazelrc +# +# The full list of Bazel options: https://docs.bazel.build/versions/master/command-line-reference.html + +# Cache action outputs on disk so they persist across output_base and bazel shutdown (eg. changing branches) +build --disk_cache=bazel-cache/disk-cache + +# Bazel repo cache settings +build --repository_cache=bazel-cache/repository-cache + +# Bazel will create symlinks from the workspace directory to output artifacts. +# Build results will be placed in a directory called "bazel-dist/bin" +# This will still create a bazel-out symlink in +# the project directory, which must be excluded from the +# editor's search path. +build --symlink_prefix=bazel-dist/ +# To disable the symlinks altogether (including bazel-out) we can use +# build --symlink_prefix=/ +# however this makes it harder to find outputs. + +# Prevents the creation of bazel-out dir +build --experimental_no_product_name_out_symlink + +# Make direct file system calls to create symlink trees +build --experimental_inprocess_symlink_creation + +# Incompatible flags to run with +build --incompatible_no_implicit_file_export +build --incompatible_restrict_string_escapes + +# Log configs +## different from default +common --color=yes +build --show_task_finish +build --noshow_progress +build --noshow_loading_progress + +## enforced default values +build --show_result=1 + +# Specifies desired output mode for running tests. +# Valid values are +# 'summary' to output only test status summary +# 'errors' to also print test logs for failed tests +# 'all' to print logs for all tests +# 'streamed' to output logs for all tests in real time +# (this will force tests to be executed locally one at a time regardless of --test_strategy value). +test --test_output=errors + +# Support for debugging NodeJS tests +# Add the Bazel option `--config=debug` to enable this +# --test_output=streamed +# Stream stdout/stderr output from each test in real-time. +# See https://docs.bazel.build/versions/master/user-manual.html#flag--test_output for more details. +# --test_strategy=exclusive +# Run one test at a time. +# --test_timeout=9999 +# Prevent long running tests from timing out +# See https://docs.bazel.build/versions/master/user-manual.html#flag--test_timeout for more details. +# --nocache_test_results +# Always run tests +# --node_options=--inspect-brk +# Pass the --inspect-brk option to all tests which enables the node inspector agent. +# See https://nodejs.org/de/docs/guides/debugging-getting-started/#command-line-options for more details. +# --define=VERBOSE_LOGS=1 +# Rules will output verbose logs if the VERBOSE_LOGS environment variable is set. `VERBOSE_LOGS` will be passed to +# `nodejs_binary` and `nodejs_test` via the default value of the `default_env_vars` attribute of those rules. +# --compilation_mode=dbg +# Rules may change their build outputs if the compilation mode is set to dbg. For example, +# mininfiers such as terser may make their output more human readable when this is set. `COMPILATION_MODE` will be passed to +# `nodejs_binary` and `nodejs_test` via the default value of the `default_env_vars` attribute of those rules. +# See https://docs.bazel.build/versions/master/user-manual.html#flag--compilation_mode for more details. +test:debug --test_output=streamed --test_strategy=exclusive --test_timeout=9999 --nocache_test_results --define=VERBOSE_LOGS=1 +# Use bazel run with `--config=debug` to turn on the NodeJS inspector agent. +# The node process will break before user code starts and wait for the debugger to connect. +run:debug --define=VERBOSE_LOGS=1 -- --node_options=--inspect-brk +# The following option will change the build output of certain rules such as terser and may not be desirable in all cases +build:debug --compilation_mode=dbg + +# Turn off legacy external runfiles +# This prevents accidentally depending on this feature, which Bazel will remove. +build --nolegacy_external_runfiles +run --nolegacy_external_runfiles +test --nolegacy_external_runfiles + +# Turn on --incompatible_strict_action_env which was on by default +# in Bazel 0.21.0 but turned off again in 0.22.0. Follow +# https://github.com/bazelbuild/bazel/issues/7026 for more details. +# This flag is needed to so that the bazel cache is not invalidated +# when running bazel via `yarn bazel`. +# See https://github.com/angular/angular/issues/27514. +build --incompatible_strict_action_env +run --incompatible_strict_action_env +test --incompatible_strict_action_env + +# Do not build runfile trees by default. If an execution strategy relies on runfile +# symlink tree, the tree is created on-demand. See: https://github.com/bazelbuild/bazel/issues/6627 +# and https://github.com/bazelbuild/bazel/commit/03246077f948f2790a83520e7dccc2625650e6df +build --nobuild_runfile_links + +# When running `bazel coverage` --instrument_test_targets needs to be set in order to +# collect coverage information from test targets +coverage --instrument_test_targets + +# Settings for CI +# Bazel flags for CI are in /src/dev/ci_setup/.bazelrc-ci + +# Load any settings specific to the current user. +# .bazelrc.user should appear in .gitignore so that settings are not shared with team members +# This needs to be last statement in this +# config, as the user configuration should be able to overwrite flags from this file. +# See https://docs.bazel.build/versions/master/best-practices.html#bazelrc +# (Note that we use .bazelrc.user so the file appears next to .bazelrc in directory listing, +# rather than user.bazelrc as suggested in the Bazel docs) +try-import %workspace%/.bazelrc.user diff --git a/.bazelversion b/.bazelversion new file mode 100644 index 0000000000000..fcdb2e109f68c --- /dev/null +++ b/.bazelversion @@ -0,0 +1 @@ +4.0.0 diff --git a/.ci/teamcity/tests/test_projects.sh b/.ci/teamcity/tests/test_projects.sh index 2553650930392..06dd3607a6799 100755 --- a/.ci/teamcity/tests/test_projects.sh +++ b/.ci/teamcity/tests/test_projects.sh @@ -5,4 +5,4 @@ set -euo pipefail source "$(dirname "${0}")/../util.sh" checks-reporter-with-killswitch "Test Projects" \ - yarn kbn run test --exclude kibana --oss --skip-kibana-plugins + yarn kbn run test --exclude kibana --oss --skip-kibana-plugins --skip-missing diff --git a/.eslintignore b/.eslintignore index 4ef96ebab062a..5d25f3a78c1ec 100644 --- a/.eslintignore +++ b/.eslintignore @@ -36,6 +36,7 @@ snapshots.js # package overrides /packages/elastic-eslint-config-kibana /packages/kbn-interpreter/src/common/lib/grammar.js +/packages/kbn-tinymath/src/grammar.js /packages/kbn-plugin-generator/template /packages/kbn-pm/dist /packages/kbn-test/src/functional_test_runner/__tests__/fixtures/ @@ -43,3 +44,6 @@ snapshots.js /packages/kbn-ui-framework/dist /packages/kbn-ui-shared-deps/flot_charts /packages/kbn-monaco/src/painless/antlr + +# Bazel +/bazel-* diff --git a/.eslintrc.js b/.eslintrc.js index 29528c249d279..d8b9a9d7cdd99 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1192,6 +1192,32 @@ module.exports = { }, }, + /** + * Osquery overrides + */ + { + extends: ['eslint:recommended', 'plugin:react/recommended'], + plugins: ['react'], + files: ['x-pack/plugins/osquery/**/*.{js,mjs,ts,tsx}'], + rules: { + 'arrow-body-style': ['error', 'as-needed'], + 'prefer-arrow-callback': 'error', + 'no-unused-vars': 'off', + 'react/prop-types': 'off', + }, + }, + { + // typescript and javascript for front end react performance + files: ['x-pack/plugins/osquery/public/**/!(*.test).{js,mjs,ts,tsx}'], + plugins: ['react', 'react-perf'], + rules: { + 'react-perf/jsx-no-new-object-as-prop': 'error', + 'react-perf/jsx-no-new-array-as-prop': 'error', + 'react-perf/jsx-no-new-function-as-prop': 'error', + 'react/jsx-no-bind': 'error', + }, + }, + /** * Prettier disables all conflicting rules, listing as last override so it takes precedence */ diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index b47d78ea6d691..dea2c12756b08 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -9,11 +9,9 @@ /x-pack/plugins/discover_enhanced/ @elastic/kibana-app /x-pack/plugins/lens/ @elastic/kibana-app /x-pack/plugins/graph/ @elastic/kibana-app -/x-pack/plugins/vis_type_timeseries_enhanced/ @elastic/kibana-app /src/plugins/advanced_settings/ @elastic/kibana-app /src/plugins/charts/ @elastic/kibana-app /src/plugins/discover/ @elastic/kibana-app -/src/plugins/lens_oss/ @elastic/kibana-app /src/plugins/management/ @elastic/kibana-app /src/plugins/kibana_legacy/ @elastic/kibana-app /src/plugins/timelion/ @elastic/kibana-app @@ -127,7 +125,6 @@ /x-pack/test/functional/es_archives/maps/ @elastic/kibana-gis /x-pack/test/visual_regression/tests/maps/index.js @elastic/kibana-gis #CC# /src/plugins/maps_legacy/ @elastic/kibana-gis -#CC# /src/plugins/maps_oss/ @elastic/kibana-gis #CC# /x-pack/plugins/file_upload @elastic/kibana-gis #CC# /x-pack/plugins/maps_legacy_licensing @elastic/kibana-gis /src/plugins/tile_map/ @elastic/kibana-gis @@ -332,6 +329,9 @@ x-pack/plugins/telemetry_collection_xpack/schema/xpack_plugins.json @elastic/kib # Security Intelligence And Analytics /x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_rules @elastic/security-intelligence-analytics +# Security Asset Management +/x-pack/plugins/osquery @elastic/security-asset-management + # Design (at the bottom for specificity of SASS files) **/*.scss @elastic/kibana-design #CC# /packages/kbn-ui-framework/ @elastic/kibana-design diff --git a/.gitignore b/.gitignore index 79d022a2d701b..2d7dd52e3ef9e 100644 --- a/.gitignore +++ b/.gitignore @@ -76,3 +76,7 @@ report.asciidoc # Yarn local mirror content .yarn-local-mirror + +# Bazel +/bazel-* +/.bazelrc.user diff --git a/.i18nrc.json b/.i18nrc.json index b425dd99857dc..0cdcae08e54e0 100644 --- a/.i18nrc.json +++ b/.i18nrc.json @@ -61,8 +61,6 @@ "visTypeVislib": "src/plugins/vis_type_vislib", "visTypeXy": "src/plugins/vis_type_xy", "visualizations": "src/plugins/visualizations", - "lensOss": "src/plugins/lens_oss", - "mapsOss": "src/plugins/maps_oss", "visualize": "src/plugins/visualize", "apmOss": "src/plugins/apm_oss", "usageCollection": "src/plugins/usage_collection" diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel new file mode 100644 index 0000000000000..0828157524fa1 --- /dev/null +++ b/WORKSPACE.bazel @@ -0,0 +1,3 @@ +workspace( + name = "kibana", +) diff --git a/docs/developer/plugin-list.asciidoc b/docs/developer/plugin-list.asciidoc index 7ce4896a8bce4..c4be7a7367c16 100644 --- a/docs/developer/plugin-list.asciidoc +++ b/docs/developer/plugin-list.asciidoc @@ -130,11 +130,6 @@ in Kibana, e.g. visualizations. It has the form of a flyout panel. |The legacyExport plugin adds support for the legacy saved objects export format. -|{kib-repo}blob/{branch}/src/plugins/lens_oss/README.md[lensOss] -|The lens_oss plugin registers the lens visualization on OSS. -It is registered as disabled. The x-pack plugin should unregister this. - - |{kib-repo}blob/{branch}/src/plugins/management/README.md[management] |This plugins contains the "Stack Management" page framework. It offers navigation and an API to link individual managment section into it. This plugin does not contain any individual @@ -145,11 +140,6 @@ management section itself. |Internal objects used by the Coordinate, Region, and Vega visualizations. -|{kib-repo}blob/{branch}/src/plugins/maps_oss/README.md[mapsOss] -|The maps_oss plugin registers the maps visualization on OSS. -It is registered as disabled. The x-pack plugin should unregister this. - - |{kib-repo}blob/{branch}/src/plugins/navigation/README.md[navigation] |The navigation plugins exports the TopNavMenu component. It also provides a stateful version of it on the start contract. @@ -470,6 +460,10 @@ Elastic. |This plugin provides shared components and services for use across observability solutions, as well as the observability landing page UI. +|{kib-repo}blob/{branch}/x-pack/plugins/osquery/README.md[osquery] +|This plugin adds extended support to Security Solution Fleet Osquery integration + + |{kib-repo}blob/{branch}/x-pack/plugins/painless_lab/README.md[painlessLab] |This plugin helps users learn how to use the Painless scripting language. @@ -565,10 +559,6 @@ in their infrastructure. |NOTE: This plugin contains implementation of URL drilldown. For drilldowns infrastructure code refer to ui_actions_enhanced plugin. -|{kib-repo}blob/{branch}/x-pack/plugins/vis_type_timeseries_enhanced/README.md[visTypeTimeseriesEnhanced] -|The vis_type_timeseries_enhanced plugin is the x-pack counterpart to the OSS vis_type_timeseries plugin. - - |{kib-repo}blob/{branch}/x-pack/plugins/watcher/README.md[watcher] |This plugins adopts some conventions in addition to or in place of conventions in Kibana (at the time of the plugin's creation): diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md index ff2a8a2b5f75f..79c603165cae4 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.links.md @@ -30,6 +30,11 @@ readonly links: { readonly metricbeat: { readonly base: string; }; + readonly enterpriseSearch: { + readonly base: string; + readonly appSearchBase: string; + readonly workplaceSearchBase: string; + }; readonly heartbeat: { readonly base: string; }; diff --git a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md index 8404326f773e6..f4bce8b51ebb1 100644 --- a/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md +++ b/docs/development/core/public/kibana-plugin-core-public.doclinksstart.md @@ -17,5 +17,5 @@ export interface DocLinksStart | --- | --- | --- | | [DOC\_LINK\_VERSION](./kibana-plugin-core-public.doclinksstart.doc_link_version.md) | string | | | [ELASTIC\_WEBSITE\_URL](./kibana-plugin-core-public.doclinksstart.elastic_website_url.md) | string | | -| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: string;
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessSyntax: string;
readonly luceneExpressions: string;
};
readonly indexPatterns: {
readonly loadingData: string;
readonly introduction: string;
};
readonly addData: string;
readonly kibana: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly eql: string;
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
executeWatchActionModes: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
putComponentTemplateMetadata: string;
putWatch: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
} | | +| [links](./kibana-plugin-core-public.doclinksstart.links.md) | {
readonly dashboard: {
readonly guide: string;
readonly drilldowns: string;
readonly drilldownsTriggerPicker: string;
readonly urlDrilldownTemplateSyntax: string;
readonly urlDrilldownVariables: string;
};
readonly discover: Record<string, string>;
readonly filebeat: {
readonly base: string;
readonly installation: string;
readonly configuration: string;
readonly elasticsearchOutput: string;
readonly startup: string;
readonly exportedFields: string;
};
readonly auditbeat: {
readonly base: string;
};
readonly metricbeat: {
readonly base: string;
};
readonly enterpriseSearch: {
readonly base: string;
readonly appSearchBase: string;
readonly workplaceSearchBase: string;
};
readonly heartbeat: {
readonly base: string;
};
readonly logstash: {
readonly base: string;
};
readonly functionbeat: {
readonly base: string;
};
readonly winlogbeat: {
readonly base: string;
};
readonly aggs: {
readonly date_histogram: string;
readonly date_range: string;
readonly date_format_pattern: string;
readonly filter: string;
readonly filters: string;
readonly geohash_grid: string;
readonly histogram: string;
readonly ip_range: string;
readonly range: string;
readonly significant_terms: string;
readonly terms: string;
readonly avg: string;
readonly avg_bucket: string;
readonly max_bucket: string;
readonly min_bucket: string;
readonly sum_bucket: string;
readonly cardinality: string;
readonly count: string;
readonly cumulative_sum: string;
readonly derivative: string;
readonly geo_bounds: string;
readonly geo_centroid: string;
readonly max: string;
readonly median: string;
readonly min: string;
readonly moving_avg: string;
readonly percentile_ranks: string;
readonly serial_diff: string;
readonly std_dev: string;
readonly sum: string;
readonly top_hits: string;
};
readonly runtimeFields: string;
readonly scriptedFields: {
readonly scriptFields: string;
readonly scriptAggs: string;
readonly painless: string;
readonly painlessApi: string;
readonly painlessSyntax: string;
readonly luceneExpressions: string;
};
readonly indexPatterns: {
readonly loadingData: string;
readonly introduction: string;
};
readonly addData: string;
readonly kibana: string;
readonly elasticsearch: Record<string, string>;
readonly siem: {
readonly guide: string;
readonly gettingStarted: string;
};
readonly query: {
readonly eql: string;
readonly luceneQuerySyntax: string;
readonly queryDsl: string;
readonly kueryQuerySyntax: string;
};
readonly date: {
readonly dateMath: string;
};
readonly management: Record<string, string>;
readonly ml: Record<string, string>;
readonly transforms: Record<string, string>;
readonly visualize: Record<string, string>;
readonly apis: Readonly<{
createIndex: string;
createSnapshotLifecyclePolicy: string;
createRoleMapping: string;
createRoleMappingTemplates: string;
createApiKey: string;
createPipeline: string;
createTransformRequest: string;
executeWatchActionModes: string;
openIndex: string;
putComponentTemplate: string;
painlessExecute: string;
putComponentTemplateMetadata: string;
putWatch: string;
updateTransform: string;
}>;
readonly observability: Record<string, string>;
readonly alerting: Record<string, string>;
readonly maps: Record<string, string>;
readonly monitoring: Record<string, string>;
readonly security: Readonly<{
apiKeyServiceSettings: string;
clusterPrivileges: string;
elasticsearchSettings: string;
elasticsearchEnableSecurity: string;
indicesPrivileges: string;
kibanaTLS: string;
kibanaPrivileges: string;
mappingRoles: string;
mappingRolesFieldRules: string;
runAsPrivilege: string;
}>;
readonly watcher: Record<string, string>;
readonly ccs: Record<string, string>;
} | | diff --git a/docs/development/core/public/kibana-plugin-core-public.uisettingsparams.md b/docs/development/core/public/kibana-plugin-core-public.uisettingsparams.md index 2cc149e2e2a79..0b7e6467667cb 100644 --- a/docs/development/core/public/kibana-plugin-core-public.uisettingsparams.md +++ b/docs/development/core/public/kibana-plugin-core-public.uisettingsparams.md @@ -26,6 +26,7 @@ export interface UiSettingsParams | [readonly](./kibana-plugin-core-public.uisettingsparams.readonly.md) | boolean | a flag indicating that value cannot be changed | | [requiresPageReload](./kibana-plugin-core-public.uisettingsparams.requirespagereload.md) | boolean | a flag indicating whether new value applying requires page reloading | | [schema](./kibana-plugin-core-public.uisettingsparams.schema.md) | Type<T> | | +| [sensitive](./kibana-plugin-core-public.uisettingsparams.sensitive.md) | boolean | a flag indicating that value might contain user sensitive data. used by telemetry to mask the value of the setting when sent. | | [type](./kibana-plugin-core-public.uisettingsparams.type.md) | UiSettingsType | defines a type of UI element [UiSettingsType](./kibana-plugin-core-public.uisettingstype.md) | | [validation](./kibana-plugin-core-public.uisettingsparams.validation.md) | ImageValidation | StringValidation | | | [value](./kibana-plugin-core-public.uisettingsparams.value.md) | T | default value to fall back to if a user doesn't provide any | diff --git a/docs/development/core/public/kibana-plugin-core-public.uisettingsparams.sensitive.md b/docs/development/core/public/kibana-plugin-core-public.uisettingsparams.sensitive.md new file mode 100644 index 0000000000000..e12f3c5649f17 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-core-public.uisettingsparams.sensitive.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-public](./kibana-plugin-core-public.md) > [UiSettingsParams](./kibana-plugin-core-public.uisettingsparams.md) > [sensitive](./kibana-plugin-core-public.uisettingsparams.sensitive.md) + +## UiSettingsParams.sensitive property + +a flag indicating that value might contain user sensitive data. used by telemetry to mask the value of the setting when sent. + +Signature: + +```typescript +sensitive?: boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.iuisettingsclient.issensitive.md b/docs/development/core/server/kibana-plugin-core-server.iuisettingsclient.issensitive.md new file mode 100644 index 0000000000000..a6f263e0b0f55 --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.iuisettingsclient.issensitive.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [IUiSettingsClient](./kibana-plugin-core-server.iuisettingsclient.md) > [isSensitive](./kibana-plugin-core-server.iuisettingsclient.issensitive.md) + +## IUiSettingsClient.isSensitive property + +Shows whether the uiSetting is a sensitive value. Used by telemetry to not send sensitive values. + +Signature: + +```typescript +isSensitive: (key: string) => boolean; +``` diff --git a/docs/development/core/server/kibana-plugin-core-server.iuisettingsclient.md b/docs/development/core/server/kibana-plugin-core-server.iuisettingsclient.md index af99b5e5bb215..dd4a69c13a2d9 100644 --- a/docs/development/core/server/kibana-plugin-core-server.iuisettingsclient.md +++ b/docs/development/core/server/kibana-plugin-core-server.iuisettingsclient.md @@ -21,6 +21,7 @@ export interface IUiSettingsClient | [getRegistered](./kibana-plugin-core-server.iuisettingsclient.getregistered.md) | () => Readonly<Record<string, PublicUiSettingsParams>> | Returns registered uiSettings values [UiSettingsParams](./kibana-plugin-core-server.uisettingsparams.md) | | [getUserProvided](./kibana-plugin-core-server.iuisettingsclient.getuserprovided.md) | <T = any>() => Promise<Record<string, UserProvidedValues<T>>> | Retrieves a set of all uiSettings values set by the user. | | [isOverridden](./kibana-plugin-core-server.iuisettingsclient.isoverridden.md) | (key: string) => boolean | Shows whether the uiSettings value set by the user. | +| [isSensitive](./kibana-plugin-core-server.iuisettingsclient.issensitive.md) | (key: string) => boolean | Shows whether the uiSetting is a sensitive value. Used by telemetry to not send sensitive values. | | [remove](./kibana-plugin-core-server.iuisettingsclient.remove.md) | (key: string) => Promise<void> | Removes uiSettings value by key. | | [removeMany](./kibana-plugin-core-server.iuisettingsclient.removemany.md) | (keys: string[]) => Promise<void> | Removes multiple uiSettings values by keys. | | [set](./kibana-plugin-core-server.iuisettingsclient.set.md) | (key: string, value: any) => Promise<void> | Writes uiSettings value and marks it as set by the user. | diff --git a/docs/development/core/server/kibana-plugin-core-server.kibanaresponsefactory.md b/docs/development/core/server/kibana-plugin-core-server.kibanaresponsefactory.md index b488baacaff25..d7eafdce017e4 100644 --- a/docs/development/core/server/kibana-plugin-core-server.kibanaresponsefactory.md +++ b/docs/development/core/server/kibana-plugin-core-server.kibanaresponsefactory.md @@ -10,7 +10,7 @@ Set of helpers used to create `KibanaResponse` to form HTTP response on an incom ```typescript kibanaResponseFactory: { - custom: | { + custom: | Buffer | Error | Stream | { message: string | Error; attributes?: Record | undefined; } | undefined>(options: CustomHttpResponseOptions) => KibanaResponse; @@ -21,9 +21,9 @@ kibanaResponseFactory: { conflict: (options?: ErrorHttpResponseOptions) => KibanaResponse; internalError: (options?: ErrorHttpResponseOptions) => KibanaResponse; customError: (options: CustomHttpResponseOptions) => KibanaResponse; - redirected: (options: RedirectResponseOptions) => KibanaResponse>; - ok: (options?: HttpResponseOptions) => KibanaResponse>; - accepted: (options?: HttpResponseOptions) => KibanaResponse>; + redirected: (options: RedirectResponseOptions) => KibanaResponse | Buffer | Stream>; + ok: (options?: HttpResponseOptions) => KibanaResponse | Buffer | Stream>; + accepted: (options?: HttpResponseOptions) => KibanaResponse | Buffer | Stream>; noContent: (options?: HttpResponseOptions) => KibanaResponse; } ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.plugininitializercontext.config.md b/docs/development/core/server/kibana-plugin-core-server.plugininitializercontext.config.md index 4ab0cb74f809f..3b5754eb4fa39 100644 --- a/docs/development/core/server/kibana-plugin-core-server.plugininitializercontext.config.md +++ b/docs/development/core/server/kibana-plugin-core-server.plugininitializercontext.config.md @@ -4,14 +4,17 @@ ## PluginInitializerContext.config property +Accessors for the plugin's configuration + Signature: ```typescript config: { legacy: { globalConfig$: Observable; + get: () => SharedGlobalConfig; }; create: () => Observable; - createIfExists: () => Observable; + get: () => T; }; ``` diff --git a/docs/development/core/server/kibana-plugin-core-server.plugininitializercontext.logger.md b/docs/development/core/server/kibana-plugin-core-server.plugininitializercontext.logger.md index 106fdaad9bc22..e5de046eccf1d 100644 --- a/docs/development/core/server/kibana-plugin-core-server.plugininitializercontext.logger.md +++ b/docs/development/core/server/kibana-plugin-core-server.plugininitializercontext.logger.md @@ -4,8 +4,29 @@ ## PluginInitializerContext.logger property + instance already bound to the plugin's logging context + Signature: ```typescript logger: LoggerFactory; ``` + +## Example + + +```typescript +// plugins/my-plugin/server/plugin.ts +// "id: myPlugin" in `plugins/my-plugin/kibana.yaml` + +export class MyPlugin implements Plugin { + constructor(private readonly initContext: PluginInitializerContext) { + this.logger = initContext.logger.get(); + // `logger` context: `plugins.myPlugin` + this.mySubLogger = initContext.logger.get('sub'); // or this.logger.get('sub'); + // `mySubLogger` context: `plugins.myPlugin.sub` + } +} + +``` + diff --git a/docs/development/core/server/kibana-plugin-core-server.plugininitializercontext.md b/docs/development/core/server/kibana-plugin-core-server.plugininitializercontext.md index 18760170afa1f..90a19d53bd5e1 100644 --- a/docs/development/core/server/kibana-plugin-core-server.plugininitializercontext.md +++ b/docs/development/core/server/kibana-plugin-core-server.plugininitializercontext.md @@ -16,8 +16,8 @@ export interface PluginInitializerContext | Property | Type | Description | | --- | --- | --- | -| [config](./kibana-plugin-core-server.plugininitializercontext.config.md) | {
legacy: {
globalConfig$: Observable<SharedGlobalConfig>;
};
create: <T = ConfigSchema>() => Observable<T>;
createIfExists: <T = ConfigSchema>() => Observable<T | undefined>;
} | | +| [config](./kibana-plugin-core-server.plugininitializercontext.config.md) | {
legacy: {
globalConfig$: Observable<SharedGlobalConfig>;
get: () => SharedGlobalConfig;
};
create: <T = ConfigSchema>() => Observable<T>;
get: <T = ConfigSchema>() => T;
} | Accessors for the plugin's configuration | | [env](./kibana-plugin-core-server.plugininitializercontext.env.md) | {
mode: EnvironmentMode;
packageInfo: Readonly<PackageInfo>;
instanceUuid: string;
} | | -| [logger](./kibana-plugin-core-server.plugininitializercontext.logger.md) | LoggerFactory | | +| [logger](./kibana-plugin-core-server.plugininitializercontext.logger.md) | LoggerFactory | instance already bound to the plugin's logging context | | [opaqueId](./kibana-plugin-core-server.plugininitializercontext.opaqueid.md) | PluginOpaqueId | | diff --git a/docs/development/core/server/kibana-plugin-core-server.uisettingsparams.md b/docs/development/core/server/kibana-plugin-core-server.uisettingsparams.md index 4dfde5200e7e9..d35afc4a149d1 100644 --- a/docs/development/core/server/kibana-plugin-core-server.uisettingsparams.md +++ b/docs/development/core/server/kibana-plugin-core-server.uisettingsparams.md @@ -26,6 +26,7 @@ export interface UiSettingsParams | [readonly](./kibana-plugin-core-server.uisettingsparams.readonly.md) | boolean | a flag indicating that value cannot be changed | | [requiresPageReload](./kibana-plugin-core-server.uisettingsparams.requirespagereload.md) | boolean | a flag indicating whether new value applying requires page reloading | | [schema](./kibana-plugin-core-server.uisettingsparams.schema.md) | Type<T> | | +| [sensitive](./kibana-plugin-core-server.uisettingsparams.sensitive.md) | boolean | a flag indicating that value might contain user sensitive data. used by telemetry to mask the value of the setting when sent. | | [type](./kibana-plugin-core-server.uisettingsparams.type.md) | UiSettingsType | defines a type of UI element [UiSettingsType](./kibana-plugin-core-server.uisettingstype.md) | | [validation](./kibana-plugin-core-server.uisettingsparams.validation.md) | ImageValidation | StringValidation | | | [value](./kibana-plugin-core-server.uisettingsparams.value.md) | T | default value to fall back to if a user doesn't provide any | diff --git a/docs/development/core/server/kibana-plugin-core-server.uisettingsparams.sensitive.md b/docs/development/core/server/kibana-plugin-core-server.uisettingsparams.sensitive.md new file mode 100644 index 0000000000000..f2c7de19dde1a --- /dev/null +++ b/docs/development/core/server/kibana-plugin-core-server.uisettingsparams.sensitive.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-core-server](./kibana-plugin-core-server.md) > [UiSettingsParams](./kibana-plugin-core-server.uisettingsparams.md) > [sensitive](./kibana-plugin-core-server.uisettingsparams.sensitive.md) + +## UiSettingsParams.sensitive property + +a flag indicating that value might contain user sensitive data. used by telemetry to mask the value of the setting when sent. + +Signature: + +```typescript +sensitive?: boolean; +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs._constructor_.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs._constructor_.md index c9e08b9712480..6ca7a1a88b30e 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs._constructor_.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.aggconfigs._constructor_.md @@ -15,11 +15,11 @@ constructor(indexPattern: IndexPattern, configStates: Pick & Pick<{ + }, "schema" | "enabled" | "id" | "params"> & Pick<{ type: string | IAggType; }, "type"> & Pick<{ type: string | IAggType; - }, never>, "enabled" | "type" | "schema" | "id" | "params">[] | undefined, opts: AggConfigsOptions); + }, never>, "schema" | "type" | "enabled" | "id" | "params">[] | undefined, opts: AggConfigsOptions); ``` ## Parameters @@ -27,6 +27,6 @@ constructor(indexPattern: IndexPattern, configStates: PickIndexPattern | | -| configStates | Pick<Pick<{
type: string;
enabled?: boolean | undefined;
id?: string | undefined;
params?: {} | import("./agg_config").SerializableState | undefined;
schema?: string | undefined;
}, "enabled" | "schema" | "id" | "params"> & Pick<{
type: string | IAggType;
}, "type"> & Pick<{
type: string | IAggType;
}, never>, "enabled" | "type" | "schema" | "id" | "params">[] | undefined | | +| configStates | Pick<Pick<{
type: string;
enabled?: boolean | undefined;
id?: string | undefined;
params?: {} | import("./agg_config").SerializableState | undefined;
schema?: string | undefined;
}, "schema" | "enabled" | "id" | "params"> & Pick<{
type: string | IAggType;
}, "type"> & Pick<{
type: string | IAggType;
}, never>, "schema" | "type" | "enabled" | "id" | "params">[] | undefined | | | opts | AggConfigsOptions | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md index 65a722868b37f..4bbc76b78ba03 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.md @@ -125,6 +125,7 @@ | [isPartialResponse](./kibana-plugin-plugins-data-public.ispartialresponse.md) | | | [isQuery](./kibana-plugin-plugins-data-public.isquery.md) | | | [isTimeRange](./kibana-plugin-plugins-data-public.istimerange.md) | | +| [noSearchSessionStorageCapabilityMessage](./kibana-plugin-plugins-data-public.nosearchsessionstoragecapabilitymessage.md) | Message to display in case storing session session is disabled due to turned off capability | | [parseSearchSourceJSON](./kibana-plugin-plugins-data-public.parsesearchsourcejson.md) | | | [QueryStringInput](./kibana-plugin-plugins-data-public.querystringinput.md) | | | [search](./kibana-plugin-plugins-data-public.search.md) | | diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.nosearchsessionstoragecapabilitymessage.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.nosearchsessionstoragecapabilitymessage.md new file mode 100644 index 0000000000000..2bb0a0db8f9b3 --- /dev/null +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.nosearchsessionstoragecapabilitymessage.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-data-public](./kibana-plugin-plugins-data-public.md) > [noSearchSessionStorageCapabilityMessage](./kibana-plugin-plugins-data-public.nosearchsessionstoragecapabilitymessage.md) + +## noSearchSessionStorageCapabilityMessage variable + +Message to display in case storing session session is disabled due to turned off capability + +Signature: + +```typescript +noSearchSessionStorageCapabilityMessage: string +``` diff --git a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md index 2fd84730957b6..83fbc00860ca5 100644 --- a/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md +++ b/docs/development/plugins/data/public/kibana-plugin-plugins-data-public.searchbar.md @@ -7,7 +7,7 @@ Signature: ```typescript -SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "indexPatterns" | "dataTestSubj" | "refreshInterval" | "screenTitle" | "onRefresh" | "onRefreshChange" | "showQueryInput" | "showDatePicker" | "showAutoRefreshOnly" | "dateRangeFrom" | "dateRangeTo" | "isRefreshPaused" | "customSubmitButton" | "timeHistory" | "indicateNoData" | "onFiltersUpdated" | "trackUiMetric" | "savedQuery" | "showSaveQuery" | "onClearSavedQuery" | "showQueryBar" | "showFilterBar" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated">, any> & { - WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; +SearchBar: React.ComponentClass, "query" | "isLoading" | "indexPatterns" | "filters" | "dataTestSubj" | "refreshInterval" | "screenTitle" | "onRefresh" | "onRefreshChange" | "showQueryInput" | "showDatePicker" | "showAutoRefreshOnly" | "dateRangeFrom" | "dateRangeTo" | "isRefreshPaused" | "customSubmitButton" | "timeHistory" | "indicateNoData" | "onFiltersUpdated" | "trackUiMetric" | "savedQuery" | "showSaveQuery" | "onClearSavedQuery" | "showQueryBar" | "showFilterBar" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated">, any> & { + WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; } ``` diff --git a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md index af7abb076d7ef..ea3ba28a52def 100644 --- a/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md +++ b/docs/development/plugins/data/server/kibana-plugin-plugins-data-server.plugin.start.md @@ -12,7 +12,7 @@ start(core: CoreStart): { fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }; @@ -31,7 +31,7 @@ start(core: CoreStart): { fieldFormatServiceFactory: (uiSettings: import("../../../core/server").IUiSettingsClient) => Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }` diff --git a/docs/development/plugins/kibana_utils/public/state_sync/kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.cancel.md b/docs/development/plugins/kibana_utils/public/state_sync/kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.cancel.md deleted file mode 100644 index 29a511d57d7bd..0000000000000 --- a/docs/development/plugins/kibana_utils/public/state_sync/kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.cancel.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-kibana\_utils-public-state\_sync](./kibana-plugin-plugins-kibana_utils-public-state_sync.md) > [IKbnUrlStateStorage](./kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.md) > [cancel](./kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.cancel.md) - -## IKbnUrlStateStorage.cancel property - -cancels any pending url updates - -Signature: - -```typescript -cancel: () => void; -``` diff --git a/docs/development/plugins/kibana_utils/public/state_sync/kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.flush.md b/docs/development/plugins/kibana_utils/public/state_sync/kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.flush.md deleted file mode 100644 index dfeef1cdce22c..0000000000000 --- a/docs/development/plugins/kibana_utils/public/state_sync/kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.flush.md +++ /dev/null @@ -1,15 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-plugins-kibana\_utils-public-state\_sync](./kibana-plugin-plugins-kibana_utils-public-state_sync.md) > [IKbnUrlStateStorage](./kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.md) > [flush](./kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.flush.md) - -## IKbnUrlStateStorage.flush property - -Synchronously runs any pending url updates, returned boolean indicates if change occurred. - -Signature: - -```typescript -flush: (opts?: { - replace?: boolean; - }) => boolean; -``` diff --git a/docs/development/plugins/kibana_utils/public/state_sync/kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.kbnurlcontrols.md b/docs/development/plugins/kibana_utils/public/state_sync/kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.kbnurlcontrols.md new file mode 100644 index 0000000000000..8e3b9a7bfeb3f --- /dev/null +++ b/docs/development/plugins/kibana_utils/public/state_sync/kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.kbnurlcontrols.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-plugins-kibana\_utils-public-state\_sync](./kibana-plugin-plugins-kibana_utils-public-state_sync.md) > [IKbnUrlStateStorage](./kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.md) > [kbnUrlControls](./kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.kbnurlcontrols.md) + +## IKbnUrlStateStorage.kbnUrlControls property + +Lower level wrapper around history library that handles batching multiple URL updates into one history change + +Signature: + +```typescript +kbnUrlControls: IKbnUrlControls; +``` diff --git a/docs/development/plugins/kibana_utils/public/state_sync/kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.md b/docs/development/plugins/kibana_utils/public/state_sync/kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.md index 371f7b7c15362..7fb8717fae003 100644 --- a/docs/development/plugins/kibana_utils/public/state_sync/kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.md +++ b/docs/development/plugins/kibana_utils/public/state_sync/kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.md @@ -20,9 +20,8 @@ export interface IKbnUrlStateStorage extends IStateStorage | Property | Type | Description | | --- | --- | --- | -| [cancel](./kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.cancel.md) | () => void | cancels any pending url updates | | [change$](./kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.change_.md) | <State = unknown>(key: string) => Observable<State | null> | | -| [flush](./kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.flush.md) | (opts?: {
replace?: boolean;
}) => boolean | Synchronously runs any pending url updates, returned boolean indicates if change occurred. | | [get](./kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.get.md) | <State = unknown>(key: string) => State | null | | +| [kbnUrlControls](./kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.kbnurlcontrols.md) | IKbnUrlControls | Lower level wrapper around history library that handles batching multiple URL updates into one history change | | [set](./kibana-plugin-plugins-kibana_utils-public-state_sync.ikbnurlstatestorage.set.md) | <State>(key: string, state: State, opts?: {
replace: boolean;
}) => Promise<string | undefined> | | diff --git a/docs/maps/maps-aggregations.asciidoc b/docs/maps/maps-aggregations.asciidoc index f09d99250c22d..3c66e187bf59c 100644 --- a/docs/maps/maps-aggregations.asciidoc +++ b/docs/maps/maps-aggregations.asciidoc @@ -98,6 +98,13 @@ Point to point uses an {es} {ref}/search-aggregations-bucket-terms-aggregation.h Then, a nested {ref}/search-aggregations-bucket-geotilegrid-aggregation.html[GeoTile grid aggregation] groups sources for each destination into grids. A line connects each source grid centroid to each destination. +Point-to-point layers are used in several common use cases: + +* Source-destination maps for network traffic +* Origin-destination maps for flight data +* Origin-destination flows for import/export/migration +* Origin-destination for pick-up/drop-off data + image::maps/images/point_to_point.png[] [role="xpack"] diff --git a/docs/redirects.asciidoc b/docs/redirects.asciidoc index 4449b58b4bab3..c7bdff800bb0b 100644 --- a/docs/redirects.asciidoc +++ b/docs/redirects.asciidoc @@ -194,10 +194,89 @@ This page has moved. Refer to <>. This page has moved. Refer to <>. +[float] +[[time-series-visual-builder]] +=== Time Series Visual Builder + +This page was deleted. Refer to <>. + +[float] +[[kibana-keystore-has-moved-from-the-data-folder-to-the-config-folder]] +=== Kibana Keystore has moved from the Data Folder to the Config Folder + +This page has been deleted. Refer to link:https://www.elastic.co/guide/en/kibana/7.9/breaking-changes-7.9.html#user-facing-changes-79[Breaking changes in 7.9]. + +[float] +[[createvis]] +=== Create Visualization + +This page has been deleted. Refer to <>. + +[float] +[[data-table]] +=== Data Table + +This page has been deleted. Refer to <>. + + +[float] +[[xy-chart]] +=== Line, Area, and Bar Chart + +This page has been deleted. Refer to <>. + +[float] +[[add-canvas-events]] +=== Add Canvas Elements + +This page has been moved. Refer to <>. + +[float] +[[vega-lite-tutorial]] +=== Vega-Lite Tutorial + +This page has been moved. Refer to <>. + +[float] +[[heatmap-chart]] +=== Heatmap Chart + +This page has been moved. Refer to <>. + +[float] +[[interface-overview]] +=== Interface Overview + +This page has been moved. Refer to <>. + +[float] +[[time-series-visualizations]] +=== Featured Visualizations + +This page has been moved. Refer to <>. + +[float] +[[timelion-customize]] +=== Customize and format visualizations + +This page has been moved. Refer to <>. + +[float] +[[dashboard-drilldown]] +=== Dashboard Drilldowns + +This page has been moved. Refer to <>. + +[float] +[[development-plugin-localization]] +=== Localization for plugins + +This page has been moved. PRefer to <>. + [role="exclude",id="visualize"] == Visualize -This content has moved. See <>. +This content has moved. Refer to <>. [role="exclude",id="explore-dashboard-data"] -This content has moved. See <>. +This content has moved. Refer to <>. diff --git a/docs/user/alerting/action-types/webhook.asciidoc b/docs/user/alerting/action-types/webhook.asciidoc index 659c3afad6bd1..fff6814325ea4 100644 --- a/docs/user/alerting/action-types/webhook.asciidoc +++ b/docs/user/alerting/action-types/webhook.asciidoc @@ -72,4 +72,17 @@ Password:: An optional password. If set, HTTP basic authentication is used. Cur Webhook actions have the following properties: -Body:: A json payload sent to the request URL. +Body:: A JSON payload sent to the request URL. For example: ++ +[source,text] +-- +{ + "short_description": "{{context.rule.name}}", + "description": "{{context.rule.description}}", + ... +} +-- + +Mustache template variables (the text enclosed in double braces, for example, `context.rule.name`) have +their values escaped, so that the final JSON will be valid (escaping double quote characters). +For more information on Mustache template variables, refer to <>. diff --git a/docs/user/dashboard/vega-reference.asciidoc b/docs/user/dashboard/vega-reference.asciidoc index 99c5e62697cfd..2c961dca44474 100644 --- a/docs/user/dashboard/vega-reference.asciidoc +++ b/docs/user/dashboard/vega-reference.asciidoc @@ -223,7 +223,7 @@ experimental[] Access the Elastic Map Service files via the same mechanism: ---- url: { // "type" defaults to "elasticsearch" otherwise - type: emsfile + %type%: emsfile // Name of the file, exactly as in the Region map visualization name: World Countries } @@ -237,7 +237,7 @@ format: {property: "features"} [[vega-with-a-map]] ==== Vega with a Map -To enable *Maps*, the graph must specify `type=map` in the host configuration: +experimental[] To enable *Maps*, the graph must specify `type=map` in the host configuration: [source,yaml] ---- @@ -289,7 +289,7 @@ experimental[] You can use the *Vega* https://vega.github.io/vega/docs/data/[dat ---- url: { // "type" defaults to "elasticsearch" otherwise - type: emsfile + %type%: emsfile // Name of the file, exactly as in the Region map visualization name: World Countries } @@ -335,7 +335,7 @@ Use the contextual *Inspect* tool to gain insights into different elements. [float] [[inspect-elasticsearch-requests]] -======= Inspect {es} requests +====== Inspect {es} requests *Vega* uses the {ref}/search-search.html[{es} search API] to get documents and aggregation results from {es}. To troubleshoot these requests, click *Inspect*, which shows the most recent requests. diff --git a/docs/user/images/alerts-and-actions.png b/docs/user/images/alerts-and-actions.png new file mode 100755 index 0000000000000..227abd9441e15 Binary files /dev/null and b/docs/user/images/alerts-and-actions.png differ diff --git a/docs/user/images/app-navigation-search.png b/docs/user/images/app-navigation-search.png new file mode 100644 index 0000000000000..3b89eed44b28f Binary files /dev/null and b/docs/user/images/app-navigation-search.png differ diff --git a/docs/user/images/features-control.png b/docs/user/images/features-control.png new file mode 100755 index 0000000000000..abe75d5ab6fc1 Binary files /dev/null and b/docs/user/images/features-control.png differ diff --git a/docs/user/images/home-page.png b/docs/user/images/home-page.png new file mode 100755 index 0000000000000..9ca4b7f43f427 Binary files /dev/null and b/docs/user/images/home-page.png differ diff --git a/docs/user/images/kibana-main-menu.png b/docs/user/images/kibana-main-menu.png new file mode 100755 index 0000000000000..79e0a3dca8658 Binary files /dev/null and b/docs/user/images/kibana-main-menu.png differ diff --git a/docs/user/images/login-screen.png b/docs/user/images/login-screen.png new file mode 100755 index 0000000000000..7a97c952e1039 Binary files /dev/null and b/docs/user/images/login-screen.png differ diff --git a/docs/user/images/roles-and-privileges.png b/docs/user/images/roles-and-privileges.png new file mode 100755 index 0000000000000..28bff6d13c871 Binary files /dev/null and b/docs/user/images/roles-and-privileges.png differ diff --git a/docs/user/images/select-your-space.png b/docs/user/images/select-your-space.png new file mode 100755 index 0000000000000..887e8eea27c5c Binary files /dev/null and b/docs/user/images/select-your-space.png differ diff --git a/docs/user/images/tags-search.png b/docs/user/images/tags-search.png new file mode 100755 index 0000000000000..67458200c50d1 Binary files /dev/null and b/docs/user/images/tags-search.png differ diff --git a/docs/user/images/visualization-journey.png b/docs/user/images/visualization-journey.png new file mode 100644 index 0000000000000..ef7634485bccd Binary files /dev/null and b/docs/user/images/visualization-journey.png differ diff --git a/docs/user/introduction.asciidoc b/docs/user/introduction.asciidoc index a41b42f259471..fb91f6a6a1c9a 100644 --- a/docs/user/introduction.asciidoc +++ b/docs/user/introduction.asciidoc @@ -1,145 +1,444 @@ [[introduction]] -== {kib} — your window into the Elastic Stack +== {kib}—your window into Elastic ++++ What is Kibana? ++++ -**_Visualize and analyze your data and manage all things Elastic Stack._** +{kib} enables you to give +shape to your data and navigate the Elastic Stack. With {kib}, you can: -Whether you’re an analyst or an admin, {kib} makes your data actionable by providing -three key functions. Kibana is: +* *Visualize and analyze your data.* +Search for hidden insights, visualize what you've found in charts, gauges, +maps and more, and combine them in a dashboard. -* **An open-source analytics and visualization platform.** -Use {kib} to explore your {es} data, and then build beautiful visualizations and dashboards. +* *Search, observe, and protect.* +From discovering documents to analyzing logs to finding security vulnerabilities, +{kib} is your portal for accessing these capabilities and more. -* **A UI for managing the Elastic Stack.** -Manage your security settings, assign user roles, take snapshots, roll up your data, -and more — all from the convenience of a {kib} UI. +* *Manage, monitor, and secure the Elastic Stack.* +Manage your indices and ingest pipelines, monitor the health of your +Elastic Stack cluster, and control which users have access to +which features. -* **A centralized hub for Elastic's solutions.** From log analytics to -document discovery to SIEM, {kib} is the portal for accessing these and other capabilities. -[role="screenshot"] -image::images/intro-kibana.png[Kibana home page] +*{kib} is for administrators, analysts, and business users.* +As an admin, your role is to manage the Elastic Stack, from creating your +deployment to getting {es} data into {kib}, and then +managing the data. As an analyst, your job is to discover insights +in the data, visualize your data on dashboards, and share your findings. As a business user, +you want to view existing dashboards and drill down into details. + +*{kib} works with all types of data.* Your data can be structured or unstructured text, +numerical data, time-series data, geospatial data, logs, metrics, security events, +and more. Kibana is designed to use Elasticsearch as a data store. +No matter your data, {kib} can help you uncover patterns and relationships and visualize the results. [float] -[[get-data-into-kibana]] -=== Ingest data +[[kibana-home-page]] +=== Where to start + +Start with the home page, where you’re guided toward the most common use cases. +For a quick reference of {kib} use cases, refer to <> -{kib} is designed to use {es} as a data source. Think of Elasticsearch as the engine that stores -and processes the data, with {kib} sitting on top. +[role="screenshot"] +image::images/home-page.png[Kibana home page] + +The main menu gets you to where you need to go. Like the home page, +the menu is organized by use case. Want to work with your logs, metrics, APM, or +Uptime data? The apps you need are under *Observability*. The main menu also includes +*Recently viewed*, so you can easily access your previously opened apps. -To start working with your data in Kibana, use one of the many ingest options, -available from the home page. You can collect data from an app or service or upload a file that contains your data. -If you're not ready to use your own data, you can add a sample data set -to give {kib} a test drive. +Hidden by default, you open the main menu by clicking the +hamburger icon. To keep the main menu visible at all times, click the *Dock navigation* item. [role="screenshot"] -image::setup/images/add-data-home.png[Built-in options for adding data to Kibana: Add data, Add Elastic Agent, Upload a file] +image::images/kibana-main-menu.png[Kibana main menu] [float] -[[explore-and-query]] -=== Explore & query +[[kibana-navigation-search]] +=== Search {kib} + +Using the Search field in the global header, you can +search for applications and objects, such as +dashboards and visualizations. + +Search suggestions include deep links into applications, +allowing you to directly navigate to the views you need most. -Ready to dive into your data? With <>, you can explore your data and -search for hidden insights and relationships. Ask your questions, and then -narrow the results to just the data you want. +[role="screenshot"] +image::images/app-navigation-search.png[Example of searching for apps] + +When searching for objects, you can search by type, name, and tag. +Tags are keywords or labels that you assign to {kib} objects, +so you can classify the objects in a way that is meaningful to you. +You can then quickly search for related objects based on shared tags. [role="screenshot"] -image::images/intro-discover.png[Discover UI] +image::images/tags-search.png[Example of searching for tags] + +To get the most from the search feature, follow these tips: + +* Use the keyboard shortcut—Ctrl+/ on Windows and Linux, Command+/ on MacOS—to focus on the input at any time. + +* Use the provided syntax keywords. ++ +[cols=2*] +|=== +|Search by type +|`type:dashboard` + +Available types: `application`, `canvas-workpad`, `dashboard`, `index-pattern`, `lens`, `maps`, `query`, `search`, `visualization` + +|Search by tag +|`tag:mytagname` + +`tag:"tag name with spaces"` + +|Search by type and name +|`type:dashboard my_dashboard_title` + +|Advanced searches +|`tag:(tagname1 or tagname2) my_dashboard_title` + +`type:lens tag:(tagname1 or tagname2)` + +`type:(dashboard or canvas-workpad) logs` + +|=== + [float] [[visualize-and-analyze]] -=== Visualize & analyze +=== Analyze your data -A visualization is worth a thousand log lines, and {kib} provides -many options for showcasing your data. Use <>, -our drag-and-drop interface, -to rapidly build -charts, tables, metrics, and more. If there -is a better visualization for your data, *Lens* suggests it, allowing for quick -switching between visualization types. - -Once your visualizations are just the way you want, -use <> to collect them in one place. A dashboard provides -insights into your data from multiple perspectives. +Data analysis is the core functionality of {kib}. +You can quickly search through large amounts of data, explore fields and values, +and then use {kib}’s drag-and-drop interface to rapidly build charts, tables, metrics, and more. [role="screenshot"] -image::images/intro-dashboard.png[Sample eCommerce data set dashboard] +image::images/visualization-journey.png[User visualization journey] + +[[get-data-into-kibana]] +. *Add data.* The best way to add {es} data to {kib} is to use one of our guided processes, +available from the <>. You can collect data from an app or service, upload a +file, or add a sample data set. -{kib} also offers these visualization features: +. *Explore.* With <>, you can search your data for hidden +insights and relationships. Ask your questions, and then filter the results to just the data you want. +You can also limit your results to the most recent documents added to {es}. -* <> gives you the ability to present your data in a -visually compelling, pixel-perfect report. Give your data the “wow” factor -needed to impress your CEO or to captivate coworkers with a big-screen display. +. *Visualize.* {kib} provides many options to create visualizations of your data, from +aggregation-based data to time series data. +<> is your starting point to create visualizations, +and then pulling them together to show your data from multiple perspectives. -* <> enables you to ask (and answer) meaningful -questions of your location-based data. Maps supports multiple -layers and data sources, mapping of individual geo points and shapes, -and dynamic client-side styling. +. *Present.* With <>, you can display your data on a visually +compelling, pixel-perfect workpad. **Canvas** can give your data +the “wow” factor needed to impress your CEO and captivate coworkers with a big-screen display. -* <> allows you to combine -an infinite number of aggregations to display complex data. -With TSVB, you can customize -every aspect of your visualization. Choose your own date format and color -gradients, and easily switch your data view between time series, metric, -top N, gauge, and markdown. +. *Share.* Ready to <> your findings with a larger audience? {kib} offers many options—embed +a dashboard, share a link, export to PDF, and more. [float] -[[organize-and-secure]] -=== Organize & secure +==== Plot location data on a map +If you’re looking to better understand the “where’’ in your data, your data +analysis journey will also include <>. This app is the right +choice when you’re looking for a spatial pattern, performing ad-hoc location-driven analysis, +or analyzing metrics with a geographic perspective. With *Maps*, you can build +world country maps, administrative region maps, and point-to-point origin-destination maps. +You can also visualize and track movement over space and through time. -Want to share Kibana’s goodness with other people or teams? You can do so with -<>, built for organizing your visualizations, dashboards, and indices. -Think of a space as its own mini {kib} installation — it’s isolated from -all other spaces, so you can tailor it to your specific needs without impacting others. +[float] +==== Model data behavior -You can even choose which features to enable within each space. Don’t need -Machine learning in your “Executive” space? Simply turn it off. +To model the behavior of your data, you'll want to use +<>. +This app can help you extract insights from your data that you might otherwise miss. +You can forecast unusual behavior in your time series data. +You can also perform outlier detection, regression, and classification analysis +on your data and generate annotated results. -[role="screenshot"] -image::images/intro-spaces.png[Space selector screen] +[float] +==== Graph relationships -You can take this all one step further with Kibana’s security features, and -control which users have access to each space. {kib} allows for fine-grained -controls, so you can give a user read-only access to -dashboards in one space, but full access to all of Kibana’s features in another. +Looking to uncover how items in your data are related? +<> is your app. Graphing relationships is useful in a variety of use cases, +from fraud detection to recommendation engines. For example, graph exploration +can help you uncover website vulnerabilities that hackers are targeting, +so you can harden your website. Or, you might provide graph-based +personalized recommendations to your e-commerce customers. + +[float] +[[extend-your-use-case]] +=== Search, observe, and protect + +Being able to search, observe, and protect your data is a requirement for any analyst. +{kib} provides solutions for each of these use cases. + +* https://www.elastic.co/guide/en/enterprise-search/current/index.html[*Enterprise Search*] enables you to create a search experience for your app, workplace, and website. + +* {observability-guide}/observability-introduction.html[*Elastic Observability*] enables you to monitor and apply analytics in real time +to events happening across all your environments. You can analyze log events, monitor the performance metrics for the host or container +that it ran in, trace the transaction, and check the overall service availability. + +* Designed for security analysts, {security-guide}/es-overview.html[*Elastic Security*] provides an overview of +the events and alerts from your environment. Elastic Security helps you defend +your organization from threats before damage and loss occur. ++ +[role="screenshot"] +image::siem/images/detections-ui.png[] [float] [[manage-all-things-stack]] === Manage all things Elastic Stack -<> provides guided processes for managing all -things Elastic Stack — indices, clusters, licenses, UI settings, -and more. Want to update your {es} indices? Set user roles and privileges? -Turn on dark mode? Kibana has UIs for all that. +{kib}'s <> takes you under the hood, +so you can twist the levers and turn the knobs. *Stack Management* provides +guided processes for administering all things Elastic Stack, +including data, indices, clusters, alerts, and security. [role="screenshot"] image::images/intro-management.png[] [float] -[[extend-your-use-case]] -=== Extend your use case — or add a new one +==== Manage your data, indices, and clusters + +{kib} offers these data management tasks—all from the convenience of a UI: + +* Refresh, flush, and clear the cache of your indices. +* Define the lifecycle of an index as it ages. +* Define a policy for taking snapshots of your cluster. +* Roll up data from one or more indices into a new, compact index. +* Replicate indices on a remote cluster and copy them to a local cluster. + +[float] +==== Alert and take action +Detecting and acting on significant shifts and signals in your data is a need +that exists in almost every use case. For example, you might set an alert to notify you when: -As a hub for Elastic's https://www.elastic.co/products/[solutions], {kib} -can help you find security vulnerabilities, -monitor performance, and address your business needs. Get alerted if a key -metric spikes. Detect anomalous behavior or forecast future spikes. Root out -bottlenecks in your application code. Kibana doesn’t limit or dictate how you explore your data. +* A shift occurs in your business critical KPIs. +* System resources, such as memory, CPU and disk space, take a dip. +* An unusually high number of service requests, suspicious processes, and login attempts occurs. + +An alert is triggered when a specified condition is met. For example, +an alert might trigger when the average or max of one of +your metrics exceeds a threshold within a specified time frame. + +When the alert triggers, you can send a notification to a system that is part of +your daily workflow. {kib} integrates with email, Slack, PagerDuty, and ServiceNow, +to name a few. + +A dedicated view for creating, searching, and editing alerts is in <>. [role="screenshot"] -image::siem/images/detections-ui.png[] +image::images/alerts-and-actions.png[Alerts and Actions view] + [float] -[[try-kibana]] -=== Give {kib} a try +[[organize-and-secure]] +=== Organize your work in spaces + +Want to share {kib}’s goodness with other people or teams without overwhelming them? You can do so +with <>, built for organizing your visualizations, dashboards, and indices. +Think of a space as its own mini {kib} installation—it’s isolated from all other spaces, +so you can tailor it to your specific needs without impacting others. + +[role="screenshot"] +image::images/select-your-space.png[Space selector screen] + +Most of {kib}’s entities are space-aware, including dashboards, visualizations, index patterns, +Canvas workpads, Timelion visualizations, graphs, tags, and machine learning jobs. + +In addition: + +* **Elastic Security** is space-aware, so the timelines and investigations +you open in one space will not be available to other spaces. + +* **Observability** is currently partially space-aware, but will be enhanced to become fully space-aware. + +* Most of the **Stack Management** features are not space aware because they +are primarily used to manage features of {es}, which serves as a shared data store for all spaces. + +* Alerts are space-aware and work nicely with the {kib} role-based access control +model to allow you secure access to them, depending on the alert type and your user roles. +For example, roles with no access to an app will not have access to its alerts. + +[float] +==== Control feature visibility + +You can take spaces one step further and control which features are visible +within each space. For example, you might hide **Dev Tools** in your "Executive" +space or show **Stack Monitoring** only in your "Admin" space. + +Controlling feature visibility is not a security feature. To secure access +to specific features on a per-user basis, you must configure +<>. + +[role="screenshot"] +image::images/features-control.png[Features Controls screen] -There is no faster way to try out {kib} than with our hosted {es} Service. -https://www.elastic.co/cloud/elasticsearch-service/signup[Sign up for a free trial] -and start exploring data in minutes. +[float] +[[intro-kibana-Security]] +=== Secure {kib} + +{kib} offers a range of security features for you to control who has access to what. +The security features are automatically turned on when +{ref}/get-started-enable-security.html[security is enabled in +{es}]. For a description of all available configuration options, +see <>. + +[float] +==== Log in +Kibana supports several <>, +allowing you to login using {es}’s built-in realms, or by your own single sign-on provider. + +[role="screenshot"] +image::images/login-screen.png[Login screen] + +[float] +==== Secure access + +{kib} provides roles and privileges for controlling which users can +view and manage {kib} features. Privileges grant permission to view an application +or perform a specific action and are assigned to roles. Roles allow you to describe +a “template” of capabilities that you can grant to many users, +without having to redefine what each user should be able to do. + +When you create a role, you can scope the assigned {kib} privileges to specific spaces. +This makes it possible to grant users different access levels in different spaces, +or even give users their very own private space. For example, power users might +have privileges to create and edit visualizations and dashboards, +while analysts or executives might have *Dashboard* and *Canvas* with read-only privileges. + +{kib}’s role management interface allows you to describe these various access +levels, or you can automate role creation via our <>. + +[role="screenshot"] +image::images/roles-and-privileges.png[{kib privileges}] + +[float] +==== Audit access + +Once you have your users and roles configured, you might want to maintain a +record of who did what, when. The {kib} audit log will record this information for you, +which can then be correlated with {es} audit logs to gain more insights into your +users’ behavior. For more information, see <>. + +[float] +[[whats-the-right-app]] +=== What’s the right app for you? + +{kib} has a wealth of apps, each with its own area of specialty. +Scan this table to quickly find the app that gets you to our goal. + +[cols=2*] +|=== + +2+| *Get started* + +|Get {kib} +|https://www.elastic.co/cloud/elasticsearch-service/signup[Sign up for a free trial] and start exploring data in minutes. + +|Don’t know where to begin +|The home page. If you’re looking to explore and visualize your data, follow +the <>. + +|Add data +|The Add data page, available from the home page. + +|See the full list of {kib} features +|The https://www.elastic.co/kibana/features[{kib} features page on elastic.co] + +2+| *Analyze and visualize your data* + +|Know what’s in your data +|<> + +|Create charts and other visualizations +|<> + +|Show your data from different perspectives +|<> + +|Work with location data +|<> + +|Create a presentation of your data +|<> + +|Generate models for your data’s behavior +|<> + +|Explore connections in your data +|<> + +|Share your data +|<>, <> + +2+|*Build a search experience* + +|Create a search experience for your workplace +|https://www.elastic.co/guide/en/workplace-search/current/workplace-search-getting-started.html[Workplace Search] + +|Build a search experience for your app +|https://www.elastic.co/guide/en/app-search/current/getting-started.html[App Search] + + +2+|*Monitor, analyze, and react to events* + +|Monitor software services and applications in real-time by collecting performance information +|{observability-guide}/apm.html[APM] + +|Monitor the availability of your sites and services +|{observability-guide}/monitor-uptime.html[Uptime] + +|Search, filter, and tail all your logs +|{observability-guide}/monitor-logs.html[Logs] + +|Analyze metrics from your infrastructure, apps, and services +|{observability-guide}/analyze-metrics.html[Metrics] + +2+|*Prevent, detect, and respond to threats* + +|Create and manage rules for suspicious source events, and view the alerts these rules create. +|{security-guide}/detection-engine-overview.html[Detections] + +|View all hosts and host-related security events. +|{security-guide}/hosts-overview.html[Hosts] + +|View key network activity metrics via an interactive map. +|{security-guide}/network-page-overview.html[Network] + +|Investigate alerts and complex threats, such as lateral movement of malware across hosts in your network. +|{security-guide}/timelines-ui.html[Timelines] + +|Create and track security issues +|{security-guide}/cases-overview.html[Cases] + +|View and manage hosts that are running Endpoint Security +|{security-guide}/admin-page-ov.html[Administration] + +2+|*Administer your Kibana instance* + +|Manage your Elasticsearch data +|< Data>> + +|Set up alerts +|< Alerts and Actions>> + +|Organize your workspace and users +|< Spaces>> + +|Define user roles and privileges +|< Users>> + +|Customize {kib} to suit your needs +|< Advanced Settings>> + +|=== + +[float] +[[try-kibana]] +=== Getting help -You can also <> — no code, no additional -infrastructure required. +Using our in-product guidance can help you get up and running, faster. +Click the help icon image:images/intro-help-icon.png[Help icon in navigation bar] +for help with questions or to provide feedback. -Our <> and in-product guidance can -help you get up and running, faster. Click the help icon image:images/intro-help-icon.png[Help icon in navigation bar] for help with questions or to provide feedback. +To keep up with what’s new and changed in Elastic, click the celebration icon in the global header. diff --git a/package.json b/package.json index d14c5b0a7dc5f..d6850a50c046f 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "**/react-syntax-highlighter/**/highlight.js": "^10.4.1", "**/request": "^2.88.2", "**/trim": "0.0.3", - "**/typescript": "4.1.2" + "**/typescript": "4.1.3" }, "engines": { "node": "14.15.4", @@ -312,7 +312,7 @@ "tabbable": "1.1.3", "tar": "4.4.13", "tinygradient": "0.4.3", - "tinymath": "1.2.1", + "@kbn/tinymath": "link:packages/kbn-tinymath", "tree-kill": "^1.2.2", "ts-easing": "^0.2.0", "tslib": "^2.0.0", @@ -346,6 +346,7 @@ "@babel/register": "^7.12.10", "@babel/traverse": "^7.12.12", "@babel/types": "^7.12.12", + "@bazel/ibazel": "^0.14.0", "@cypress/snapshot": "^2.1.7", "@cypress/webpack-preprocessor": "^5.5.0", "@elastic/apm-rum": "^5.6.1", @@ -561,8 +562,8 @@ "@types/xml2js": "^0.4.5", "@types/yauzl": "^2.9.1", "@types/zen-observable": "^0.8.0", - "@typescript-eslint/eslint-plugin": "^4.8.1", - "@typescript-eslint/parser": "^4.8.1", + "@typescript-eslint/eslint-plugin": "^4.14.1", + "@typescript-eslint/parser": "^4.14.1", "@welldone-software/why-did-you-render": "^5.0.0", "@yarnpkg/lockfile": "^1.1.0", "abab": "^2.0.4", @@ -595,7 +596,7 @@ "callsites": "^3.1.0", "chai": "3.5.0", "chance": "1.0.18", - "chromedriver": "^87.0.3", + "chromedriver": "^88.0.0", "clean-webpack-plugin": "^3.0.0", "cmd-shim": "^2.1.0", "compare-versions": "3.5.1", @@ -820,9 +821,9 @@ "topojson-client": "3.0.0", "ts-loader": "^7.0.5", "tsd": "^0.13.1", - "typescript": "4.1.2", + "typescript": "4.1.3", "typescript-fsa": "^3.0.0", - "typescript-fsa-reducers": "^1.2.1", + "typescript-fsa-reducers": "^1.2.2", "unlazy-loader": "^0.1.3", "unstated": "^2.1.1", "url-loader": "^2.2.0", @@ -846,4 +847,4 @@ "yargs": "^15.4.1", "zlib": "^1.0.5" } -} +} \ No newline at end of file diff --git a/packages/elastic-eslint-config-kibana/.eslintrc.js b/packages/elastic-eslint-config-kibana/.eslintrc.js index c0f8bf0ecb508..2e978c543cc69 100644 --- a/packages/elastic-eslint-config-kibana/.eslintrc.js +++ b/packages/elastic-eslint-config-kibana/.eslintrc.js @@ -65,6 +65,11 @@ module.exports = { to: false, disallowedMessage: `Don't import monaco directly, use or add exports to @kbn/monaco` }, + { + from: 'tinymath', + to: '@kbn/tinymath', + disallowedMessage: `Don't use 'tinymath', use '@kbn/tinymath'` + }, ], ], }, diff --git a/packages/kbn-config/src/config_service.mock.ts b/packages/kbn-config/src/config_service.mock.ts index cd6f399ddcce2..59f788767004c 100644 --- a/packages/kbn-config/src/config_service.mock.ts +++ b/packages/kbn-config/src/config_service.mock.ts @@ -17,8 +17,8 @@ const createConfigServiceMock = ({ }: { atPath?: Record; getConfig$?: Record } = {}) => { const mocked: jest.Mocked = { atPath: jest.fn(), + atPathSync: jest.fn(), getConfig$: jest.fn(), - optionalAtPath: jest.fn(), getUsedPaths: jest.fn(), getUnusedPaths: jest.fn(), isEnabledAtPath: jest.fn(), @@ -27,6 +27,7 @@ const createConfigServiceMock = ({ validate: jest.fn(), }; mocked.atPath.mockReturnValue(new BehaviorSubject(atPath)); + mocked.atPathSync.mockReturnValue(atPath); mocked.getConfig$.mockReturnValue(new BehaviorSubject(new ObjectToConfigAdapter(getConfig$))); mocked.getUsedPaths.mockResolvedValue([]); mocked.getUnusedPaths.mockResolvedValue([]); diff --git a/packages/kbn-config/src/config_service.test.ts b/packages/kbn-config/src/config_service.test.ts index 96d1f794a691c..e55916d7d348c 100644 --- a/packages/kbn-config/src/config_service.test.ts +++ b/packages/kbn-config/src/config_service.test.ts @@ -105,27 +105,6 @@ test('re-validate config when updated', async () => { `); }); -test("returns undefined if fetching optional config at a path that doesn't exist", async () => { - const rawConfig = getRawConfigProvider({}); - const configService = new ConfigService(rawConfig, defaultEnv, logger); - - const value$ = configService.optionalAtPath('unique-name'); - const value = await value$.pipe(first()).toPromise(); - - expect(value).toBeUndefined(); -}); - -test('returns observable config at optional path if it exists', async () => { - const rawConfig = getRawConfigProvider({ value: 'bar' }); - const configService = new ConfigService(rawConfig, defaultEnv, logger); - await configService.setSchema('value', schema.string()); - - const value$ = configService.optionalAtPath('value'); - const value: any = await value$.pipe(first()).toPromise(); - - expect(value).toBe('bar'); -}); - test("does not push new configs when reloading if config at path hasn't changed", async () => { const rawConfig$ = new BehaviorSubject>({ key: 'value' }); const rawConfigProvider = rawConfigServiceMock.create({ rawConfig$ }); @@ -209,34 +188,38 @@ test('flags schema paths as handled when registering a schema', async () => { test('tracks unhandled paths', async () => { const initialConfig = { - bar: { - deep1: { - key: '123', - }, - deep2: { - key: '321', - }, + service: { + string: 'str', + number: 42, }, - foo: 'value', - quux: { - deep1: { - key: 'hello', - }, - deep2: { - key: 'world', - }, + plugin: { + foo: 'bar', + }, + unknown: { + hello: 'dolly', + number: 9000, }, }; const rawConfigProvider = rawConfigServiceMock.create({ rawConfig: initialConfig }); const configService = new ConfigService(rawConfigProvider, defaultEnv, logger); - - configService.atPath('foo'); - configService.atPath(['bar', 'deep2']); + await configService.setSchema( + 'service', + schema.object({ + string: schema.string(), + number: schema.number(), + }) + ); + await configService.setSchema( + 'plugin', + schema.object({ + foo: schema.string(), + }) + ); const unused = await configService.getUnusedPaths(); - expect(unused).toEqual(['bar.deep1.key', 'quux.deep1.key', 'quux.deep2.key']); + expect(unused).toEqual(['unknown.hello', 'unknown.number']); }); test('correctly passes context', async () => { @@ -339,22 +322,18 @@ test('does not throw if schema does not define "enabled" schema', async () => { const rawConfigProvider = rawConfigServiceMock.create({ rawConfig: initialConfig }); const configService = new ConfigService(rawConfigProvider, defaultEnv, logger); - await expect( + expect( configService.setSchema( 'pid', schema.object({ file: schema.string(), }) ) - ).resolves.toBeUndefined(); + ).toBeUndefined(); const value$ = configService.atPath('pid'); const value: any = await value$.pipe(first()).toPromise(); expect(value.enabled).toBe(undefined); - - const valueOptional$ = configService.optionalAtPath('pid'); - const valueOptional: any = await valueOptional$.pipe(first()).toPromise(); - expect(valueOptional.enabled).toBe(undefined); }); test('treats config as enabled if config path is not present in config', async () => { @@ -457,3 +436,44 @@ test('logs deprecation warning during validation', async () => { ] `); }); + +describe('atPathSync', () => { + test('returns the value at path', async () => { + const rawConfig = getRawConfigProvider({ key: 'foo' }); + const configService = new ConfigService(rawConfig, defaultEnv, logger); + const stringSchema = schema.string(); + await configService.setSchema('key', stringSchema); + + await configService.validate(); + + const value = configService.atPathSync('key'); + expect(value).toBe('foo'); + }); + + test('throws if called before `validate`', async () => { + const rawConfig = getRawConfigProvider({ key: 'foo' }); + const configService = new ConfigService(rawConfig, defaultEnv, logger); + const stringSchema = schema.string(); + await configService.setSchema('key', stringSchema); + + expect(() => configService.atPathSync('key')).toThrowErrorMatchingInlineSnapshot( + `"\`atPathSync\` called before config was validated"` + ); + }); + + test('returns the last config value', async () => { + const rawConfig$ = new BehaviorSubject>({ key: 'value' }); + const rawConfigProvider = rawConfigServiceMock.create({ rawConfig$ }); + + const configService = new ConfigService(rawConfigProvider, defaultEnv, logger); + await configService.setSchema('key', schema.string()); + + await configService.validate(); + + expect(configService.atPathSync('key')).toEqual('value'); + + rawConfig$.next({ key: 'new-value' }); + + expect(configService.atPathSync('key')).toEqual('new-value'); + }); +}); diff --git a/packages/kbn-config/src/config_service.ts b/packages/kbn-config/src/config_service.ts index 9518279f35766..929735ffc15f2 100644 --- a/packages/kbn-config/src/config_service.ts +++ b/packages/kbn-config/src/config_service.ts @@ -10,7 +10,7 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { Type } from '@kbn/config-schema'; import { isEqual } from 'lodash'; import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; -import { distinctUntilChanged, first, map, shareReplay, take } from 'rxjs/operators'; +import { distinctUntilChanged, first, map, shareReplay, take, tap } from 'rxjs/operators'; import { Logger, LoggerFactory } from '@kbn/logging'; import { Config, ConfigPath, Env } from '.'; @@ -32,13 +32,15 @@ export class ConfigService { private readonly log: Logger; private readonly deprecationLog: Logger; + private validated = false; private readonly config$: Observable; + private lastConfig?: Config; /** * Whenever a config if read at a path, we mark that path as 'handled'. We can * then list all unhandled config paths when the startup process is completed. */ - private readonly handledPaths: ConfigPath[] = []; + private readonly handledPaths: Set = new Set(); private readonly schemas = new Map>(); private readonly deprecations = new BehaviorSubject([]); @@ -55,6 +57,9 @@ export class ConfigService { const migrated = applyDeprecations(rawConfig, deprecations); return new LegacyObjectToConfigAdapter(migrated); }), + tap((config) => { + this.lastConfig = config; + }), shareReplay(1) ); } @@ -62,7 +67,7 @@ export class ConfigService { /** * Set config schema for a path and performs its validation */ - public async setSchema(path: ConfigPath, schema: Type) { + public setSchema(path: ConfigPath, schema: Type) { const namespace = pathToString(path); if (this.schemas.has(namespace)) { throw new Error(`Validation schema for [${path}] was already registered.`); @@ -94,15 +99,16 @@ export class ConfigService { public async validate() { const namespaces = [...this.schemas.keys()]; for (let i = 0; i < namespaces.length; i++) { - await this.validateConfigAtPath(namespaces[i]).pipe(first()).toPromise(); + await this.getValidatedConfigAtPath$(namespaces[i]).pipe(first()).toPromise(); } await this.logDeprecation(); + this.validated = true; } /** * Returns the full config object observable. This is not intended for - * "normal use", but for features that _need_ access to the full object. + * "normal use", but for internal features that _need_ access to the full object. */ public getConfig$() { return this.config$; @@ -110,27 +116,26 @@ export class ConfigService { /** * Reads the subset of the config at the specified `path` and validates it - * against the static `schema` on the given `ConfigClass`. + * against its registered schema. * * @param path - The path to the desired subset of the config. */ public atPath(path: ConfigPath) { - return this.validateConfigAtPath(path) as Observable; + return this.getValidatedConfigAtPath$(path) as Observable; } /** - * Same as `atPath`, but returns `undefined` if there is no config at the - * specified path. + * Similar to {@link atPath}, but return the last emitted value synchronously instead of an + * observable. * - * {@link ConfigService.atPath} + * @param path - The path to the desired subset of the config. */ - public optionalAtPath(path: ConfigPath) { - return this.getDistinctConfig(path).pipe( - map((config) => { - if (config === undefined) return undefined; - return this.validateAtPath(path, config) as TSchema; - }) - ); + public atPathSync(path: ConfigPath) { + if (!this.validated) { + throw new Error('`atPathSync` called before config was validated'); + } + const configAtPath = this.lastConfig!.get(path); + return this.validateAtPath(path, configAtPath) as TSchema; } public async isEnabledAtPath(path: ConfigPath) { @@ -144,10 +149,7 @@ export class ConfigService { const config = await this.config$.pipe(first()).toPromise(); // if plugin hasn't got a config schema, we try to read "enabled" directly - const isEnabled = - validatedConfig && validatedConfig.enabled !== undefined - ? validatedConfig.enabled - : config.get(enabledPath); + const isEnabled = validatedConfig?.enabled ?? config.get(enabledPath); // not declared. consider that plugin is enabled by default if (isEnabled === undefined) { @@ -170,15 +172,13 @@ export class ConfigService { public async getUnusedPaths() { const config = await this.config$.pipe(first()).toPromise(); - const handledPaths = this.handledPaths.map(pathToString); - + const handledPaths = [...this.handledPaths.values()].map(pathToString); return config.getFlattenedPaths().filter((path) => !isPathHandled(path, handledPaths)); } public async getUsedPaths() { const config = await this.config$.pipe(first()).toPromise(); - const handledPaths = this.handledPaths.map(pathToString); - + const handledPaths = [...this.handledPaths.values()].map(pathToString); return config.getFlattenedPaths().filter((path) => isPathHandled(path, handledPaths)); } @@ -210,22 +210,17 @@ export class ConfigService { ); } - private validateConfigAtPath(path: ConfigPath) { - return this.getDistinctConfig(path).pipe(map((config) => this.validateAtPath(path, config))); - } - - private getDistinctConfig(path: ConfigPath) { - this.markAsHandled(path); - + private getValidatedConfigAtPath$(path: ConfigPath) { return this.config$.pipe( map((config) => config.get(path)), - distinctUntilChanged(isEqual) + distinctUntilChanged(isEqual), + map((config) => this.validateAtPath(path, config)) ); } private markAsHandled(path: ConfigPath) { this.log.debug(`Marking config path as handled: ${path}`); - this.handledPaths.push(path); + this.handledPaths.add(path); } } diff --git a/packages/kbn-config/src/deprecation/deprecation_factory.test.ts b/packages/kbn-config/src/deprecation/deprecation_factory.test.ts index 5184494dd74bf..1c13c539c3746 100644 --- a/packages/kbn-config/src/deprecation/deprecation_factory.test.ts +++ b/packages/kbn-config/src/deprecation/deprecation_factory.test.ts @@ -7,7 +7,7 @@ */ import { ConfigDeprecationLogger } from './types'; -import { configDeprecationFactory, copyFromRoot } from './deprecation_factory'; +import { configDeprecationFactory } from './deprecation_factory'; describe('DeprecationFactory', () => { const { rename, unused, renameFromRoot, unusedFromRoot } = configDeprecationFactory; @@ -239,89 +239,6 @@ describe('DeprecationFactory', () => { }); }); - describe('copyFromRoot', () => { - it('copies a property to a different namespace', () => { - const rawConfig = { - originplugin: { - deprecated: 'toberenamed', - valid: 'valid', - }, - destinationplugin: { - property: 'value', - }, - }; - const processed = copyFromRoot('originplugin.deprecated', 'destinationplugin.renamed')( - rawConfig, - 'does-not-matter', - logger - ); - expect(processed).toEqual({ - originplugin: { - deprecated: 'toberenamed', - valid: 'valid', - }, - destinationplugin: { - renamed: 'toberenamed', - property: 'value', - }, - }); - expect(deprecationMessages.length).toEqual(0); - }); - - it('does not alter config if origin property is not present', () => { - const rawConfig = { - myplugin: { - new: 'new', - valid: 'valid', - }, - someOtherPlugin: { - property: 'value', - }, - }; - const processed = copyFromRoot('myplugin.deprecated', 'myplugin.new')( - rawConfig, - 'does-not-matter', - logger - ); - expect(processed).toEqual({ - myplugin: { - new: 'new', - valid: 'valid', - }, - someOtherPlugin: { - property: 'value', - }, - }); - expect(deprecationMessages.length).toEqual(0); - }); - - it('does not alter config if they both exist', () => { - const rawConfig = { - myplugin: { - deprecated: 'deprecated', - renamed: 'renamed', - }, - someOtherPlugin: { - property: 'value', - }, - }; - const processed = copyFromRoot('myplugin.deprecated', 'someOtherPlugin.property')( - rawConfig, - 'does-not-matter', - logger - ); - expect(processed).toEqual({ - myplugin: { - deprecated: 'deprecated', - renamed: 'renamed', - }, - someOtherPlugin: { - property: 'value', - }, - }); - }); - }); - describe('unused', () => { it('removes the unused property from the config and logs a warning is present', () => { const rawConfig = { diff --git a/packages/kbn-config/src/deprecation/deprecation_factory.ts b/packages/kbn-config/src/deprecation/deprecation_factory.ts index 40e29f9739156..04826446dc1ad 100644 --- a/packages/kbn-config/src/deprecation/deprecation_factory.ts +++ b/packages/kbn-config/src/deprecation/deprecation_factory.ts @@ -45,26 +45,6 @@ const _rename = ( return config; }; -const _copy = ( - config: Record, - rootPath: string, - originKey: string, - destinationKey: string -) => { - const originPath = getPath(rootPath, originKey); - const originValue = get(config, originPath); - if (originValue === undefined) { - return config; - } - - const destinationPath = getPath(rootPath, destinationKey); - const destinationValue = get(config, destinationPath); - if (destinationValue === undefined) { - set(config, destinationPath, originValue); - } - return config; -}; - const _unused = ( config: Record, rootPath: string, @@ -89,12 +69,6 @@ const renameFromRoot = (oldKey: string, newKey: string, silent?: boolean): Confi log ) => _rename(config, '', log, oldKey, newKey, silent); -export const copyFromRoot = (originKey: string, destinationKey: string): ConfigDeprecation => ( - config, - rootPath, - log -) => _copy(config, '', originKey, destinationKey); - const unused = (unusedKey: string): ConfigDeprecation => (config, rootPath, log) => _unused(config, rootPath, log, unusedKey); diff --git a/packages/kbn-config/src/deprecation/index.ts b/packages/kbn-config/src/deprecation/index.ts index 87ec43d1fb09f..6deebd27399bb 100644 --- a/packages/kbn-config/src/deprecation/index.ts +++ b/packages/kbn-config/src/deprecation/index.ts @@ -13,5 +13,5 @@ export { ConfigDeprecationFactory, ConfigDeprecationProvider, } from './types'; -export { configDeprecationFactory, copyFromRoot } from './deprecation_factory'; +export { configDeprecationFactory } from './deprecation_factory'; export { applyDeprecations } from './apply_deprecations'; diff --git a/packages/kbn-config/src/index.ts b/packages/kbn-config/src/index.ts index e1743270f62c7..a803e3fd2dc8e 100644 --- a/packages/kbn-config/src/index.ts +++ b/packages/kbn-config/src/index.ts @@ -14,7 +14,6 @@ export { ConfigDeprecationLogger, ConfigDeprecationProvider, ConfigDeprecationWithContext, - copyFromRoot, } from './deprecation'; export { RawConfigurationProvider, RawConfigService, getConfigFromFiles } from './raw'; diff --git a/packages/kbn-config/src/legacy/legacy_object_to_config_adapter.ts b/packages/kbn-config/src/legacy/legacy_object_to_config_adapter.ts index c037c5f0308c8..c12a147fddddc 100644 --- a/packages/kbn-config/src/legacy/legacy_object_to_config_adapter.ts +++ b/packages/kbn-config/src/legacy/legacy_object_to_config_adapter.ts @@ -84,7 +84,7 @@ export class LegacyObjectToConfigAdapter extends ObjectToConfigAdapter { }; } - private static transformPlugins(configValue: LegacyVars) { + private static transformPlugins(configValue: LegacyVars = {}) { // These properties are the only ones we use from the existing `plugins` config node // since `scanDirs` isn't respected by new platform plugin discovery. return { diff --git a/packages/kbn-es/package.json b/packages/kbn-es/package.json index c8e25a95594c6..8ea83d744bcb9 100644 --- a/packages/kbn-es/package.json +++ b/packages/kbn-es/package.json @@ -8,6 +8,7 @@ "devOnly": true }, "scripts": { + "build": "node scripts/build", "kbn:bootstrap": "node scripts/build", "kbn:watch": "node scripts/build --watch" }, diff --git a/packages/kbn-es/src/cli_commands/snapshot.js b/packages/kbn-es/src/cli_commands/snapshot.js index d66c352a356aa..711992a5895ed 100644 --- a/packages/kbn-es/src/cli_commands/snapshot.js +++ b/packages/kbn-es/src/cli_commands/snapshot.js @@ -62,8 +62,6 @@ exports.run = async (defaults = {}) => { await cluster.extractDataDirectory(installPath, options.dataArchive); } - options.bundledJDK = true; - await cluster.run(installPath, options); } }; diff --git a/packages/kbn-es/src/cluster.js b/packages/kbn-es/src/cluster.js index 60f8a327594d6..f554dd8a1b8e5 100644 --- a/packages/kbn-es/src/cluster.js +++ b/packages/kbn-es/src/cluster.js @@ -279,7 +279,7 @@ exports.Cluster = class Cluster { env: { ...(installPath ? { ES_TMPDIR: path.resolve(installPath, 'ES_TMPDIR') } : {}), ...process.env, - ...(options.bundledJDK ? { JAVA_HOME: '' } : {}), + JAVA_HOME: '', // By default, we want to always unset JAVA_HOME so that the bundled JDK will be used ...(options.esEnvVars || {}), }, stdio: ['ignore', 'pipe', 'pipe'], diff --git a/packages/kbn-es/src/install/archive.js b/packages/kbn-es/src/install/archive.js index 1e9e19f4533af..80ff4eb6f83b0 100644 --- a/packages/kbn-es/src/install/archive.js +++ b/packages/kbn-es/src/install/archive.js @@ -34,7 +34,6 @@ exports.installArchive = async function installArchive(archive, options = {}) { basePath = BASE_PATH, installPath = path.resolve(basePath, path.basename(archive, '.tar.gz')), log = defaultLog, - bundledJDK = false, esArgs = [], } = options; @@ -64,7 +63,7 @@ exports.installArchive = async function installArchive(archive, options = {}) { await appendToConfig(installPath, 'xpack.security.enabled', 'true'); await appendToConfig(installPath, 'xpack.license.self_generated.type', license); - await configureKeystore(installPath, log, bundledJDK, [ + await configureKeystore(installPath, log, [ ['bootstrap.password', password], ...parseSettings(esArgs, { filter: SettingsFilter.SecureOnly }), ]); @@ -89,20 +88,11 @@ async function appendToConfig(installPath, key, value) { * * @param {String} installPath * @param {ToolingLog} log - * @param {boolean} bundledJDK * @param {Array<[string, string]>} secureSettings List of custom Elasticsearch secure settings to * add into the keystore. */ -async function configureKeystore( - installPath, - log = defaultLog, - bundledJDK = false, - secureSettings -) { - const env = {}; - if (bundledJDK) { - env.JAVA_HOME = ''; - } +async function configureKeystore(installPath, log = defaultLog, secureSettings) { + const env = { JAVA_HOME: '' }; await execa(ES_KEYSTORE_BIN, ['create'], { cwd: installPath, env }); for (const [secureSettingName, secureSettingValue] of secureSettings) { diff --git a/packages/kbn-es/src/install/snapshot.js b/packages/kbn-es/src/install/snapshot.js index 55c0e41ea9640..b9562f20d81b7 100644 --- a/packages/kbn-es/src/install/snapshot.js +++ b/packages/kbn-es/src/install/snapshot.js @@ -61,7 +61,6 @@ exports.installSnapshot = async function installSnapshot({ basePath = BASE_PATH, installPath = path.resolve(basePath, version), log = defaultLog, - bundledJDK = true, esArgs, }) { const { downloadPath } = await exports.downloadSnapshot({ @@ -78,7 +77,6 @@ exports.installSnapshot = async function installSnapshot({ basePath, installPath, log, - bundledJDK, esArgs, }); }; diff --git a/packages/kbn-legacy-logging/src/get_logging_config.ts b/packages/kbn-legacy-logging/src/get_logging_config.ts index 5a28ab1271538..280b9f815c78c 100644 --- a/packages/kbn-legacy-logging/src/get_logging_config.ts +++ b/packages/kbn-legacy-logging/src/get_logging_config.ts @@ -28,7 +28,10 @@ export function getLoggingConfiguration(config: LegacyLoggingConfig, opsInterval } else if (config.verbose) { _.defaults(events, { log: '*', - ops: '*', + // To avoid duplicate logs, we explicitly disable this in verbose + // mode as it is already provided by the new logging config under + // the `metrics.ops` context. + ops: '!', request: '*', response: '*', error: '*', diff --git a/packages/kbn-optimizer/limits.yml b/packages/kbn-optimizer/limits.yml index ef672d6cbeb2e..a13976d148738 100644 --- a/packages/kbn-optimizer/limits.yml +++ b/packages/kbn-optimizer/limits.yml @@ -44,7 +44,6 @@ pageLoadAssetSize: kibanaReact: 161921 kibanaUtils: 198829 lens: 96624 - lensOss: 19341 licenseManagement: 41817 licensing: 39008 lists: 202261 @@ -53,7 +52,6 @@ pageLoadAssetSize: maps: 183610 mapsLegacy: 116817 mapsLegacyLicensing: 20214 - mapsOss: 19284 ml: 82187 monitoring: 50000 navigation: 37269 @@ -105,4 +103,5 @@ pageLoadAssetSize: stackAlerts: 29684 presentationUtil: 28545 spacesOss: 18817 + osquery: 107090 mapsFileUpload: 23775 diff --git a/packages/kbn-optimizer/src/worker/webpack.config.ts b/packages/kbn-optimizer/src/worker/webpack.config.ts index 96356e01f8c04..089ff163a692d 100644 --- a/packages/kbn-optimizer/src/worker/webpack.config.ts +++ b/packages/kbn-optimizer/src/worker/webpack.config.ts @@ -216,7 +216,6 @@ export function getWebpackConfig(bundle: Bundle, bundleRefs: BundleRefs, worker: extensions: ['.js', '.ts', '.tsx', '.json'], mainFields: ['browser', 'main'], alias: { - tinymath: require.resolve('tinymath/lib/tinymath.es5.js'), core_app_image_assets: Path.resolve(worker.repoRoot, 'src/core/public/core_app/images'), }, }, diff --git a/packages/kbn-pm/README.md b/packages/kbn-pm/README.md index c169b5c75e178..eb1ac6ffa92aa 100644 --- a/packages/kbn-pm/README.md +++ b/packages/kbn-pm/README.md @@ -150,14 +150,14 @@ e.g. `build` or `test`. Instead of jumping into each package and running `yarn build` you can run: ``` -yarn kbn run build +yarn kbn run build --skip-missing ``` And if needed, you can skip packages in the same way as for bootstrapping, e.g. with `--exclude` and `--skip-kibana-plugins`: ``` -yarn kbn run build --exclude kibana +yarn kbn run build --exclude kibana --skip-missing ``` ### Watching diff --git a/packages/kbn-pm/dist/index.js b/packages/kbn-pm/dist/index.js index 09995a9be30a6..df04965cd8c32 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -94,7 +94,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _cli__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "run", function() { return _cli__WEBPACK_IMPORTED_MODULE_0__["run"]; }); -/* harmony import */ var _production__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(510); +/* harmony import */ var _production__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(512); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _production__WEBPACK_IMPORTED_MODULE_1__["buildProductionProjects"]; }); /* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(248); @@ -106,7 +106,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_package_json__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(251); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "transformDependencies", function() { return _utils_package_json__WEBPACK_IMPORTED_MODULE_4__["transformDependencies"]; }); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(509); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(511); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "getProjectPaths", function() { return _config__WEBPACK_IMPORTED_MODULE_5__["getProjectPaths"]; }); /* @@ -139,7 +139,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(5); /* harmony import */ var _kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(_kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_3__); /* harmony import */ var _commands__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(128); -/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(503); +/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(505); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(246); /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one @@ -179,12 +179,15 @@ function help() { --debug Set log level to debug --quiet Set log level to error --silent Disable log output + + "run" options: + --skip-missing Ignore packages which don't have the requested script ` + '\n'); } async function run(argv) { _utils_log__WEBPACK_IMPORTED_MODULE_6__["log"].setLogLevel(Object(_kbn_dev_utils_tooling_log__WEBPACK_IMPORTED_MODULE_3__["pickLevelFromFlags"])(getopts__WEBPACK_IMPORTED_MODULE_1___default()(argv, { - boolean: ['verbose', 'debug', 'quiet', 'silent'] + boolean: ['verbose', 'debug', 'quiet', 'silent', 'skip-missing'] }))); // We can simplify this setup (and remove this extra handling) once Yarn // starts forwarding the `--` directly to this script, see // https://github.com/yarnpkg/yarn/blob/b2d3e1a8fe45ef376b716d597cc79b38702a9320/src/cli/index.js#L174-L182 @@ -8824,9 +8827,9 @@ exports.ToolingLogCollectingWriter = ToolingLogCollectingWriter; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "commands", function() { return commands; }); /* harmony import */ var _bootstrap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(129); -/* harmony import */ var _clean__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(371); -/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(402); -/* harmony import */ var _watch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(403); +/* harmony import */ var _clean__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(373); +/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(404); +/* harmony import */ var _watch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(405); /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License @@ -8862,6 +8865,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_bootstrap_cache_file__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(368); /* harmony import */ var _utils_yarn_lock__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(365); /* harmony import */ var _utils_validate_dependencies__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(369); +/* harmony import */ var _utils_bazel__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(371); /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License @@ -8878,19 +8882,23 @@ __webpack_require__.r(__webpack_exports__); + const BootstrapCommand = { description: 'Install dependencies and crosslink projects', name: 'bootstrap', async run(projects, projectGraph, { options, - kbn + kbn, + rootPath }) { var _projects$get; const batchedProjects = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_4__["topologicallyBatchProjects"])(projects, projectGraph); const kibanaProjectPath = (_projects$get = projects.get('kibana')) === null || _projects$get === void 0 ? void 0 : _projects$get.path; - const extraArgs = [...(options['frozen-lockfile'] === true ? ['--frozen-lockfile'] : []), ...(options['prefer-offline'] === true ? ['--prefer-offline'] : [])]; + const extraArgs = [...(options['frozen-lockfile'] === true ? ['--frozen-lockfile'] : []), ...(options['prefer-offline'] === true ? ['--prefer-offline'] : [])]; // Install bazel machinery tools if needed + + await Object(_utils_bazel__WEBPACK_IMPORTED_MODULE_9__["installBazelTools"])(rootPath); // Install monorepo npm dependencies for (const batch of batchedProjects) { for (const project of batch) { @@ -47932,12 +47940,90 @@ function addProjectToTree(tree, pathParts, project) { /* 371 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _install_tools__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(372); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "installBazelTools", function() { return _install_tools__WEBPACK_IMPORTED_MODULE_0__["installBazelTools"]; }); + +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + + +/***/ }), +/* 372 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "installBazelTools", function() { return installBazelTools; }); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var _child_process__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(319); +/* harmony import */ var _fs__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(131); +/* harmony import */ var _log__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(246); +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + + + + + +async function readBazelToolsVersionFile(repoRootPath, versionFilename) { + const version = (await Object(_fs__WEBPACK_IMPORTED_MODULE_2__["readFile"])(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(repoRootPath, versionFilename))).toString().split('\n')[0]; + + if (!version) { + throw new Error(`[bazel_tools] Failed on reading bazel tools versions\n ${versionFilename} file do not contain any version set`); + } + + return version; +} + +async function installBazelTools(repoRootPath) { + _log__WEBPACK_IMPORTED_MODULE_3__["log"].debug(`[bazel_tools] reading bazel tools versions from version files`); + const bazeliskVersion = await readBazelToolsVersionFile(repoRootPath, '.bazeliskversion'); + const bazelVersion = await readBazelToolsVersionFile(repoRootPath, '.bazelversion'); // Check what globals are installed + + _log__WEBPACK_IMPORTED_MODULE_3__["log"].debug(`[bazel_tools] verify if bazelisk is installed`); + const { + stdout + } = await Object(_child_process__WEBPACK_IMPORTED_MODULE_1__["spawn"])('yarn', ['global', 'list'], { + stdio: 'pipe' + }); // Install bazelisk if not installed + + if (!stdout.includes(`@bazel/bazelisk@${bazeliskVersion}`)) { + _log__WEBPACK_IMPORTED_MODULE_3__["log"].info(`[bazel_tools] installing Bazel tools`); + _log__WEBPACK_IMPORTED_MODULE_3__["log"].debug(`[bazel_tools] bazelisk is not installed. Installing @bazel/bazelisk@${bazeliskVersion} and bazel@${bazelVersion}`); + await Object(_child_process__WEBPACK_IMPORTED_MODULE_1__["spawn"])('yarn', ['global', 'add', `@bazel/bazelisk@${bazeliskVersion}`], { + env: { + USE_BAZEL_VERSION: bazelVersion + }, + stdio: 'pipe' + }); + } + + _log__WEBPACK_IMPORTED_MODULE_3__["log"].success(`[bazel_tools] all bazel tools are correctly installed`); +} + +/***/ }), +/* 373 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CleanCommand", function() { return CleanCommand; }); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(143); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(372); +/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(374); /* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(ora__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__); @@ -48026,20 +48112,20 @@ const CleanCommand = { }; /***/ }), -/* 372 */ +/* 374 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readline = __webpack_require__(373); -const chalk = __webpack_require__(374); -const cliCursor = __webpack_require__(381); -const cliSpinners = __webpack_require__(383); -const logSymbols = __webpack_require__(385); -const stripAnsi = __webpack_require__(394); -const wcwidth = __webpack_require__(396); -const isInteractive = __webpack_require__(400); -const MuteStream = __webpack_require__(401); +const readline = __webpack_require__(375); +const chalk = __webpack_require__(376); +const cliCursor = __webpack_require__(383); +const cliSpinners = __webpack_require__(385); +const logSymbols = __webpack_require__(387); +const stripAnsi = __webpack_require__(396); +const wcwidth = __webpack_require__(398); +const isInteractive = __webpack_require__(402); +const MuteStream = __webpack_require__(403); const TEXT = Symbol('text'); const PREFIX_TEXT = Symbol('prefixText'); @@ -48392,23 +48478,23 @@ module.exports.promise = (action, options) => { /***/ }), -/* 373 */ +/* 375 */ /***/ (function(module, exports) { module.exports = require("readline"); /***/ }), -/* 374 */ +/* 376 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const ansiStyles = __webpack_require__(375); +const ansiStyles = __webpack_require__(377); const {stdout: stdoutColor, stderr: stderrColor} = __webpack_require__(120); const { stringReplaceAll, stringEncaseCRLFWithFirstIndex -} = __webpack_require__(379); +} = __webpack_require__(381); // `supportsColor.level` → `ansiStyles.color[name]` mapping const levelMapping = [ @@ -48609,7 +48695,7 @@ const chalkTag = (chalk, ...strings) => { } if (template === undefined) { - template = __webpack_require__(380); + template = __webpack_require__(382); } return template(chalk, parts.join('')); @@ -48638,7 +48724,7 @@ module.exports = chalk; /***/ }), -/* 375 */ +/* 377 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48684,7 +48770,7 @@ const setLazyProperty = (object, property, get) => { let colorConvert; const makeDynamicStyles = (wrap, targetSpace, identity, isBackground) => { if (colorConvert === undefined) { - colorConvert = __webpack_require__(376); + colorConvert = __webpack_require__(378); } const offset = isBackground ? 10 : 0; @@ -48809,11 +48895,11 @@ Object.defineProperty(module, 'exports', { /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(115)(module))) /***/ }), -/* 376 */ +/* 378 */ /***/ (function(module, exports, __webpack_require__) { -const conversions = __webpack_require__(377); -const route = __webpack_require__(378); +const conversions = __webpack_require__(379); +const route = __webpack_require__(380); const convert = {}; @@ -48896,7 +48982,7 @@ module.exports = convert; /***/ }), -/* 377 */ +/* 379 */ /***/ (function(module, exports, __webpack_require__) { /* MIT license */ @@ -49741,10 +49827,10 @@ convert.rgb.gray = function (rgb) { /***/ }), -/* 378 */ +/* 380 */ /***/ (function(module, exports, __webpack_require__) { -const conversions = __webpack_require__(377); +const conversions = __webpack_require__(379); /* This function routes a model to all other models. @@ -49844,7 +49930,7 @@ module.exports = function (fromModel) { /***/ }), -/* 379 */ +/* 381 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -49890,7 +49976,7 @@ module.exports = { /***/ }), -/* 380 */ +/* 382 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -50031,12 +50117,12 @@ module.exports = (chalk, temporary) => { /***/ }), -/* 381 */ +/* 383 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const restoreCursor = __webpack_require__(382); +const restoreCursor = __webpack_require__(384); let isHidden = false; @@ -50073,7 +50159,7 @@ exports.toggle = (force, writableStream) => { /***/ }), -/* 382 */ +/* 384 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -50089,13 +50175,13 @@ module.exports = onetime(() => { /***/ }), -/* 383 */ +/* 385 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const spinners = Object.assign({}, __webpack_require__(384)); +const spinners = Object.assign({}, __webpack_require__(386)); const spinnersList = Object.keys(spinners); @@ -50113,18 +50199,18 @@ module.exports.default = spinners; /***/ }), -/* 384 */ +/* 386 */ /***/ (function(module) { module.exports = JSON.parse("{\"dots\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠹\",\"⠸\",\"⠼\",\"⠴\",\"⠦\",\"⠧\",\"⠇\",\"⠏\"]},\"dots2\":{\"interval\":80,\"frames\":[\"⣾\",\"⣽\",\"⣻\",\"⢿\",\"⡿\",\"⣟\",\"⣯\",\"⣷\"]},\"dots3\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠞\",\"⠖\",\"⠦\",\"⠴\",\"⠲\",\"⠳\",\"⠓\"]},\"dots4\":{\"interval\":80,\"frames\":[\"⠄\",\"⠆\",\"⠇\",\"⠋\",\"⠙\",\"⠸\",\"⠰\",\"⠠\",\"⠰\",\"⠸\",\"⠙\",\"⠋\",\"⠇\",\"⠆\"]},\"dots5\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\"]},\"dots6\":{\"interval\":80,\"frames\":[\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠴\",\"⠲\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠚\",\"⠙\",\"⠉\",\"⠁\"]},\"dots7\":{\"interval\":80,\"frames\":[\"⠈\",\"⠉\",\"⠋\",\"⠓\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠖\",\"⠦\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\"]},\"dots8\":{\"interval\":80,\"frames\":[\"⠁\",\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\",\"⠈\"]},\"dots9\":{\"interval\":80,\"frames\":[\"⢹\",\"⢺\",\"⢼\",\"⣸\",\"⣇\",\"⡧\",\"⡗\",\"⡏\"]},\"dots10\":{\"interval\":80,\"frames\":[\"⢄\",\"⢂\",\"⢁\",\"⡁\",\"⡈\",\"⡐\",\"⡠\"]},\"dots11\":{\"interval\":100,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⡀\",\"⢀\",\"⠠\",\"⠐\",\"⠈\"]},\"dots12\":{\"interval\":80,\"frames\":[\"⢀⠀\",\"⡀⠀\",\"⠄⠀\",\"⢂⠀\",\"⡂⠀\",\"⠅⠀\",\"⢃⠀\",\"⡃⠀\",\"⠍⠀\",\"⢋⠀\",\"⡋⠀\",\"⠍⠁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⢈⠩\",\"⡀⢙\",\"⠄⡙\",\"⢂⠩\",\"⡂⢘\",\"⠅⡘\",\"⢃⠨\",\"⡃⢐\",\"⠍⡐\",\"⢋⠠\",\"⡋⢀\",\"⠍⡁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⠈⠩\",\"⠀⢙\",\"⠀⡙\",\"⠀⠩\",\"⠀⢘\",\"⠀⡘\",\"⠀⠨\",\"⠀⢐\",\"⠀⡐\",\"⠀⠠\",\"⠀⢀\",\"⠀⡀\"]},\"dots8Bit\":{\"interval\":80,\"frames\":[\"⠀\",\"⠁\",\"⠂\",\"⠃\",\"⠄\",\"⠅\",\"⠆\",\"⠇\",\"⡀\",\"⡁\",\"⡂\",\"⡃\",\"⡄\",\"⡅\",\"⡆\",\"⡇\",\"⠈\",\"⠉\",\"⠊\",\"⠋\",\"⠌\",\"⠍\",\"⠎\",\"⠏\",\"⡈\",\"⡉\",\"⡊\",\"⡋\",\"⡌\",\"⡍\",\"⡎\",\"⡏\",\"⠐\",\"⠑\",\"⠒\",\"⠓\",\"⠔\",\"⠕\",\"⠖\",\"⠗\",\"⡐\",\"⡑\",\"⡒\",\"⡓\",\"⡔\",\"⡕\",\"⡖\",\"⡗\",\"⠘\",\"⠙\",\"⠚\",\"⠛\",\"⠜\",\"⠝\",\"⠞\",\"⠟\",\"⡘\",\"⡙\",\"⡚\",\"⡛\",\"⡜\",\"⡝\",\"⡞\",\"⡟\",\"⠠\",\"⠡\",\"⠢\",\"⠣\",\"⠤\",\"⠥\",\"⠦\",\"⠧\",\"⡠\",\"⡡\",\"⡢\",\"⡣\",\"⡤\",\"⡥\",\"⡦\",\"⡧\",\"⠨\",\"⠩\",\"⠪\",\"⠫\",\"⠬\",\"⠭\",\"⠮\",\"⠯\",\"⡨\",\"⡩\",\"⡪\",\"⡫\",\"⡬\",\"⡭\",\"⡮\",\"⡯\",\"⠰\",\"⠱\",\"⠲\",\"⠳\",\"⠴\",\"⠵\",\"⠶\",\"⠷\",\"⡰\",\"⡱\",\"⡲\",\"⡳\",\"⡴\",\"⡵\",\"⡶\",\"⡷\",\"⠸\",\"⠹\",\"⠺\",\"⠻\",\"⠼\",\"⠽\",\"⠾\",\"⠿\",\"⡸\",\"⡹\",\"⡺\",\"⡻\",\"⡼\",\"⡽\",\"⡾\",\"⡿\",\"⢀\",\"⢁\",\"⢂\",\"⢃\",\"⢄\",\"⢅\",\"⢆\",\"⢇\",\"⣀\",\"⣁\",\"⣂\",\"⣃\",\"⣄\",\"⣅\",\"⣆\",\"⣇\",\"⢈\",\"⢉\",\"⢊\",\"⢋\",\"⢌\",\"⢍\",\"⢎\",\"⢏\",\"⣈\",\"⣉\",\"⣊\",\"⣋\",\"⣌\",\"⣍\",\"⣎\",\"⣏\",\"⢐\",\"⢑\",\"⢒\",\"⢓\",\"⢔\",\"⢕\",\"⢖\",\"⢗\",\"⣐\",\"⣑\",\"⣒\",\"⣓\",\"⣔\",\"⣕\",\"⣖\",\"⣗\",\"⢘\",\"⢙\",\"⢚\",\"⢛\",\"⢜\",\"⢝\",\"⢞\",\"⢟\",\"⣘\",\"⣙\",\"⣚\",\"⣛\",\"⣜\",\"⣝\",\"⣞\",\"⣟\",\"⢠\",\"⢡\",\"⢢\",\"⢣\",\"⢤\",\"⢥\",\"⢦\",\"⢧\",\"⣠\",\"⣡\",\"⣢\",\"⣣\",\"⣤\",\"⣥\",\"⣦\",\"⣧\",\"⢨\",\"⢩\",\"⢪\",\"⢫\",\"⢬\",\"⢭\",\"⢮\",\"⢯\",\"⣨\",\"⣩\",\"⣪\",\"⣫\",\"⣬\",\"⣭\",\"⣮\",\"⣯\",\"⢰\",\"⢱\",\"⢲\",\"⢳\",\"⢴\",\"⢵\",\"⢶\",\"⢷\",\"⣰\",\"⣱\",\"⣲\",\"⣳\",\"⣴\",\"⣵\",\"⣶\",\"⣷\",\"⢸\",\"⢹\",\"⢺\",\"⢻\",\"⢼\",\"⢽\",\"⢾\",\"⢿\",\"⣸\",\"⣹\",\"⣺\",\"⣻\",\"⣼\",\"⣽\",\"⣾\",\"⣿\"]},\"line\":{\"interval\":130,\"frames\":[\"-\",\"\\\\\",\"|\",\"/\"]},\"line2\":{\"interval\":100,\"frames\":[\"⠂\",\"-\",\"–\",\"—\",\"–\",\"-\"]},\"pipe\":{\"interval\":100,\"frames\":[\"┤\",\"┘\",\"┴\",\"└\",\"├\",\"┌\",\"┬\",\"┐\"]},\"simpleDots\":{\"interval\":400,\"frames\":[\". \",\".. \",\"...\",\" \"]},\"simpleDotsScrolling\":{\"interval\":200,\"frames\":[\". \",\".. \",\"...\",\" ..\",\" .\",\" \"]},\"star\":{\"interval\":70,\"frames\":[\"✶\",\"✸\",\"✹\",\"✺\",\"✹\",\"✷\"]},\"star2\":{\"interval\":80,\"frames\":[\"+\",\"x\",\"*\"]},\"flip\":{\"interval\":70,\"frames\":[\"_\",\"_\",\"_\",\"-\",\"`\",\"`\",\"'\",\"´\",\"-\",\"_\",\"_\",\"_\"]},\"hamburger\":{\"interval\":100,\"frames\":[\"☱\",\"☲\",\"☴\"]},\"growVertical\":{\"interval\":120,\"frames\":[\"▁\",\"▃\",\"▄\",\"▅\",\"▆\",\"▇\",\"▆\",\"▅\",\"▄\",\"▃\"]},\"growHorizontal\":{\"interval\":120,\"frames\":[\"▏\",\"▎\",\"▍\",\"▌\",\"▋\",\"▊\",\"▉\",\"▊\",\"▋\",\"▌\",\"▍\",\"▎\"]},\"balloon\":{\"interval\":140,\"frames\":[\" \",\".\",\"o\",\"O\",\"@\",\"*\",\" \"]},\"balloon2\":{\"interval\":120,\"frames\":[\".\",\"o\",\"O\",\"°\",\"O\",\"o\",\".\"]},\"noise\":{\"interval\":100,\"frames\":[\"▓\",\"▒\",\"░\"]},\"bounce\":{\"interval\":120,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⠂\"]},\"boxBounce\":{\"interval\":120,\"frames\":[\"▖\",\"▘\",\"▝\",\"▗\"]},\"boxBounce2\":{\"interval\":100,\"frames\":[\"▌\",\"▀\",\"▐\",\"▄\"]},\"triangle\":{\"interval\":50,\"frames\":[\"◢\",\"◣\",\"◤\",\"◥\"]},\"arc\":{\"interval\":100,\"frames\":[\"◜\",\"◠\",\"◝\",\"◞\",\"◡\",\"◟\"]},\"circle\":{\"interval\":120,\"frames\":[\"◡\",\"⊙\",\"◠\"]},\"squareCorners\":{\"interval\":180,\"frames\":[\"◰\",\"◳\",\"◲\",\"◱\"]},\"circleQuarters\":{\"interval\":120,\"frames\":[\"◴\",\"◷\",\"◶\",\"◵\"]},\"circleHalves\":{\"interval\":50,\"frames\":[\"◐\",\"◓\",\"◑\",\"◒\"]},\"squish\":{\"interval\":100,\"frames\":[\"╫\",\"╪\"]},\"toggle\":{\"interval\":250,\"frames\":[\"⊶\",\"⊷\"]},\"toggle2\":{\"interval\":80,\"frames\":[\"▫\",\"▪\"]},\"toggle3\":{\"interval\":120,\"frames\":[\"□\",\"■\"]},\"toggle4\":{\"interval\":100,\"frames\":[\"■\",\"□\",\"▪\",\"▫\"]},\"toggle5\":{\"interval\":100,\"frames\":[\"▮\",\"▯\"]},\"toggle6\":{\"interval\":300,\"frames\":[\"ဝ\",\"၀\"]},\"toggle7\":{\"interval\":80,\"frames\":[\"⦾\",\"⦿\"]},\"toggle8\":{\"interval\":100,\"frames\":[\"◍\",\"◌\"]},\"toggle9\":{\"interval\":100,\"frames\":[\"◉\",\"◎\"]},\"toggle10\":{\"interval\":100,\"frames\":[\"㊂\",\"㊀\",\"㊁\"]},\"toggle11\":{\"interval\":50,\"frames\":[\"⧇\",\"⧆\"]},\"toggle12\":{\"interval\":120,\"frames\":[\"☗\",\"☖\"]},\"toggle13\":{\"interval\":80,\"frames\":[\"=\",\"*\",\"-\"]},\"arrow\":{\"interval\":100,\"frames\":[\"←\",\"↖\",\"↑\",\"↗\",\"→\",\"↘\",\"↓\",\"↙\"]},\"arrow2\":{\"interval\":80,\"frames\":[\"⬆️ \",\"↗️ \",\"➡️ \",\"↘️ \",\"⬇️ \",\"↙️ \",\"⬅️ \",\"↖️ \"]},\"arrow3\":{\"interval\":120,\"frames\":[\"▹▹▹▹▹\",\"▸▹▹▹▹\",\"▹▸▹▹▹\",\"▹▹▸▹▹\",\"▹▹▹▸▹\",\"▹▹▹▹▸\"]},\"bouncingBar\":{\"interval\":80,\"frames\":[\"[ ]\",\"[= ]\",\"[== ]\",\"[=== ]\",\"[ ===]\",\"[ ==]\",\"[ =]\",\"[ ]\",\"[ =]\",\"[ ==]\",\"[ ===]\",\"[====]\",\"[=== ]\",\"[== ]\",\"[= ]\"]},\"bouncingBall\":{\"interval\":80,\"frames\":[\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ●)\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"(● )\"]},\"smiley\":{\"interval\":200,\"frames\":[\"😄 \",\"😝 \"]},\"monkey\":{\"interval\":300,\"frames\":[\"🙈 \",\"🙈 \",\"🙉 \",\"🙊 \"]},\"hearts\":{\"interval\":100,\"frames\":[\"💛 \",\"💙 \",\"💜 \",\"💚 \",\"❤️ \"]},\"clock\":{\"interval\":100,\"frames\":[\"🕛 \",\"🕐 \",\"🕑 \",\"🕒 \",\"🕓 \",\"🕔 \",\"🕕 \",\"🕖 \",\"🕗 \",\"🕘 \",\"🕙 \",\"🕚 \"]},\"earth\":{\"interval\":180,\"frames\":[\"🌍 \",\"🌎 \",\"🌏 \"]},\"material\":{\"interval\":17,\"frames\":[\"█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"███▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"████▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"██████▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"███████▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"████████▁▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"██████████▁▁▁▁▁▁▁▁▁▁\",\"███████████▁▁▁▁▁▁▁▁▁\",\"█████████████▁▁▁▁▁▁▁\",\"██████████████▁▁▁▁▁▁\",\"██████████████▁▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁▁██████████████▁▁▁▁\",\"▁▁▁██████████████▁▁▁\",\"▁▁▁▁█████████████▁▁▁\",\"▁▁▁▁██████████████▁▁\",\"▁▁▁▁██████████████▁▁\",\"▁▁▁▁▁██████████████▁\",\"▁▁▁▁▁██████████████▁\",\"▁▁▁▁▁██████████████▁\",\"▁▁▁▁▁▁██████████████\",\"▁▁▁▁▁▁██████████████\",\"▁▁▁▁▁▁▁█████████████\",\"▁▁▁▁▁▁▁█████████████\",\"▁▁▁▁▁▁▁▁████████████\",\"▁▁▁▁▁▁▁▁████████████\",\"▁▁▁▁▁▁▁▁▁███████████\",\"▁▁▁▁▁▁▁▁▁███████████\",\"▁▁▁▁▁▁▁▁▁▁██████████\",\"▁▁▁▁▁▁▁▁▁▁██████████\",\"▁▁▁▁▁▁▁▁▁▁▁▁████████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁███████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁██████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████\",\"█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████\",\"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"███▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"████▁▁▁▁▁▁▁▁▁▁▁▁▁▁██\",\"█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"█████▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"██████▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"████████▁▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"█████████▁▁▁▁▁▁▁▁▁▁▁\",\"███████████▁▁▁▁▁▁▁▁▁\",\"████████████▁▁▁▁▁▁▁▁\",\"████████████▁▁▁▁▁▁▁▁\",\"██████████████▁▁▁▁▁▁\",\"██████████████▁▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁██████████████▁▁▁▁▁\",\"▁▁▁█████████████▁▁▁▁\",\"▁▁▁▁▁████████████▁▁▁\",\"▁▁▁▁▁████████████▁▁▁\",\"▁▁▁▁▁▁███████████▁▁▁\",\"▁▁▁▁▁▁▁▁█████████▁▁▁\",\"▁▁▁▁▁▁▁▁█████████▁▁▁\",\"▁▁▁▁▁▁▁▁▁█████████▁▁\",\"▁▁▁▁▁▁▁▁▁█████████▁▁\",\"▁▁▁▁▁▁▁▁▁▁█████████▁\",\"▁▁▁▁▁▁▁▁▁▁▁████████▁\",\"▁▁▁▁▁▁▁▁▁▁▁████████▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁███████▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁███████▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁███████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁███████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁████\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁███\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁██\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\",\"▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁\"]},\"moon\":{\"interval\":80,\"frames\":[\"🌑 \",\"🌒 \",\"🌓 \",\"🌔 \",\"🌕 \",\"🌖 \",\"🌗 \",\"🌘 \"]},\"runner\":{\"interval\":140,\"frames\":[\"🚶 \",\"🏃 \"]},\"pong\":{\"interval\":80,\"frames\":[\"▐⠂ ▌\",\"▐⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂▌\",\"▐ ⠠▌\",\"▐ ⡀▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐⠠ ▌\"]},\"shark\":{\"interval\":120,\"frames\":[\"▐|\\\\____________▌\",\"▐_|\\\\___________▌\",\"▐__|\\\\__________▌\",\"▐___|\\\\_________▌\",\"▐____|\\\\________▌\",\"▐_____|\\\\_______▌\",\"▐______|\\\\______▌\",\"▐_______|\\\\_____▌\",\"▐________|\\\\____▌\",\"▐_________|\\\\___▌\",\"▐__________|\\\\__▌\",\"▐___________|\\\\_▌\",\"▐____________|\\\\▌\",\"▐____________/|▌\",\"▐___________/|_▌\",\"▐__________/|__▌\",\"▐_________/|___▌\",\"▐________/|____▌\",\"▐_______/|_____▌\",\"▐______/|______▌\",\"▐_____/|_______▌\",\"▐____/|________▌\",\"▐___/|_________▌\",\"▐__/|__________▌\",\"▐_/|___________▌\",\"▐/|____________▌\"]},\"dqpb\":{\"interval\":100,\"frames\":[\"d\",\"q\",\"p\",\"b\"]},\"weather\":{\"interval\":100,\"frames\":[\"☀️ \",\"☀️ \",\"☀️ \",\"🌤 \",\"⛅️ \",\"🌥 \",\"☁️ \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"⛈ \",\"🌨 \",\"🌧 \",\"🌨 \",\"☁️ \",\"🌥 \",\"⛅️ \",\"🌤 \",\"☀️ \",\"☀️ \"]},\"christmas\":{\"interval\":400,\"frames\":[\"🌲\",\"🎄\"]},\"grenade\":{\"interval\":80,\"frames\":[\"، \",\"′ \",\" ´ \",\" ‾ \",\" ⸌\",\" ⸊\",\" |\",\" ⁎\",\" ⁕\",\" ෴ \",\" ⁓\",\" \",\" \",\" \"]},\"point\":{\"interval\":125,\"frames\":[\"∙∙∙\",\"●∙∙\",\"∙●∙\",\"∙∙●\",\"∙∙∙\"]},\"layer\":{\"interval\":150,\"frames\":[\"-\",\"=\",\"≡\"]},\"betaWave\":{\"interval\":80,\"frames\":[\"ρββββββ\",\"βρβββββ\",\"ββρββββ\",\"βββρβββ\",\"ββββρββ\",\"βββββρβ\",\"ββββββρ\"]},\"aesthetic\":{\"interval\":80,\"frames\":[\"▰▱▱▱▱▱▱\",\"▰▰▱▱▱▱▱\",\"▰▰▰▱▱▱▱\",\"▰▰▰▰▱▱▱\",\"▰▰▰▰▰▱▱\",\"▰▰▰▰▰▰▱\",\"▰▰▰▰▰▰▰\",\"▰▱▱▱▱▱▱\"]}}"); /***/ }), -/* 385 */ +/* 387 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const chalk = __webpack_require__(386); +const chalk = __webpack_require__(388); const isSupported = process.platform !== 'win32' || process.env.CI || process.env.TERM === 'xterm-256color'; @@ -50146,16 +50232,16 @@ module.exports = isSupported ? main : fallbacks; /***/ }), -/* 386 */ +/* 388 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const escapeStringRegexp = __webpack_require__(265); -const ansiStyles = __webpack_require__(387); -const stdoutColor = __webpack_require__(392).stdout; +const ansiStyles = __webpack_require__(389); +const stdoutColor = __webpack_require__(394).stdout; -const template = __webpack_require__(393); +const template = __webpack_require__(395); const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); @@ -50381,12 +50467,12 @@ module.exports.default = module.exports; // For TypeScript /***/ }), -/* 387 */ +/* 389 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; /* WEBPACK VAR INJECTION */(function(module) { -const colorConvert = __webpack_require__(388); +const colorConvert = __webpack_require__(390); const wrapAnsi16 = (fn, offset) => function () { const code = fn.apply(colorConvert, arguments); @@ -50554,11 +50640,11 @@ Object.defineProperty(module, 'exports', { /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(115)(module))) /***/ }), -/* 388 */ +/* 390 */ /***/ (function(module, exports, __webpack_require__) { -var conversions = __webpack_require__(389); -var route = __webpack_require__(391); +var conversions = __webpack_require__(391); +var route = __webpack_require__(393); var convert = {}; @@ -50638,11 +50724,11 @@ module.exports = convert; /***/ }), -/* 389 */ +/* 391 */ /***/ (function(module, exports, __webpack_require__) { /* MIT license */ -var cssKeywords = __webpack_require__(390); +var cssKeywords = __webpack_require__(392); // NOTE: conversions should only return primitive values (i.e. arrays, or // values that give correct `typeof` results). @@ -51512,7 +51598,7 @@ convert.rgb.gray = function (rgb) { /***/ }), -/* 390 */ +/* 392 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -51671,10 +51757,10 @@ module.exports = { /***/ }), -/* 391 */ +/* 393 */ /***/ (function(module, exports, __webpack_require__) { -var conversions = __webpack_require__(389); +var conversions = __webpack_require__(391); /* this function routes a model to all other models. @@ -51774,7 +51860,7 @@ module.exports = function (fromModel) { /***/ }), -/* 392 */ +/* 394 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -51912,7 +51998,7 @@ module.exports = { /***/ }), -/* 393 */ +/* 395 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -52047,18 +52133,18 @@ module.exports = (chalk, tmp) => { /***/ }), -/* 394 */ +/* 396 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const ansiRegex = __webpack_require__(395); +const ansiRegex = __webpack_require__(397); module.exports = string => typeof string === 'string' ? string.replace(ansiRegex(), '') : string; /***/ }), -/* 395 */ +/* 397 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -52075,14 +52161,14 @@ module.exports = ({onlyFirst = false} = {}) => { /***/ }), -/* 396 */ +/* 398 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var defaults = __webpack_require__(397) -var combining = __webpack_require__(399) +var defaults = __webpack_require__(399) +var combining = __webpack_require__(401) var DEFAULTS = { nul: 0, @@ -52181,10 +52267,10 @@ function bisearch(ucs) { /***/ }), -/* 397 */ +/* 399 */ /***/ (function(module, exports, __webpack_require__) { -var clone = __webpack_require__(398); +var clone = __webpack_require__(400); module.exports = function(options, defaults) { options = options || {}; @@ -52199,7 +52285,7 @@ module.exports = function(options, defaults) { }; /***/ }), -/* 398 */ +/* 400 */ /***/ (function(module, exports, __webpack_require__) { var clone = (function() { @@ -52371,7 +52457,7 @@ if ( true && module.exports) { /***/ }), -/* 399 */ +/* 401 */ /***/ (function(module, exports) { module.exports = [ @@ -52427,7 +52513,7 @@ module.exports = [ /***/ }), -/* 400 */ +/* 402 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -52443,7 +52529,7 @@ module.exports = ({stream = process.stdout} = {}) => { /***/ }), -/* 401 */ +/* 403 */ /***/ (function(module, exports, __webpack_require__) { var Stream = __webpack_require__(138) @@ -52594,7 +52680,7 @@ MuteStream.prototype.close = proxy('close') /***/ }), -/* 402 */ +/* 404 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52620,7 +52706,8 @@ const RunCommand = { name: 'run', async run(projects, projectGraph, { - extraArgs + extraArgs, + options }) { const batchedProjects = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_3__["topologicallyBatchProjects"])(projects, projectGraph); @@ -52631,20 +52718,26 @@ const RunCommand = { const scriptName = extraArgs[0]; const scriptArgs = extraArgs.slice(1); await Object(_utils_parallelize__WEBPACK_IMPORTED_MODULE_2__["parallelizeBatches"])(batchedProjects, async project => { - if (project.hasScript(scriptName)) { - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].info(`[${project.name}] running "${scriptName}" script`); - await project.runScriptStreaming(scriptName, { - args: scriptArgs - }); - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].success(`[${project.name}] complete`); + if (!project.hasScript(scriptName)) { + if (!!options['skip-missing']) { + return; + } + + throw new _utils_errors__WEBPACK_IMPORTED_MODULE_0__["CliError"](`[${project.name}] no "${scriptName}" script defined. To skip packages without the "${scriptName}" script pass --skip-missing`); } + + _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].info(`[${project.name}] running "${scriptName}" script`); + await project.runScriptStreaming(scriptName, { + args: scriptArgs + }); + _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].success(`[${project.name}] complete`); }); } }; /***/ }), -/* 403 */ +/* 405 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -52654,7 +52747,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(246); /* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(247); /* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(248); -/* harmony import */ var _utils_watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(404); +/* harmony import */ var _utils_watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(406); /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License @@ -52729,14 +52822,14 @@ const WatchCommand = { }; /***/ }), -/* 404 */ +/* 406 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "waitUntilWatchIsReady", function() { return waitUntilWatchIsReady; }); /* harmony import */ var rxjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(8); -/* harmony import */ var rxjs_operators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(405); +/* harmony import */ var rxjs_operators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(407); /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License @@ -52792,141 +52885,141 @@ function waitUntilWatchIsReady(stream, opts = {}) { } /***/ }), -/* 405 */ +/* 407 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony import */ var _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(406); +/* harmony import */ var _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(408); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "audit", function() { return _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__["audit"]; }); -/* harmony import */ var _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(407); +/* harmony import */ var _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(409); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "auditTime", function() { return _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__["auditTime"]; }); -/* harmony import */ var _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(408); +/* harmony import */ var _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(410); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buffer", function() { return _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__["buffer"]; }); -/* harmony import */ var _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(409); +/* harmony import */ var _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(411); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferCount", function() { return _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__["bufferCount"]; }); -/* harmony import */ var _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(410); +/* harmony import */ var _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(412); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferTime", function() { return _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__["bufferTime"]; }); -/* harmony import */ var _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(411); +/* harmony import */ var _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(413); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferToggle", function() { return _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__["bufferToggle"]; }); -/* harmony import */ var _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(412); +/* harmony import */ var _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(414); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferWhen", function() { return _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__["bufferWhen"]; }); -/* harmony import */ var _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(413); +/* harmony import */ var _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(415); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "catchError", function() { return _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__["catchError"]; }); -/* harmony import */ var _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(414); +/* harmony import */ var _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(416); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "combineAll", function() { return _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__["combineAll"]; }); -/* harmony import */ var _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(415); +/* harmony import */ var _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(417); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "combineLatest", function() { return _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__["combineLatest"]; }); -/* harmony import */ var _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(416); +/* harmony import */ var _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(418); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concat", function() { return _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__["concat"]; }); /* harmony import */ var _internal_operators_concatAll__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(80); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatAll", function() { return _internal_operators_concatAll__WEBPACK_IMPORTED_MODULE_11__["concatAll"]; }); -/* harmony import */ var _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(417); +/* harmony import */ var _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(419); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatMap", function() { return _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__["concatMap"]; }); -/* harmony import */ var _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(418); +/* harmony import */ var _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(420); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatMapTo", function() { return _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__["concatMapTo"]; }); -/* harmony import */ var _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(419); +/* harmony import */ var _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(421); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "count", function() { return _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__["count"]; }); -/* harmony import */ var _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(420); +/* harmony import */ var _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(422); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "debounce", function() { return _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__["debounce"]; }); -/* harmony import */ var _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(421); +/* harmony import */ var _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(423); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "debounceTime", function() { return _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__["debounceTime"]; }); -/* harmony import */ var _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(422); +/* harmony import */ var _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(424); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "defaultIfEmpty", function() { return _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__["defaultIfEmpty"]; }); -/* harmony import */ var _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(423); +/* harmony import */ var _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(425); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "delay", function() { return _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__["delay"]; }); -/* harmony import */ var _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(425); +/* harmony import */ var _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(427); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "delayWhen", function() { return _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__["delayWhen"]; }); -/* harmony import */ var _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(426); +/* harmony import */ var _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(428); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "dematerialize", function() { return _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__["dematerialize"]; }); -/* harmony import */ var _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(427); +/* harmony import */ var _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(429); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinct", function() { return _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__["distinct"]; }); -/* harmony import */ var _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(428); +/* harmony import */ var _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(430); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinctUntilChanged", function() { return _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__["distinctUntilChanged"]; }); -/* harmony import */ var _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(429); +/* harmony import */ var _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(431); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinctUntilKeyChanged", function() { return _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__["distinctUntilKeyChanged"]; }); -/* harmony import */ var _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(430); +/* harmony import */ var _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(432); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "elementAt", function() { return _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__["elementAt"]; }); -/* harmony import */ var _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(433); +/* harmony import */ var _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(435); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "endWith", function() { return _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__["endWith"]; }); -/* harmony import */ var _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(434); +/* harmony import */ var _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(436); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "every", function() { return _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__["every"]; }); -/* harmony import */ var _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(435); +/* harmony import */ var _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(437); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "exhaust", function() { return _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__["exhaust"]; }); -/* harmony import */ var _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(436); +/* harmony import */ var _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(438); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "exhaustMap", function() { return _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__["exhaustMap"]; }); -/* harmony import */ var _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(437); +/* harmony import */ var _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(439); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "expand", function() { return _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__["expand"]; }); /* harmony import */ var _internal_operators_filter__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(105); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "filter", function() { return _internal_operators_filter__WEBPACK_IMPORTED_MODULE_30__["filter"]; }); -/* harmony import */ var _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(438); +/* harmony import */ var _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(440); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "finalize", function() { return _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__["finalize"]; }); -/* harmony import */ var _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(439); +/* harmony import */ var _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(441); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "find", function() { return _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__["find"]; }); -/* harmony import */ var _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(440); +/* harmony import */ var _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(442); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "findIndex", function() { return _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__["findIndex"]; }); -/* harmony import */ var _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(441); +/* harmony import */ var _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(443); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "first", function() { return _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__["first"]; }); /* harmony import */ var _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_35__ = __webpack_require__(31); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "groupBy", function() { return _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_35__["groupBy"]; }); -/* harmony import */ var _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(442); +/* harmony import */ var _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(444); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ignoreElements", function() { return _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__["ignoreElements"]; }); -/* harmony import */ var _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(443); +/* harmony import */ var _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(445); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "isEmpty", function() { return _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__["isEmpty"]; }); -/* harmony import */ var _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(444); +/* harmony import */ var _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(446); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "last", function() { return _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__["last"]; }); /* harmony import */ var _internal_operators_map__WEBPACK_IMPORTED_MODULE_39__ = __webpack_require__(66); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "map", function() { return _internal_operators_map__WEBPACK_IMPORTED_MODULE_39__["map"]; }); -/* harmony import */ var _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(446); +/* harmony import */ var _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(448); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mapTo", function() { return _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__["mapTo"]; }); -/* harmony import */ var _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(447); +/* harmony import */ var _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(449); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "materialize", function() { return _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__["materialize"]; }); -/* harmony import */ var _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(448); +/* harmony import */ var _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(450); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "max", function() { return _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__["max"]; }); -/* harmony import */ var _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(451); +/* harmony import */ var _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(453); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "merge", function() { return _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__["merge"]; }); /* harmony import */ var _internal_operators_mergeAll__WEBPACK_IMPORTED_MODULE_44__ = __webpack_require__(81); @@ -52937,175 +53030,175 @@ __webpack_require__.r(__webpack_exports__); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "flatMap", function() { return _internal_operators_mergeMap__WEBPACK_IMPORTED_MODULE_45__["flatMap"]; }); -/* harmony import */ var _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(452); +/* harmony import */ var _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(454); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeMapTo", function() { return _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__["mergeMapTo"]; }); -/* harmony import */ var _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(453); +/* harmony import */ var _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(455); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeScan", function() { return _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__["mergeScan"]; }); -/* harmony import */ var _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(454); +/* harmony import */ var _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(456); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "min", function() { return _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__["min"]; }); -/* harmony import */ var _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__ = __webpack_require__(455); +/* harmony import */ var _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__ = __webpack_require__(457); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "multicast", function() { return _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__["multicast"]; }); /* harmony import */ var _internal_operators_observeOn__WEBPACK_IMPORTED_MODULE_50__ = __webpack_require__(41); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "observeOn", function() { return _internal_operators_observeOn__WEBPACK_IMPORTED_MODULE_50__["observeOn"]; }); -/* harmony import */ var _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__ = __webpack_require__(456); +/* harmony import */ var _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__ = __webpack_require__(458); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "onErrorResumeNext", function() { return _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__["onErrorResumeNext"]; }); -/* harmony import */ var _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__ = __webpack_require__(457); +/* harmony import */ var _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__ = __webpack_require__(459); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pairwise", function() { return _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__["pairwise"]; }); -/* harmony import */ var _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__ = __webpack_require__(458); +/* harmony import */ var _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__ = __webpack_require__(460); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "partition", function() { return _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__["partition"]; }); -/* harmony import */ var _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__ = __webpack_require__(459); +/* harmony import */ var _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__ = __webpack_require__(461); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pluck", function() { return _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__["pluck"]; }); -/* harmony import */ var _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__ = __webpack_require__(460); +/* harmony import */ var _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__ = __webpack_require__(462); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publish", function() { return _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__["publish"]; }); -/* harmony import */ var _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__ = __webpack_require__(461); +/* harmony import */ var _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__ = __webpack_require__(463); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishBehavior", function() { return _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__["publishBehavior"]; }); -/* harmony import */ var _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__ = __webpack_require__(462); +/* harmony import */ var _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__ = __webpack_require__(464); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishLast", function() { return _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__["publishLast"]; }); -/* harmony import */ var _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__ = __webpack_require__(463); +/* harmony import */ var _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__ = __webpack_require__(465); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishReplay", function() { return _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__["publishReplay"]; }); -/* harmony import */ var _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__ = __webpack_require__(464); +/* harmony import */ var _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__ = __webpack_require__(466); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "race", function() { return _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__["race"]; }); -/* harmony import */ var _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__ = __webpack_require__(449); +/* harmony import */ var _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__ = __webpack_require__(451); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "reduce", function() { return _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__["reduce"]; }); -/* harmony import */ var _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__ = __webpack_require__(465); +/* harmony import */ var _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__ = __webpack_require__(467); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "repeat", function() { return _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__["repeat"]; }); -/* harmony import */ var _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__ = __webpack_require__(466); +/* harmony import */ var _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__ = __webpack_require__(468); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "repeatWhen", function() { return _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__["repeatWhen"]; }); -/* harmony import */ var _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__ = __webpack_require__(467); +/* harmony import */ var _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__ = __webpack_require__(469); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "retry", function() { return _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__["retry"]; }); -/* harmony import */ var _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__ = __webpack_require__(468); +/* harmony import */ var _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__ = __webpack_require__(470); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "retryWhen", function() { return _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__["retryWhen"]; }); /* harmony import */ var _internal_operators_refCount__WEBPACK_IMPORTED_MODULE_65__ = __webpack_require__(30); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "refCount", function() { return _internal_operators_refCount__WEBPACK_IMPORTED_MODULE_65__["refCount"]; }); -/* harmony import */ var _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__ = __webpack_require__(469); +/* harmony import */ var _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__ = __webpack_require__(471); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sample", function() { return _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__["sample"]; }); -/* harmony import */ var _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__ = __webpack_require__(470); +/* harmony import */ var _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__ = __webpack_require__(472); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sampleTime", function() { return _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__["sampleTime"]; }); -/* harmony import */ var _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__ = __webpack_require__(450); +/* harmony import */ var _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__ = __webpack_require__(452); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "scan", function() { return _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__["scan"]; }); -/* harmony import */ var _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__ = __webpack_require__(471); +/* harmony import */ var _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__ = __webpack_require__(473); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sequenceEqual", function() { return _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__["sequenceEqual"]; }); -/* harmony import */ var _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__ = __webpack_require__(472); +/* harmony import */ var _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__ = __webpack_require__(474); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "share", function() { return _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__["share"]; }); -/* harmony import */ var _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__ = __webpack_require__(473); +/* harmony import */ var _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__ = __webpack_require__(475); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "shareReplay", function() { return _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__["shareReplay"]; }); -/* harmony import */ var _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__ = __webpack_require__(474); +/* harmony import */ var _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__ = __webpack_require__(476); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "single", function() { return _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__["single"]; }); -/* harmony import */ var _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__ = __webpack_require__(475); +/* harmony import */ var _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__ = __webpack_require__(477); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skip", function() { return _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__["skip"]; }); -/* harmony import */ var _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__ = __webpack_require__(476); +/* harmony import */ var _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__ = __webpack_require__(478); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipLast", function() { return _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__["skipLast"]; }); -/* harmony import */ var _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__ = __webpack_require__(477); +/* harmony import */ var _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__ = __webpack_require__(479); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipUntil", function() { return _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__["skipUntil"]; }); -/* harmony import */ var _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__ = __webpack_require__(478); +/* harmony import */ var _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__ = __webpack_require__(480); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipWhile", function() { return _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__["skipWhile"]; }); -/* harmony import */ var _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__ = __webpack_require__(479); +/* harmony import */ var _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__ = __webpack_require__(481); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "startWith", function() { return _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__["startWith"]; }); -/* harmony import */ var _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__ = __webpack_require__(480); +/* harmony import */ var _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__ = __webpack_require__(482); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "subscribeOn", function() { return _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__["subscribeOn"]; }); -/* harmony import */ var _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__ = __webpack_require__(482); +/* harmony import */ var _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__ = __webpack_require__(484); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchAll", function() { return _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__["switchAll"]; }); -/* harmony import */ var _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__ = __webpack_require__(483); +/* harmony import */ var _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__ = __webpack_require__(485); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchMap", function() { return _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__["switchMap"]; }); -/* harmony import */ var _internal_operators_switchMapTo__WEBPACK_IMPORTED_MODULE_81__ = __webpack_require__(484); +/* harmony import */ var _internal_operators_switchMapTo__WEBPACK_IMPORTED_MODULE_81__ = __webpack_require__(486); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchMapTo", function() { return _internal_operators_switchMapTo__WEBPACK_IMPORTED_MODULE_81__["switchMapTo"]; }); -/* harmony import */ var _internal_operators_take__WEBPACK_IMPORTED_MODULE_82__ = __webpack_require__(432); +/* harmony import */ var _internal_operators_take__WEBPACK_IMPORTED_MODULE_82__ = __webpack_require__(434); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "take", function() { return _internal_operators_take__WEBPACK_IMPORTED_MODULE_82__["take"]; }); -/* harmony import */ var _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__ = __webpack_require__(445); +/* harmony import */ var _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__ = __webpack_require__(447); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeLast", function() { return _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__["takeLast"]; }); -/* harmony import */ var _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__ = __webpack_require__(485); +/* harmony import */ var _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__ = __webpack_require__(487); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeUntil", function() { return _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__["takeUntil"]; }); -/* harmony import */ var _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__ = __webpack_require__(486); +/* harmony import */ var _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__ = __webpack_require__(488); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeWhile", function() { return _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__["takeWhile"]; }); -/* harmony import */ var _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__ = __webpack_require__(487); +/* harmony import */ var _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__ = __webpack_require__(489); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "tap", function() { return _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__["tap"]; }); -/* harmony import */ var _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__ = __webpack_require__(488); +/* harmony import */ var _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__ = __webpack_require__(490); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throttle", function() { return _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__["throttle"]; }); -/* harmony import */ var _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__ = __webpack_require__(489); +/* harmony import */ var _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__ = __webpack_require__(491); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throttleTime", function() { return _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__["throttleTime"]; }); -/* harmony import */ var _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__ = __webpack_require__(431); +/* harmony import */ var _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__ = __webpack_require__(433); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throwIfEmpty", function() { return _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__["throwIfEmpty"]; }); -/* harmony import */ var _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__ = __webpack_require__(490); +/* harmony import */ var _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__ = __webpack_require__(492); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeInterval", function() { return _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__["timeInterval"]; }); -/* harmony import */ var _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__ = __webpack_require__(491); +/* harmony import */ var _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__ = __webpack_require__(493); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeout", function() { return _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__["timeout"]; }); -/* harmony import */ var _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__ = __webpack_require__(492); +/* harmony import */ var _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__ = __webpack_require__(494); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeoutWith", function() { return _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__["timeoutWith"]; }); -/* harmony import */ var _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__ = __webpack_require__(493); +/* harmony import */ var _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__ = __webpack_require__(495); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timestamp", function() { return _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__["timestamp"]; }); -/* harmony import */ var _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__ = __webpack_require__(494); +/* harmony import */ var _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__ = __webpack_require__(496); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "toArray", function() { return _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__["toArray"]; }); -/* harmony import */ var _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__ = __webpack_require__(495); +/* harmony import */ var _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__ = __webpack_require__(497); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "window", function() { return _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__["window"]; }); -/* harmony import */ var _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__ = __webpack_require__(496); +/* harmony import */ var _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__ = __webpack_require__(498); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowCount", function() { return _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__["windowCount"]; }); -/* harmony import */ var _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__ = __webpack_require__(497); +/* harmony import */ var _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__ = __webpack_require__(499); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowTime", function() { return _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__["windowTime"]; }); -/* harmony import */ var _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__ = __webpack_require__(498); +/* harmony import */ var _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__ = __webpack_require__(500); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowToggle", function() { return _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__["windowToggle"]; }); -/* harmony import */ var _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__ = __webpack_require__(499); +/* harmony import */ var _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__ = __webpack_require__(501); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowWhen", function() { return _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__["windowWhen"]; }); -/* harmony import */ var _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__ = __webpack_require__(500); +/* harmony import */ var _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__ = __webpack_require__(502); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "withLatestFrom", function() { return _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__["withLatestFrom"]; }); -/* harmony import */ var _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__ = __webpack_require__(501); +/* harmony import */ var _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__ = __webpack_require__(503); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "zip", function() { return _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__["zip"]; }); -/* harmony import */ var _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__ = __webpack_require__(502); +/* harmony import */ var _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__ = __webpack_require__(504); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "zipAll", function() { return _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__["zipAll"]; }); /** PURE_IMPORTS_START PURE_IMPORTS_END */ @@ -53216,7 +53309,7 @@ __webpack_require__.r(__webpack_exports__); /***/ }), -/* 406 */ +/* 408 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53295,14 +53388,14 @@ var AuditSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 407 */ +/* 409 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "auditTime", function() { return auditTime; }); /* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(55); -/* harmony import */ var _audit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(406); +/* harmony import */ var _audit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(408); /* harmony import */ var _observable_timer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(108); /** PURE_IMPORTS_START _scheduler_async,_audit,_observable_timer PURE_IMPORTS_END */ @@ -53318,7 +53411,7 @@ function auditTime(duration, scheduler) { /***/ }), -/* 408 */ +/* 410 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53365,7 +53458,7 @@ var BufferSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 409 */ +/* 411 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53466,7 +53559,7 @@ var BufferSkipCountSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 410 */ +/* 412 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53627,7 +53720,7 @@ function dispatchBufferClose(arg) { /***/ }), -/* 411 */ +/* 413 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53746,7 +53839,7 @@ var BufferToggleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 412 */ +/* 414 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53839,7 +53932,7 @@ var BufferWhenSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 413 */ +/* 415 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53899,7 +53992,7 @@ var CatchSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 414 */ +/* 416 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53915,7 +54008,7 @@ function combineAll(project) { /***/ }), -/* 415 */ +/* 417 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53947,7 +54040,7 @@ function combineLatest() { /***/ }), -/* 416 */ +/* 418 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53967,7 +54060,7 @@ function concat() { /***/ }), -/* 417 */ +/* 419 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -53983,13 +54076,13 @@ function concatMap(project, resultSelector) { /***/ }), -/* 418 */ +/* 420 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "concatMapTo", function() { return concatMapTo; }); -/* harmony import */ var _concatMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(417); +/* harmony import */ var _concatMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(419); /** PURE_IMPORTS_START _concatMap PURE_IMPORTS_END */ function concatMapTo(innerObservable, resultSelector) { @@ -53999,7 +54092,7 @@ function concatMapTo(innerObservable, resultSelector) { /***/ }), -/* 419 */ +/* 421 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54064,7 +54157,7 @@ var CountSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 420 */ +/* 422 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54149,7 +54242,7 @@ var DebounceSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 421 */ +/* 423 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54225,7 +54318,7 @@ function dispatchNext(subscriber) { /***/ }), -/* 422 */ +/* 424 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54275,7 +54368,7 @@ var DefaultIfEmptySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 423 */ +/* 425 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54283,7 +54376,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "delay", function() { return delay; }); /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(12); /* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(55); -/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(424); +/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(426); /* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(11); /* harmony import */ var _Notification__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(42); /** PURE_IMPORTS_START tslib,_scheduler_async,_util_isDate,_Subscriber,_Notification PURE_IMPORTS_END */ @@ -54382,7 +54475,7 @@ var DelayMessage = /*@__PURE__*/ (function () { /***/ }), -/* 424 */ +/* 426 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54396,7 +54489,7 @@ function isDate(value) { /***/ }), -/* 425 */ +/* 427 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54542,7 +54635,7 @@ var SubscriptionDelaySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 426 */ +/* 428 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54580,7 +54673,7 @@ var DeMaterializeSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 427 */ +/* 429 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54656,7 +54749,7 @@ var DistinctSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 428 */ +/* 430 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54727,13 +54820,13 @@ var DistinctUntilChangedSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 429 */ +/* 431 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "distinctUntilKeyChanged", function() { return distinctUntilKeyChanged; }); -/* harmony import */ var _distinctUntilChanged__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(428); +/* harmony import */ var _distinctUntilChanged__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(430); /** PURE_IMPORTS_START _distinctUntilChanged PURE_IMPORTS_END */ function distinctUntilKeyChanged(key, compare) { @@ -54743,7 +54836,7 @@ function distinctUntilKeyChanged(key, compare) { /***/ }), -/* 430 */ +/* 432 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54751,9 +54844,9 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "elementAt", function() { return elementAt; }); /* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(62); /* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(105); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(431); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(422); -/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(432); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(433); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(424); +/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(434); /** PURE_IMPORTS_START _util_ArgumentOutOfRangeError,_filter,_throwIfEmpty,_defaultIfEmpty,_take PURE_IMPORTS_END */ @@ -54775,7 +54868,7 @@ function elementAt(index, defaultValue) { /***/ }), -/* 431 */ +/* 433 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54841,7 +54934,7 @@ function defaultErrorFactory() { /***/ }), -/* 432 */ +/* 434 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54903,7 +54996,7 @@ var TakeSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 433 */ +/* 435 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54925,7 +55018,7 @@ function endWith() { /***/ }), -/* 434 */ +/* 436 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -54987,7 +55080,7 @@ var EverySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 435 */ +/* 437 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55041,7 +55134,7 @@ var SwitchFirstSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 436 */ +/* 438 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55135,7 +55228,7 @@ var ExhaustMapSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 437 */ +/* 439 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55247,7 +55340,7 @@ var ExpandSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 438 */ +/* 440 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55285,7 +55378,7 @@ var FinallySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 439 */ +/* 441 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55357,13 +55450,13 @@ var FindValueSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 440 */ +/* 442 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "findIndex", function() { return findIndex; }); -/* harmony import */ var _operators_find__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(439); +/* harmony import */ var _operators_find__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(441); /** PURE_IMPORTS_START _operators_find PURE_IMPORTS_END */ function findIndex(predicate, thisArg) { @@ -55373,7 +55466,7 @@ function findIndex(predicate, thisArg) { /***/ }), -/* 441 */ +/* 443 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55381,9 +55474,9 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "first", function() { return first; }); /* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(63); /* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(105); -/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(432); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(422); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(431); +/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(434); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(424); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(433); /* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(25); /** PURE_IMPORTS_START _util_EmptyError,_filter,_take,_defaultIfEmpty,_throwIfEmpty,_util_identity PURE_IMPORTS_END */ @@ -55400,7 +55493,7 @@ function first(predicate, defaultValue) { /***/ }), -/* 442 */ +/* 444 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55437,7 +55530,7 @@ var IgnoreElementsSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 443 */ +/* 445 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55481,7 +55574,7 @@ var IsEmptySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 444 */ +/* 446 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55489,9 +55582,9 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "last", function() { return last; }); /* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(63); /* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(105); -/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(445); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(431); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(422); +/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(447); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(433); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(424); /* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(25); /** PURE_IMPORTS_START _util_EmptyError,_filter,_takeLast,_throwIfEmpty,_defaultIfEmpty,_util_identity PURE_IMPORTS_END */ @@ -55508,7 +55601,7 @@ function last(predicate, defaultValue) { /***/ }), -/* 445 */ +/* 447 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55585,7 +55678,7 @@ var TakeLastSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 446 */ +/* 448 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55624,7 +55717,7 @@ var MapToSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 447 */ +/* 449 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55674,13 +55767,13 @@ var MaterializeSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 448 */ +/* 450 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "max", function() { return max; }); -/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(449); +/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(451); /** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ function max(comparer) { @@ -55693,15 +55786,15 @@ function max(comparer) { /***/ }), -/* 449 */ +/* 451 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "reduce", function() { return reduce; }); -/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(450); -/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(445); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(422); +/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(452); +/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(447); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(424); /* harmony import */ var _util_pipe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(24); /** PURE_IMPORTS_START _scan,_takeLast,_defaultIfEmpty,_util_pipe PURE_IMPORTS_END */ @@ -55722,7 +55815,7 @@ function reduce(accumulator, seed) { /***/ }), -/* 450 */ +/* 452 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55804,7 +55897,7 @@ var ScanSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 451 */ +/* 453 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55824,7 +55917,7 @@ function merge() { /***/ }), -/* 452 */ +/* 454 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55849,7 +55942,7 @@ function mergeMapTo(innerObservable, resultSelector, concurrent) { /***/ }), -/* 453 */ +/* 455 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -55958,13 +56051,13 @@ var MergeScanSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 454 */ +/* 456 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "min", function() { return min; }); -/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(449); +/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(451); /** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ function min(comparer) { @@ -55977,7 +56070,7 @@ function min(comparer) { /***/ }), -/* 455 */ +/* 457 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56026,7 +56119,7 @@ var MulticastOperator = /*@__PURE__*/ (function () { /***/ }), -/* 456 */ +/* 458 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56116,7 +56209,7 @@ var OnErrorResumeNextSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 457 */ +/* 459 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56164,7 +56257,7 @@ var PairwiseSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 458 */ +/* 460 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56187,7 +56280,7 @@ function partition(predicate, thisArg) { /***/ }), -/* 459 */ +/* 461 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56227,14 +56320,14 @@ function plucker(props, length) { /***/ }), -/* 460 */ +/* 462 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publish", function() { return publish; }); /* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(27); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(455); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(457); /** PURE_IMPORTS_START _Subject,_multicast PURE_IMPORTS_END */ @@ -56247,14 +56340,14 @@ function publish(selector) { /***/ }), -/* 461 */ +/* 463 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publishBehavior", function() { return publishBehavior; }); /* harmony import */ var _BehaviorSubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(32); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(455); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(457); /** PURE_IMPORTS_START _BehaviorSubject,_multicast PURE_IMPORTS_END */ @@ -56265,14 +56358,14 @@ function publishBehavior(value) { /***/ }), -/* 462 */ +/* 464 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publishLast", function() { return publishLast; }); /* harmony import */ var _AsyncSubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(50); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(455); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(457); /** PURE_IMPORTS_START _AsyncSubject,_multicast PURE_IMPORTS_END */ @@ -56283,14 +56376,14 @@ function publishLast() { /***/ }), -/* 463 */ +/* 465 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publishReplay", function() { return publishReplay; }); /* harmony import */ var _ReplaySubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(33); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(455); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(457); /** PURE_IMPORTS_START _ReplaySubject,_multicast PURE_IMPORTS_END */ @@ -56306,7 +56399,7 @@ function publishReplay(bufferSize, windowTime, selectorOrScheduler, scheduler) { /***/ }), -/* 464 */ +/* 466 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56333,7 +56426,7 @@ function race() { /***/ }), -/* 465 */ +/* 467 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56398,7 +56491,7 @@ var RepeatSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 466 */ +/* 468 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56492,7 +56585,7 @@ var RepeatWhenSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 467 */ +/* 469 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56545,7 +56638,7 @@ var RetrySubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 468 */ +/* 470 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56631,7 +56724,7 @@ var RetryWhenSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 469 */ +/* 471 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56686,7 +56779,7 @@ var SampleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 470 */ +/* 472 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56746,7 +56839,7 @@ function dispatchNotification(state) { /***/ }), -/* 471 */ +/* 473 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56869,13 +56962,13 @@ var SequenceEqualCompareToSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 472 */ +/* 474 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "share", function() { return share; }); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(455); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(457); /* harmony import */ var _refCount__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(30); /* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(27); /** PURE_IMPORTS_START _multicast,_refCount,_Subject PURE_IMPORTS_END */ @@ -56892,7 +56985,7 @@ function share() { /***/ }), -/* 473 */ +/* 475 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -56961,7 +57054,7 @@ function shareReplayOperator(_a) { /***/ }), -/* 474 */ +/* 476 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57041,7 +57134,7 @@ var SingleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 475 */ +/* 477 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57083,7 +57176,7 @@ var SkipSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 476 */ +/* 478 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57145,7 +57238,7 @@ var SkipLastSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 477 */ +/* 479 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57202,7 +57295,7 @@ var SkipUntilSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 478 */ +/* 480 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57258,7 +57351,7 @@ var SkipWhileSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 479 */ +/* 481 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57287,13 +57380,13 @@ function startWith() { /***/ }), -/* 480 */ +/* 482 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeOn", function() { return subscribeOn; }); -/* harmony import */ var _observable_SubscribeOnObservable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(481); +/* harmony import */ var _observable_SubscribeOnObservable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(483); /** PURE_IMPORTS_START _observable_SubscribeOnObservable PURE_IMPORTS_END */ function subscribeOn(scheduler, delay) { @@ -57318,7 +57411,7 @@ var SubscribeOnOperator = /*@__PURE__*/ (function () { /***/ }), -/* 481 */ +/* 483 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57382,13 +57475,13 @@ var SubscribeOnObservable = /*@__PURE__*/ (function (_super) { /***/ }), -/* 482 */ +/* 484 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "switchAll", function() { return switchAll; }); -/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(483); +/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(485); /* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(25); /** PURE_IMPORTS_START _switchMap,_util_identity PURE_IMPORTS_END */ @@ -57400,7 +57493,7 @@ function switchAll() { /***/ }), -/* 483 */ +/* 485 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57488,13 +57581,13 @@ var SwitchMapSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 484 */ +/* 486 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "switchMapTo", function() { return switchMapTo; }); -/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(483); +/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(485); /** PURE_IMPORTS_START _switchMap PURE_IMPORTS_END */ function switchMapTo(innerObservable, resultSelector) { @@ -57504,7 +57597,7 @@ function switchMapTo(innerObservable, resultSelector) { /***/ }), -/* 485 */ +/* 487 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57552,7 +57645,7 @@ var TakeUntilSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 486 */ +/* 488 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57620,7 +57713,7 @@ var TakeWhileSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 487 */ +/* 489 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57708,7 +57801,7 @@ var TapSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 488 */ +/* 490 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57810,7 +57903,7 @@ var ThrottleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 489 */ +/* 491 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57819,7 +57912,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(12); /* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(11); /* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(55); -/* harmony import */ var _throttle__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(488); +/* harmony import */ var _throttle__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(490); /** PURE_IMPORTS_START tslib,_Subscriber,_scheduler_async,_throttle PURE_IMPORTS_END */ @@ -57908,7 +58001,7 @@ function dispatchNext(arg) { /***/ }), -/* 490 */ +/* 492 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57916,7 +58009,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timeInterval", function() { return timeInterval; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TimeInterval", function() { return TimeInterval; }); /* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(55); -/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(450); +/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(452); /* harmony import */ var _observable_defer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(91); /* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(66); /** PURE_IMPORTS_START _scheduler_async,_scan,_observable_defer,_map PURE_IMPORTS_END */ @@ -57952,7 +58045,7 @@ var TimeInterval = /*@__PURE__*/ (function () { /***/ }), -/* 491 */ +/* 493 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57960,7 +58053,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timeout", function() { return timeout; }); /* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(55); /* harmony import */ var _util_TimeoutError__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(64); -/* harmony import */ var _timeoutWith__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(492); +/* harmony import */ var _timeoutWith__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(494); /* harmony import */ var _observable_throwError__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(49); /** PURE_IMPORTS_START _scheduler_async,_util_TimeoutError,_timeoutWith,_observable_throwError PURE_IMPORTS_END */ @@ -57977,7 +58070,7 @@ function timeout(due, scheduler) { /***/ }), -/* 492 */ +/* 494 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -57985,7 +58078,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timeoutWith", function() { return timeoutWith; }); /* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(12); /* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(55); -/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(424); +/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(426); /* harmony import */ var _innerSubscribe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(90); /** PURE_IMPORTS_START tslib,_scheduler_async,_util_isDate,_innerSubscribe PURE_IMPORTS_END */ @@ -58056,7 +58149,7 @@ var TimeoutWithSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 493 */ +/* 495 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -58086,13 +58179,13 @@ var Timestamp = /*@__PURE__*/ (function () { /***/ }), -/* 494 */ +/* 496 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "toArray", function() { return toArray; }); -/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(449); +/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(451); /** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ function toArrayReducer(arr, item, index) { @@ -58109,7 +58202,7 @@ function toArray() { /***/ }), -/* 495 */ +/* 497 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -58187,7 +58280,7 @@ var WindowSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 496 */ +/* 498 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -58277,7 +58370,7 @@ var WindowCountSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 497 */ +/* 499 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -58447,7 +58540,7 @@ function dispatchWindowClose(state) { /***/ }), -/* 498 */ +/* 500 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -58590,7 +58683,7 @@ var WindowToggleSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 499 */ +/* 501 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -58687,7 +58780,7 @@ var WindowSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 500 */ +/* 502 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -58782,7 +58875,7 @@ var WithLatestFromSubscriber = /*@__PURE__*/ (function (_super) { /***/ }), -/* 501 */ +/* 503 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -58804,7 +58897,7 @@ function zip() { /***/ }), -/* 502 */ +/* 504 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -58820,7 +58913,7 @@ function zipAll(project) { /***/ }), -/* 503 */ +/* 505 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -58830,7 +58923,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(246); /* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(248); /* harmony import */ var _utils_projects_tree__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(370); -/* harmony import */ var _utils_kibana__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(504); +/* harmony import */ var _utils_kibana__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(506); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } @@ -58901,7 +58994,7 @@ function toArray(value) { } /***/ }), -/* 504 */ +/* 506 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -58909,13 +59002,13 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Kibana", function() { return Kibana; }); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(4); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(505); +/* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(507); /* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(multimatch__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var is_path_inside__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(239); /* harmony import */ var is_path_inside__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(is_path_inside__WEBPACK_IMPORTED_MODULE_2__); /* harmony import */ var _yarn_lock__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(365); /* harmony import */ var _projects__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(248); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(509); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(511); function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } @@ -59066,15 +59159,15 @@ class Kibana { } /***/ }), -/* 505 */ +/* 507 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const minimatch = __webpack_require__(150); -const arrayUnion = __webpack_require__(506); -const arrayDiffer = __webpack_require__(507); -const arrify = __webpack_require__(508); +const arrayUnion = __webpack_require__(508); +const arrayDiffer = __webpack_require__(509); +const arrify = __webpack_require__(510); module.exports = (list, patterns, options = {}) => { list = arrify(list); @@ -59098,7 +59191,7 @@ module.exports = (list, patterns, options = {}) => { /***/ }), -/* 506 */ +/* 508 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59110,7 +59203,7 @@ module.exports = (...arguments_) => { /***/ }), -/* 507 */ +/* 509 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59125,7 +59218,7 @@ module.exports = arrayDiffer; /***/ }), -/* 508 */ +/* 510 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59155,7 +59248,7 @@ module.exports = arrify; /***/ }), -/* 509 */ +/* 511 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -59214,12 +59307,12 @@ function getProjectPaths({ } /***/ }), -/* 510 */ +/* 512 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony import */ var _build_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(511); +/* harmony import */ var _build_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(513); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _build_production_projects__WEBPACK_IMPORTED_MODULE_0__["buildProductionProjects"]; }); /* @@ -59232,19 +59325,19 @@ __webpack_require__.r(__webpack_exports__); /***/ }), -/* 511 */ +/* 513 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return buildProductionProjects; }); -/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(512); +/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(514); /* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cpy__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(143); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(4); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(509); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(511); /* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(131); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(246); /* harmony import */ var _utils_package_json__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(251); @@ -59370,7 +59463,7 @@ async function copyToBuild(project, kibanaRoot, buildRoot) { } /***/ }), -/* 512 */ +/* 514 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59378,14 +59471,14 @@ async function copyToBuild(project, kibanaRoot, buildRoot) { const EventEmitter = __webpack_require__(156); const path = __webpack_require__(4); const os = __webpack_require__(121); -const pMap = __webpack_require__(513); -const arrify = __webpack_require__(508); -const globby = __webpack_require__(514); -const hasGlob = __webpack_require__(710); -const cpFile = __webpack_require__(712); -const junk = __webpack_require__(722); -const pFilter = __webpack_require__(723); -const CpyError = __webpack_require__(725); +const pMap = __webpack_require__(515); +const arrify = __webpack_require__(510); +const globby = __webpack_require__(516); +const hasGlob = __webpack_require__(712); +const cpFile = __webpack_require__(714); +const junk = __webpack_require__(724); +const pFilter = __webpack_require__(725); +const CpyError = __webpack_require__(727); const defaultOptions = { ignoreJunk: true @@ -59536,7 +59629,7 @@ module.exports = (source, destination, { /***/ }), -/* 513 */ +/* 515 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59624,17 +59717,17 @@ module.exports = async ( /***/ }), -/* 514 */ +/* 516 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); -const arrayUnion = __webpack_require__(515); +const arrayUnion = __webpack_require__(517); const glob = __webpack_require__(147); -const fastGlob = __webpack_require__(517); -const dirGlob = __webpack_require__(703); -const gitignore = __webpack_require__(706); +const fastGlob = __webpack_require__(519); +const dirGlob = __webpack_require__(705); +const gitignore = __webpack_require__(708); const DEFAULT_FILTER = () => false; @@ -59779,12 +59872,12 @@ module.exports.gitignore = gitignore; /***/ }), -/* 515 */ +/* 517 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var arrayUniq = __webpack_require__(516); +var arrayUniq = __webpack_require__(518); module.exports = function () { return arrayUniq([].concat.apply([], arguments)); @@ -59792,7 +59885,7 @@ module.exports = function () { /***/ }), -/* 516 */ +/* 518 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59861,10 +59954,10 @@ if ('Set' in global) { /***/ }), -/* 517 */ +/* 519 */ /***/ (function(module, exports, __webpack_require__) { -const pkg = __webpack_require__(518); +const pkg = __webpack_require__(520); module.exports = pkg.async; module.exports.default = pkg.async; @@ -59877,19 +59970,19 @@ module.exports.generateTasks = pkg.generateTasks; /***/ }), -/* 518 */ +/* 520 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var optionsManager = __webpack_require__(519); -var taskManager = __webpack_require__(520); -var reader_async_1 = __webpack_require__(674); -var reader_stream_1 = __webpack_require__(698); -var reader_sync_1 = __webpack_require__(699); -var arrayUtils = __webpack_require__(701); -var streamUtils = __webpack_require__(702); +var optionsManager = __webpack_require__(521); +var taskManager = __webpack_require__(522); +var reader_async_1 = __webpack_require__(676); +var reader_stream_1 = __webpack_require__(700); +var reader_sync_1 = __webpack_require__(701); +var arrayUtils = __webpack_require__(703); +var streamUtils = __webpack_require__(704); /** * Synchronous API. */ @@ -59955,7 +60048,7 @@ function isString(source) { /***/ }), -/* 519 */ +/* 521 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -59993,13 +60086,13 @@ exports.prepare = prepare; /***/ }), -/* 520 */ +/* 522 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var patternUtils = __webpack_require__(521); +var patternUtils = __webpack_require__(523); /** * Generate tasks based on parent directory of each pattern. */ @@ -60090,16 +60183,16 @@ exports.convertPatternGroupToTask = convertPatternGroupToTask; /***/ }), -/* 521 */ +/* 523 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(4); -var globParent = __webpack_require__(522); +var globParent = __webpack_require__(524); var isGlob = __webpack_require__(172); -var micromatch = __webpack_require__(525); +var micromatch = __webpack_require__(527); var GLOBSTAR = '**'; /** * Return true for static pattern. @@ -60245,15 +60338,15 @@ exports.matchAny = matchAny; /***/ }), -/* 522 */ +/* 524 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var path = __webpack_require__(4); -var isglob = __webpack_require__(523); -var pathDirname = __webpack_require__(524); +var isglob = __webpack_require__(525); +var pathDirname = __webpack_require__(526); var isWin32 = __webpack_require__(121).platform() === 'win32'; module.exports = function globParent(str) { @@ -60276,7 +60369,7 @@ module.exports = function globParent(str) { /***/ }), -/* 523 */ +/* 525 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -60307,7 +60400,7 @@ module.exports = function isGlob(str) { /***/ }), -/* 524 */ +/* 526 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60457,7 +60550,7 @@ module.exports.win32 = win32; /***/ }), -/* 525 */ +/* 527 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60468,18 +60561,18 @@ module.exports.win32 = win32; */ var util = __webpack_require__(112); -var braces = __webpack_require__(526); -var toRegex = __webpack_require__(527); -var extend = __webpack_require__(640); +var braces = __webpack_require__(528); +var toRegex = __webpack_require__(529); +var extend = __webpack_require__(642); /** * Local dependencies */ -var compilers = __webpack_require__(642); -var parsers = __webpack_require__(669); -var cache = __webpack_require__(670); -var utils = __webpack_require__(671); +var compilers = __webpack_require__(644); +var parsers = __webpack_require__(671); +var cache = __webpack_require__(672); +var utils = __webpack_require__(673); var MAX_LENGTH = 1024 * 64; /** @@ -61341,7 +61434,7 @@ module.exports = micromatch; /***/ }), -/* 526 */ +/* 528 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61351,18 +61444,18 @@ module.exports = micromatch; * Module dependencies */ -var toRegex = __webpack_require__(527); -var unique = __webpack_require__(549); -var extend = __webpack_require__(550); +var toRegex = __webpack_require__(529); +var unique = __webpack_require__(551); +var extend = __webpack_require__(552); /** * Local dependencies */ -var compilers = __webpack_require__(552); -var parsers = __webpack_require__(565); -var Braces = __webpack_require__(569); -var utils = __webpack_require__(553); +var compilers = __webpack_require__(554); +var parsers = __webpack_require__(567); +var Braces = __webpack_require__(571); +var utils = __webpack_require__(555); var MAX_LENGTH = 1024 * 64; var cache = {}; @@ -61666,16 +61759,16 @@ module.exports = braces; /***/ }), -/* 527 */ +/* 529 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var safe = __webpack_require__(528); -var define = __webpack_require__(534); -var extend = __webpack_require__(542); -var not = __webpack_require__(546); +var safe = __webpack_require__(530); +var define = __webpack_require__(536); +var extend = __webpack_require__(544); +var not = __webpack_require__(548); var MAX_LENGTH = 1024 * 64; /** @@ -61828,10 +61921,10 @@ module.exports.makeRe = makeRe; /***/ }), -/* 528 */ +/* 530 */ /***/ (function(module, exports, __webpack_require__) { -var parse = __webpack_require__(529); +var parse = __webpack_require__(531); var types = parse.types; module.exports = function (re, opts) { @@ -61877,13 +61970,13 @@ function isRegExp (x) { /***/ }), -/* 529 */ +/* 531 */ /***/ (function(module, exports, __webpack_require__) { -var util = __webpack_require__(530); -var types = __webpack_require__(531); -var sets = __webpack_require__(532); -var positions = __webpack_require__(533); +var util = __webpack_require__(532); +var types = __webpack_require__(533); +var sets = __webpack_require__(534); +var positions = __webpack_require__(535); module.exports = function(regexpStr) { @@ -62165,11 +62258,11 @@ module.exports.types = types; /***/ }), -/* 530 */ +/* 532 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(531); -var sets = __webpack_require__(532); +var types = __webpack_require__(533); +var sets = __webpack_require__(534); // All of these are private and only used by randexp. @@ -62282,7 +62375,7 @@ exports.error = function(regexp, msg) { /***/ }), -/* 531 */ +/* 533 */ /***/ (function(module, exports) { module.exports = { @@ -62298,10 +62391,10 @@ module.exports = { /***/ }), -/* 532 */ +/* 534 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(531); +var types = __webpack_require__(533); var INTS = function() { return [{ type: types.RANGE , from: 48, to: 57 }]; @@ -62386,10 +62479,10 @@ exports.anyChar = function() { /***/ }), -/* 533 */ +/* 535 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(531); +var types = __webpack_require__(533); exports.wordBoundary = function() { return { type: types.POSITION, value: 'b' }; @@ -62409,7 +62502,7 @@ exports.end = function() { /***/ }), -/* 534 */ +/* 536 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62422,8 +62515,8 @@ exports.end = function() { -var isobject = __webpack_require__(535); -var isDescriptor = __webpack_require__(536); +var isobject = __webpack_require__(537); +var isDescriptor = __webpack_require__(538); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -62454,7 +62547,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 535 */ +/* 537 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62473,7 +62566,7 @@ module.exports = function isObject(val) { /***/ }), -/* 536 */ +/* 538 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62486,9 +62579,9 @@ module.exports = function isObject(val) { -var typeOf = __webpack_require__(537); -var isAccessor = __webpack_require__(538); -var isData = __webpack_require__(540); +var typeOf = __webpack_require__(539); +var isAccessor = __webpack_require__(540); +var isData = __webpack_require__(542); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -62502,7 +62595,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 537 */ +/* 539 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -62637,7 +62730,7 @@ function isBuffer(val) { /***/ }), -/* 538 */ +/* 540 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62650,7 +62743,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(539); +var typeOf = __webpack_require__(541); // accessor descriptor properties var accessor = { @@ -62713,7 +62806,7 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 539 */ +/* 541 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -62848,7 +62941,7 @@ function isBuffer(val) { /***/ }), -/* 540 */ +/* 542 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62861,7 +62954,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(541); +var typeOf = __webpack_require__(543); module.exports = function isDataDescriptor(obj, prop) { // data descriptor properties @@ -62904,7 +62997,7 @@ module.exports = function isDataDescriptor(obj, prop) { /***/ }), -/* 541 */ +/* 543 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -63039,14 +63132,14 @@ function isBuffer(val) { /***/ }), -/* 542 */ +/* 544 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(543); -var assignSymbols = __webpack_require__(545); +var isExtendable = __webpack_require__(545); +var assignSymbols = __webpack_require__(547); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -63106,7 +63199,7 @@ function isEnum(obj, key) { /***/ }), -/* 543 */ +/* 545 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63119,7 +63212,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(544); +var isPlainObject = __webpack_require__(546); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -63127,7 +63220,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 544 */ +/* 546 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63140,7 +63233,7 @@ module.exports = function isExtendable(val) { -var isObject = __webpack_require__(535); +var isObject = __webpack_require__(537); function isObjectObject(o) { return isObject(o) === true @@ -63171,7 +63264,7 @@ module.exports = function isPlainObject(o) { /***/ }), -/* 545 */ +/* 547 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63218,14 +63311,14 @@ module.exports = function(receiver, objects) { /***/ }), -/* 546 */ +/* 548 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(547); -var safe = __webpack_require__(528); +var extend = __webpack_require__(549); +var safe = __webpack_require__(530); /** * The main export is a function that takes a `pattern` string and an `options` object. @@ -63297,14 +63390,14 @@ module.exports = toRegex; /***/ }), -/* 547 */ +/* 549 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(548); -var assignSymbols = __webpack_require__(545); +var isExtendable = __webpack_require__(550); +var assignSymbols = __webpack_require__(547); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -63364,7 +63457,7 @@ function isEnum(obj, key) { /***/ }), -/* 548 */ +/* 550 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63377,7 +63470,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(544); +var isPlainObject = __webpack_require__(546); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -63385,7 +63478,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 549 */ +/* 551 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63435,13 +63528,13 @@ module.exports.immutable = function uniqueImmutable(arr) { /***/ }), -/* 550 */ +/* 552 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(551); +var isObject = __webpack_require__(553); module.exports = function extend(o/*, objects*/) { if (!isObject(o)) { o = {}; } @@ -63475,7 +63568,7 @@ function hasOwn(obj, key) { /***/ }), -/* 551 */ +/* 553 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63495,13 +63588,13 @@ module.exports = function isExtendable(val) { /***/ }), -/* 552 */ +/* 554 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(553); +var utils = __webpack_require__(555); module.exports = function(braces, options) { braces.compiler @@ -63784,25 +63877,25 @@ function hasQueue(node) { /***/ }), -/* 553 */ +/* 555 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var splitString = __webpack_require__(554); +var splitString = __webpack_require__(556); var utils = module.exports; /** * Module dependencies */ -utils.extend = __webpack_require__(550); -utils.flatten = __webpack_require__(557); -utils.isObject = __webpack_require__(535); -utils.fillRange = __webpack_require__(558); -utils.repeat = __webpack_require__(564); -utils.unique = __webpack_require__(549); +utils.extend = __webpack_require__(552); +utils.flatten = __webpack_require__(559); +utils.isObject = __webpack_require__(537); +utils.fillRange = __webpack_require__(560); +utils.repeat = __webpack_require__(566); +utils.unique = __webpack_require__(551); utils.define = function(obj, key, val) { Object.defineProperty(obj, key, { @@ -64134,7 +64227,7 @@ utils.escapeRegex = function(str) { /***/ }), -/* 554 */ +/* 556 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64147,7 +64240,7 @@ utils.escapeRegex = function(str) { -var extend = __webpack_require__(555); +var extend = __webpack_require__(557); module.exports = function(str, options, fn) { if (typeof str !== 'string') { @@ -64312,14 +64405,14 @@ function keepEscaping(opts, str, idx) { /***/ }), -/* 555 */ +/* 557 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(556); -var assignSymbols = __webpack_require__(545); +var isExtendable = __webpack_require__(558); +var assignSymbols = __webpack_require__(547); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -64379,7 +64472,7 @@ function isEnum(obj, key) { /***/ }), -/* 556 */ +/* 558 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64392,7 +64485,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(544); +var isPlainObject = __webpack_require__(546); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -64400,7 +64493,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 557 */ +/* 559 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64429,7 +64522,7 @@ function flat(arr, res) { /***/ }), -/* 558 */ +/* 560 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64443,10 +64536,10 @@ function flat(arr, res) { var util = __webpack_require__(112); -var isNumber = __webpack_require__(559); -var extend = __webpack_require__(550); -var repeat = __webpack_require__(562); -var toRegex = __webpack_require__(563); +var isNumber = __webpack_require__(561); +var extend = __webpack_require__(552); +var repeat = __webpack_require__(564); +var toRegex = __webpack_require__(565); /** * Return a range of numbers or letters. @@ -64644,7 +64737,7 @@ module.exports = fillRange; /***/ }), -/* 559 */ +/* 561 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64657,7 +64750,7 @@ module.exports = fillRange; -var typeOf = __webpack_require__(560); +var typeOf = __webpack_require__(562); module.exports = function isNumber(num) { var type = typeOf(num); @@ -64673,10 +64766,10 @@ module.exports = function isNumber(num) { /***/ }), -/* 560 */ +/* 562 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(561); +var isBuffer = __webpack_require__(563); var toString = Object.prototype.toString; /** @@ -64795,7 +64888,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 561 */ +/* 563 */ /***/ (function(module, exports) { /*! @@ -64822,7 +64915,7 @@ function isSlowBuffer (obj) { /***/ }), -/* 562 */ +/* 564 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64899,7 +64992,7 @@ function repeat(str, num) { /***/ }), -/* 563 */ +/* 565 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64912,8 +65005,8 @@ function repeat(str, num) { -var repeat = __webpack_require__(562); -var isNumber = __webpack_require__(559); +var repeat = __webpack_require__(564); +var isNumber = __webpack_require__(561); var cache = {}; function toRegexRange(min, max, options) { @@ -65200,7 +65293,7 @@ module.exports = toRegexRange; /***/ }), -/* 564 */ +/* 566 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -65225,14 +65318,14 @@ module.exports = function repeat(ele, num) { /***/ }), -/* 565 */ +/* 567 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Node = __webpack_require__(566); -var utils = __webpack_require__(553); +var Node = __webpack_require__(568); +var utils = __webpack_require__(555); /** * Braces parsers @@ -65592,15 +65685,15 @@ function concatNodes(pos, node, parent, options) { /***/ }), -/* 566 */ +/* 568 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(535); -var define = __webpack_require__(567); -var utils = __webpack_require__(568); +var isObject = __webpack_require__(537); +var define = __webpack_require__(569); +var utils = __webpack_require__(570); var ownNames; /** @@ -66091,7 +66184,7 @@ exports = module.exports = Node; /***/ }), -/* 567 */ +/* 569 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -66104,7 +66197,7 @@ exports = module.exports = Node; -var isDescriptor = __webpack_require__(536); +var isDescriptor = __webpack_require__(538); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -66129,13 +66222,13 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 568 */ +/* 570 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(560); +var typeOf = __webpack_require__(562); var utils = module.exports; /** @@ -67155,17 +67248,17 @@ function assert(val, message) { /***/ }), -/* 569 */ +/* 571 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(550); -var Snapdragon = __webpack_require__(570); -var compilers = __webpack_require__(552); -var parsers = __webpack_require__(565); -var utils = __webpack_require__(553); +var extend = __webpack_require__(552); +var Snapdragon = __webpack_require__(572); +var compilers = __webpack_require__(554); +var parsers = __webpack_require__(567); +var utils = __webpack_require__(555); /** * Customize Snapdragon parser and renderer @@ -67266,17 +67359,17 @@ module.exports = Braces; /***/ }), -/* 570 */ +/* 572 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Base = __webpack_require__(571); -var define = __webpack_require__(598); -var Compiler = __webpack_require__(608); -var Parser = __webpack_require__(637); -var utils = __webpack_require__(617); +var Base = __webpack_require__(573); +var define = __webpack_require__(600); +var Compiler = __webpack_require__(610); +var Parser = __webpack_require__(639); +var utils = __webpack_require__(619); var regexCache = {}; var cache = {}; @@ -67447,20 +67540,20 @@ module.exports.Parser = Parser; /***/ }), -/* 571 */ +/* 573 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(112); -var define = __webpack_require__(572); -var CacheBase = __webpack_require__(573); -var Emitter = __webpack_require__(574); -var isObject = __webpack_require__(535); -var merge = __webpack_require__(592); -var pascal = __webpack_require__(595); -var cu = __webpack_require__(596); +var define = __webpack_require__(574); +var CacheBase = __webpack_require__(575); +var Emitter = __webpack_require__(576); +var isObject = __webpack_require__(537); +var merge = __webpack_require__(594); +var pascal = __webpack_require__(597); +var cu = __webpack_require__(598); /** * Optionally define a custom `cache` namespace to use. @@ -67889,7 +67982,7 @@ module.exports.namespace = namespace; /***/ }), -/* 572 */ +/* 574 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67902,7 +67995,7 @@ module.exports.namespace = namespace; -var isDescriptor = __webpack_require__(536); +var isDescriptor = __webpack_require__(538); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -67927,21 +68020,21 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 573 */ +/* 575 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(535); -var Emitter = __webpack_require__(574); -var visit = __webpack_require__(575); -var toPath = __webpack_require__(578); -var union = __webpack_require__(579); -var del = __webpack_require__(583); -var get = __webpack_require__(581); -var has = __webpack_require__(588); -var set = __webpack_require__(591); +var isObject = __webpack_require__(537); +var Emitter = __webpack_require__(576); +var visit = __webpack_require__(577); +var toPath = __webpack_require__(580); +var union = __webpack_require__(581); +var del = __webpack_require__(585); +var get = __webpack_require__(583); +var has = __webpack_require__(590); +var set = __webpack_require__(593); /** * Create a `Cache` constructor that when instantiated will @@ -68195,7 +68288,7 @@ module.exports.namespace = namespace; /***/ }), -/* 574 */ +/* 576 */ /***/ (function(module, exports, __webpack_require__) { @@ -68364,7 +68457,7 @@ Emitter.prototype.hasListeners = function(event){ /***/ }), -/* 575 */ +/* 577 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68377,8 +68470,8 @@ Emitter.prototype.hasListeners = function(event){ -var visit = __webpack_require__(576); -var mapVisit = __webpack_require__(577); +var visit = __webpack_require__(578); +var mapVisit = __webpack_require__(579); module.exports = function(collection, method, val) { var result; @@ -68401,7 +68494,7 @@ module.exports = function(collection, method, val) { /***/ }), -/* 576 */ +/* 578 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68414,7 +68507,7 @@ module.exports = function(collection, method, val) { -var isObject = __webpack_require__(535); +var isObject = __webpack_require__(537); module.exports = function visit(thisArg, method, target, val) { if (!isObject(thisArg) && typeof thisArg !== 'function') { @@ -68441,14 +68534,14 @@ module.exports = function visit(thisArg, method, target, val) { /***/ }), -/* 577 */ +/* 579 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(112); -var visit = __webpack_require__(576); +var visit = __webpack_require__(578); /** * Map `visit` over an array of objects. @@ -68485,7 +68578,7 @@ function isObject(val) { /***/ }), -/* 578 */ +/* 580 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68498,7 +68591,7 @@ function isObject(val) { -var typeOf = __webpack_require__(560); +var typeOf = __webpack_require__(562); module.exports = function toPath(args) { if (typeOf(args) !== 'arguments') { @@ -68525,16 +68618,16 @@ function filter(arr) { /***/ }), -/* 579 */ +/* 581 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(551); -var union = __webpack_require__(580); -var get = __webpack_require__(581); -var set = __webpack_require__(582); +var isObject = __webpack_require__(553); +var union = __webpack_require__(582); +var get = __webpack_require__(583); +var set = __webpack_require__(584); module.exports = function unionValue(obj, prop, value) { if (!isObject(obj)) { @@ -68562,7 +68655,7 @@ function arrayify(val) { /***/ }), -/* 580 */ +/* 582 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68598,7 +68691,7 @@ module.exports = function union(init) { /***/ }), -/* 581 */ +/* 583 */ /***/ (function(module, exports) { /*! @@ -68654,7 +68747,7 @@ function toString(val) { /***/ }), -/* 582 */ +/* 584 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68667,10 +68760,10 @@ function toString(val) { -var split = __webpack_require__(554); -var extend = __webpack_require__(550); -var isPlainObject = __webpack_require__(544); -var isObject = __webpack_require__(551); +var split = __webpack_require__(556); +var extend = __webpack_require__(552); +var isPlainObject = __webpack_require__(546); +var isObject = __webpack_require__(553); module.exports = function(obj, prop, val) { if (!isObject(obj)) { @@ -68716,7 +68809,7 @@ function isValidKey(key) { /***/ }), -/* 583 */ +/* 585 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68729,8 +68822,8 @@ function isValidKey(key) { -var isObject = __webpack_require__(535); -var has = __webpack_require__(584); +var isObject = __webpack_require__(537); +var has = __webpack_require__(586); module.exports = function unset(obj, prop) { if (!isObject(obj)) { @@ -68755,7 +68848,7 @@ module.exports = function unset(obj, prop) { /***/ }), -/* 584 */ +/* 586 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68768,9 +68861,9 @@ module.exports = function unset(obj, prop) { -var isObject = __webpack_require__(585); -var hasValues = __webpack_require__(587); -var get = __webpack_require__(581); +var isObject = __webpack_require__(587); +var hasValues = __webpack_require__(589); +var get = __webpack_require__(583); module.exports = function(obj, prop, noZero) { if (isObject(obj)) { @@ -68781,7 +68874,7 @@ module.exports = function(obj, prop, noZero) { /***/ }), -/* 585 */ +/* 587 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68794,7 +68887,7 @@ module.exports = function(obj, prop, noZero) { -var isArray = __webpack_require__(586); +var isArray = __webpack_require__(588); module.exports = function isObject(val) { return val != null && typeof val === 'object' && isArray(val) === false; @@ -68802,7 +68895,7 @@ module.exports = function isObject(val) { /***/ }), -/* 586 */ +/* 588 */ /***/ (function(module, exports) { var toString = {}.toString; @@ -68813,7 +68906,7 @@ module.exports = Array.isArray || function (arr) { /***/ }), -/* 587 */ +/* 589 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68856,7 +68949,7 @@ module.exports = function hasValue(o, noZero) { /***/ }), -/* 588 */ +/* 590 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68869,9 +68962,9 @@ module.exports = function hasValue(o, noZero) { -var isObject = __webpack_require__(535); -var hasValues = __webpack_require__(589); -var get = __webpack_require__(581); +var isObject = __webpack_require__(537); +var hasValues = __webpack_require__(591); +var get = __webpack_require__(583); module.exports = function(val, prop) { return hasValues(isObject(val) && prop ? get(val, prop) : val); @@ -68879,7 +68972,7 @@ module.exports = function(val, prop) { /***/ }), -/* 589 */ +/* 591 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68892,8 +68985,8 @@ module.exports = function(val, prop) { -var typeOf = __webpack_require__(590); -var isNumber = __webpack_require__(559); +var typeOf = __webpack_require__(592); +var isNumber = __webpack_require__(561); module.exports = function hasValue(val) { // is-number checks for NaN and other edge cases @@ -68946,10 +69039,10 @@ module.exports = function hasValue(val) { /***/ }), -/* 590 */ +/* 592 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(561); +var isBuffer = __webpack_require__(563); var toString = Object.prototype.toString; /** @@ -69071,7 +69164,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 591 */ +/* 593 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69084,10 +69177,10 @@ module.exports = function kindOf(val) { -var split = __webpack_require__(554); -var extend = __webpack_require__(550); -var isPlainObject = __webpack_require__(544); -var isObject = __webpack_require__(551); +var split = __webpack_require__(556); +var extend = __webpack_require__(552); +var isPlainObject = __webpack_require__(546); +var isObject = __webpack_require__(553); module.exports = function(obj, prop, val) { if (!isObject(obj)) { @@ -69133,14 +69226,14 @@ function isValidKey(key) { /***/ }), -/* 592 */ +/* 594 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(593); -var forIn = __webpack_require__(594); +var isExtendable = __webpack_require__(595); +var forIn = __webpack_require__(596); function mixinDeep(target, objects) { var len = arguments.length, i = 0; @@ -69204,7 +69297,7 @@ module.exports = mixinDeep; /***/ }), -/* 593 */ +/* 595 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69217,7 +69310,7 @@ module.exports = mixinDeep; -var isPlainObject = __webpack_require__(544); +var isPlainObject = __webpack_require__(546); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -69225,7 +69318,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 594 */ +/* 596 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69248,7 +69341,7 @@ module.exports = function forIn(obj, fn, thisArg) { /***/ }), -/* 595 */ +/* 597 */ /***/ (function(module, exports) { /*! @@ -69275,14 +69368,14 @@ module.exports = pascalcase; /***/ }), -/* 596 */ +/* 598 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(112); -var utils = __webpack_require__(597); +var utils = __webpack_require__(599); /** * Expose class utils @@ -69647,7 +69740,7 @@ cu.bubble = function(Parent, events) { /***/ }), -/* 597 */ +/* 599 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69661,10 +69754,10 @@ var utils = {}; * Lazily required module dependencies */ -utils.union = __webpack_require__(580); -utils.define = __webpack_require__(598); -utils.isObj = __webpack_require__(535); -utils.staticExtend = __webpack_require__(605); +utils.union = __webpack_require__(582); +utils.define = __webpack_require__(600); +utils.isObj = __webpack_require__(537); +utils.staticExtend = __webpack_require__(607); /** @@ -69675,7 +69768,7 @@ module.exports = utils; /***/ }), -/* 598 */ +/* 600 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69688,7 +69781,7 @@ module.exports = utils; -var isDescriptor = __webpack_require__(599); +var isDescriptor = __webpack_require__(601); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -69713,7 +69806,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 599 */ +/* 601 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69726,9 +69819,9 @@ module.exports = function defineProperty(obj, prop, val) { -var typeOf = __webpack_require__(600); -var isAccessor = __webpack_require__(601); -var isData = __webpack_require__(603); +var typeOf = __webpack_require__(602); +var isAccessor = __webpack_require__(603); +var isData = __webpack_require__(605); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -69742,7 +69835,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 600 */ +/* 602 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -69895,7 +69988,7 @@ function isBuffer(val) { /***/ }), -/* 601 */ +/* 603 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69908,7 +70001,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(602); +var typeOf = __webpack_require__(604); // accessor descriptor properties var accessor = { @@ -69971,10 +70064,10 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 602 */ +/* 604 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(561); +var isBuffer = __webpack_require__(563); var toString = Object.prototype.toString; /** @@ -70093,7 +70186,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 603 */ +/* 605 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70106,7 +70199,7 @@ module.exports = function kindOf(val) { -var typeOf = __webpack_require__(604); +var typeOf = __webpack_require__(606); // data descriptor properties var data = { @@ -70155,10 +70248,10 @@ module.exports = isDataDescriptor; /***/ }), -/* 604 */ +/* 606 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(561); +var isBuffer = __webpack_require__(563); var toString = Object.prototype.toString; /** @@ -70277,7 +70370,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 605 */ +/* 607 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70290,8 +70383,8 @@ module.exports = function kindOf(val) { -var copy = __webpack_require__(606); -var define = __webpack_require__(598); +var copy = __webpack_require__(608); +var define = __webpack_require__(600); var util = __webpack_require__(112); /** @@ -70374,15 +70467,15 @@ module.exports = extend; /***/ }), -/* 606 */ +/* 608 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(560); -var copyDescriptor = __webpack_require__(607); -var define = __webpack_require__(598); +var typeOf = __webpack_require__(562); +var copyDescriptor = __webpack_require__(609); +var define = __webpack_require__(600); /** * Copy static properties, prototype properties, and descriptors from one object to another. @@ -70555,7 +70648,7 @@ module.exports.has = has; /***/ }), -/* 607 */ +/* 609 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70643,16 +70736,16 @@ function isObject(val) { /***/ }), -/* 608 */ +/* 610 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(609); -var define = __webpack_require__(598); -var debug = __webpack_require__(611)('snapdragon:compiler'); -var utils = __webpack_require__(617); +var use = __webpack_require__(611); +var define = __webpack_require__(600); +var debug = __webpack_require__(613)('snapdragon:compiler'); +var utils = __webpack_require__(619); /** * Create a new `Compiler` with the given `options`. @@ -70806,7 +70899,7 @@ Compiler.prototype = { // source map support if (opts.sourcemap) { - var sourcemaps = __webpack_require__(636); + var sourcemaps = __webpack_require__(638); sourcemaps(this); this.mapVisit(this.ast.nodes); this.applySourceMaps(); @@ -70827,7 +70920,7 @@ module.exports = Compiler; /***/ }), -/* 609 */ +/* 611 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70840,7 +70933,7 @@ module.exports = Compiler; -var utils = __webpack_require__(610); +var utils = __webpack_require__(612); module.exports = function base(app, opts) { if (!utils.isObject(app) && typeof app !== 'function') { @@ -70955,7 +71048,7 @@ module.exports = function base(app, opts) { /***/ }), -/* 610 */ +/* 612 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70969,8 +71062,8 @@ var utils = {}; * Lazily required module dependencies */ -utils.define = __webpack_require__(598); -utils.isObject = __webpack_require__(535); +utils.define = __webpack_require__(600); +utils.isObject = __webpack_require__(537); utils.isString = function(val) { @@ -70985,7 +71078,7 @@ module.exports = utils; /***/ }), -/* 611 */ +/* 613 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -70994,14 +71087,14 @@ module.exports = utils; */ if (typeof process !== 'undefined' && process.type === 'renderer') { - module.exports = __webpack_require__(612); + module.exports = __webpack_require__(614); } else { - module.exports = __webpack_require__(615); + module.exports = __webpack_require__(617); } /***/ }), -/* 612 */ +/* 614 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -71010,7 +71103,7 @@ if (typeof process !== 'undefined' && process.type === 'renderer') { * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(613); +exports = module.exports = __webpack_require__(615); exports.log = log; exports.formatArgs = formatArgs; exports.save = save; @@ -71192,7 +71285,7 @@ function localstorage() { /***/ }), -/* 613 */ +/* 615 */ /***/ (function(module, exports, __webpack_require__) { @@ -71208,7 +71301,7 @@ exports.coerce = coerce; exports.disable = disable; exports.enable = enable; exports.enabled = enabled; -exports.humanize = __webpack_require__(614); +exports.humanize = __webpack_require__(616); /** * The currently active debug mode names, and names to skip. @@ -71400,7 +71493,7 @@ function coerce(val) { /***/ }), -/* 614 */ +/* 616 */ /***/ (function(module, exports) { /** @@ -71558,7 +71651,7 @@ function plural(ms, n, name) { /***/ }), -/* 615 */ +/* 617 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -71574,7 +71667,7 @@ var util = __webpack_require__(112); * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(613); +exports = module.exports = __webpack_require__(615); exports.init = init; exports.log = log; exports.formatArgs = formatArgs; @@ -71753,7 +71846,7 @@ function createWritableStdioStream (fd) { case 'PIPE': case 'TCP': - var net = __webpack_require__(616); + var net = __webpack_require__(618); stream = new net.Socket({ fd: fd, readable: false, @@ -71812,13 +71905,13 @@ exports.enable(load()); /***/ }), -/* 616 */ +/* 618 */ /***/ (function(module, exports) { module.exports = require("net"); /***/ }), -/* 617 */ +/* 619 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -71828,9 +71921,9 @@ module.exports = require("net"); * Module dependencies */ -exports.extend = __webpack_require__(550); -exports.SourceMap = __webpack_require__(618); -exports.sourceMapResolve = __webpack_require__(629); +exports.extend = __webpack_require__(552); +exports.SourceMap = __webpack_require__(620); +exports.sourceMapResolve = __webpack_require__(631); /** * Convert backslash in the given string to forward slashes @@ -71873,7 +71966,7 @@ exports.last = function(arr, n) { /***/ }), -/* 618 */ +/* 620 */ /***/ (function(module, exports, __webpack_require__) { /* @@ -71881,13 +71974,13 @@ exports.last = function(arr, n) { * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ -exports.SourceMapGenerator = __webpack_require__(619).SourceMapGenerator; -exports.SourceMapConsumer = __webpack_require__(625).SourceMapConsumer; -exports.SourceNode = __webpack_require__(628).SourceNode; +exports.SourceMapGenerator = __webpack_require__(621).SourceMapGenerator; +exports.SourceMapConsumer = __webpack_require__(627).SourceMapConsumer; +exports.SourceNode = __webpack_require__(630).SourceNode; /***/ }), -/* 619 */ +/* 621 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -71897,10 +71990,10 @@ exports.SourceNode = __webpack_require__(628).SourceNode; * http://opensource.org/licenses/BSD-3-Clause */ -var base64VLQ = __webpack_require__(620); -var util = __webpack_require__(622); -var ArraySet = __webpack_require__(623).ArraySet; -var MappingList = __webpack_require__(624).MappingList; +var base64VLQ = __webpack_require__(622); +var util = __webpack_require__(624); +var ArraySet = __webpack_require__(625).ArraySet; +var MappingList = __webpack_require__(626).MappingList; /** * An instance of the SourceMapGenerator represents a source map which is @@ -72309,7 +72402,7 @@ exports.SourceMapGenerator = SourceMapGenerator; /***/ }), -/* 620 */ +/* 622 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -72349,7 +72442,7 @@ exports.SourceMapGenerator = SourceMapGenerator; * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -var base64 = __webpack_require__(621); +var base64 = __webpack_require__(623); // A single base 64 digit can contain 6 bits of data. For the base 64 variable // length quantities we use in the source map spec, the first bit is the sign, @@ -72455,7 +72548,7 @@ exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) { /***/ }), -/* 621 */ +/* 623 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -72528,7 +72621,7 @@ exports.decode = function (charCode) { /***/ }), -/* 622 */ +/* 624 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -72951,7 +73044,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate /***/ }), -/* 623 */ +/* 625 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -72961,7 +73054,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(622); +var util = __webpack_require__(624); var has = Object.prototype.hasOwnProperty; var hasNativeMap = typeof Map !== "undefined"; @@ -73078,7 +73171,7 @@ exports.ArraySet = ArraySet; /***/ }), -/* 624 */ +/* 626 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -73088,7 +73181,7 @@ exports.ArraySet = ArraySet; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(622); +var util = __webpack_require__(624); /** * Determine whether mappingB is after mappingA with respect to generated @@ -73163,7 +73256,7 @@ exports.MappingList = MappingList; /***/ }), -/* 625 */ +/* 627 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -73173,11 +73266,11 @@ exports.MappingList = MappingList; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(622); -var binarySearch = __webpack_require__(626); -var ArraySet = __webpack_require__(623).ArraySet; -var base64VLQ = __webpack_require__(620); -var quickSort = __webpack_require__(627).quickSort; +var util = __webpack_require__(624); +var binarySearch = __webpack_require__(628); +var ArraySet = __webpack_require__(625).ArraySet; +var base64VLQ = __webpack_require__(622); +var quickSort = __webpack_require__(629).quickSort; function SourceMapConsumer(aSourceMap) { var sourceMap = aSourceMap; @@ -74251,7 +74344,7 @@ exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer; /***/ }), -/* 626 */ +/* 628 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -74368,7 +74461,7 @@ exports.search = function search(aNeedle, aHaystack, aCompare, aBias) { /***/ }), -/* 627 */ +/* 629 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -74488,7 +74581,7 @@ exports.quickSort = function (ary, comparator) { /***/ }), -/* 628 */ +/* 630 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -74498,8 +74591,8 @@ exports.quickSort = function (ary, comparator) { * http://opensource.org/licenses/BSD-3-Clause */ -var SourceMapGenerator = __webpack_require__(619).SourceMapGenerator; -var util = __webpack_require__(622); +var SourceMapGenerator = __webpack_require__(621).SourceMapGenerator; +var util = __webpack_require__(624); // Matches a Windows-style `\r\n` newline or a `\n` newline used by all other // operating systems these days (capturing the result). @@ -74907,17 +75000,17 @@ exports.SourceNode = SourceNode; /***/ }), -/* 629 */ +/* 631 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014, 2015, 2016, 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var sourceMappingURL = __webpack_require__(630) -var resolveUrl = __webpack_require__(631) -var decodeUriComponent = __webpack_require__(632) -var urix = __webpack_require__(634) -var atob = __webpack_require__(635) +var sourceMappingURL = __webpack_require__(632) +var resolveUrl = __webpack_require__(633) +var decodeUriComponent = __webpack_require__(634) +var urix = __webpack_require__(636) +var atob = __webpack_require__(637) @@ -75215,7 +75308,7 @@ module.exports = { /***/ }), -/* 630 */ +/* 632 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;// Copyright 2014 Simon Lydell @@ -75278,7 +75371,7 @@ void (function(root, factory) { /***/ }), -/* 631 */ +/* 633 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -75296,13 +75389,13 @@ module.exports = resolveUrl /***/ }), -/* 632 */ +/* 634 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var decodeUriComponent = __webpack_require__(633) +var decodeUriComponent = __webpack_require__(635) function customDecodeUriComponent(string) { // `decodeUriComponent` turns `+` into ` `, but that's not wanted. @@ -75313,7 +75406,7 @@ module.exports = customDecodeUriComponent /***/ }), -/* 633 */ +/* 635 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75414,7 +75507,7 @@ module.exports = function (encodedURI) { /***/ }), -/* 634 */ +/* 636 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -75437,7 +75530,7 @@ module.exports = urix /***/ }), -/* 635 */ +/* 637 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75451,7 +75544,7 @@ module.exports = atob.atob = atob; /***/ }), -/* 636 */ +/* 638 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -75459,8 +75552,8 @@ module.exports = atob.atob = atob; var fs = __webpack_require__(134); var path = __webpack_require__(4); -var define = __webpack_require__(598); -var utils = __webpack_require__(617); +var define = __webpack_require__(600); +var utils = __webpack_require__(619); /** * Expose `mixin()`. @@ -75603,19 +75696,19 @@ exports.comment = function(node) { /***/ }), -/* 637 */ +/* 639 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(609); +var use = __webpack_require__(611); var util = __webpack_require__(112); -var Cache = __webpack_require__(638); -var define = __webpack_require__(598); -var debug = __webpack_require__(611)('snapdragon:parser'); -var Position = __webpack_require__(639); -var utils = __webpack_require__(617); +var Cache = __webpack_require__(640); +var define = __webpack_require__(600); +var debug = __webpack_require__(613)('snapdragon:parser'); +var Position = __webpack_require__(641); +var utils = __webpack_require__(619); /** * Create a new `Parser` with the given `input` and `options`. @@ -76143,7 +76236,7 @@ module.exports = Parser; /***/ }), -/* 638 */ +/* 640 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -76250,13 +76343,13 @@ MapCache.prototype.del = function mapDelete(key) { /***/ }), -/* 639 */ +/* 641 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var define = __webpack_require__(598); +var define = __webpack_require__(600); /** * Store position for a node @@ -76271,14 +76364,14 @@ module.exports = function Position(start, parser) { /***/ }), -/* 640 */ +/* 642 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(641); -var assignSymbols = __webpack_require__(545); +var isExtendable = __webpack_require__(643); +var assignSymbols = __webpack_require__(547); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -76338,7 +76431,7 @@ function isEnum(obj, key) { /***/ }), -/* 641 */ +/* 643 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -76351,7 +76444,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(544); +var isPlainObject = __webpack_require__(546); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -76359,14 +76452,14 @@ module.exports = function isExtendable(val) { /***/ }), -/* 642 */ +/* 644 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var nanomatch = __webpack_require__(643); -var extglob = __webpack_require__(658); +var nanomatch = __webpack_require__(645); +var extglob = __webpack_require__(660); module.exports = function(snapdragon) { var compilers = snapdragon.compiler.compilers; @@ -76443,7 +76536,7 @@ function escapeExtglobs(compiler) { /***/ }), -/* 643 */ +/* 645 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -76454,17 +76547,17 @@ function escapeExtglobs(compiler) { */ var util = __webpack_require__(112); -var toRegex = __webpack_require__(527); -var extend = __webpack_require__(644); +var toRegex = __webpack_require__(529); +var extend = __webpack_require__(646); /** * Local dependencies */ -var compilers = __webpack_require__(646); -var parsers = __webpack_require__(647); -var cache = __webpack_require__(650); -var utils = __webpack_require__(652); +var compilers = __webpack_require__(648); +var parsers = __webpack_require__(649); +var cache = __webpack_require__(652); +var utils = __webpack_require__(654); var MAX_LENGTH = 1024 * 64; /** @@ -77288,14 +77381,14 @@ module.exports = nanomatch; /***/ }), -/* 644 */ +/* 646 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(645); -var assignSymbols = __webpack_require__(545); +var isExtendable = __webpack_require__(647); +var assignSymbols = __webpack_require__(547); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -77355,7 +77448,7 @@ function isEnum(obj, key) { /***/ }), -/* 645 */ +/* 647 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77368,7 +77461,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(544); +var isPlainObject = __webpack_require__(546); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -77376,7 +77469,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 646 */ +/* 648 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -77722,15 +77815,15 @@ module.exports = function(nanomatch, options) { /***/ }), -/* 647 */ +/* 649 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var regexNot = __webpack_require__(546); -var toRegex = __webpack_require__(527); -var isOdd = __webpack_require__(648); +var regexNot = __webpack_require__(548); +var toRegex = __webpack_require__(529); +var isOdd = __webpack_require__(650); /** * Characters to use in negation regex (we want to "not" match @@ -78116,7 +78209,7 @@ module.exports.not = NOT_REGEX; /***/ }), -/* 648 */ +/* 650 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78129,7 +78222,7 @@ module.exports.not = NOT_REGEX; -var isNumber = __webpack_require__(649); +var isNumber = __webpack_require__(651); module.exports = function isOdd(i) { if (!isNumber(i)) { @@ -78143,7 +78236,7 @@ module.exports = function isOdd(i) { /***/ }), -/* 649 */ +/* 651 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78171,14 +78264,14 @@ module.exports = function isNumber(num) { /***/ }), -/* 650 */ +/* 652 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(651))(); +module.exports = new (__webpack_require__(653))(); /***/ }), -/* 651 */ +/* 653 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78191,7 +78284,7 @@ module.exports = new (__webpack_require__(651))(); -var MapCache = __webpack_require__(638); +var MapCache = __webpack_require__(640); /** * Create a new `FragmentCache` with an optional object to use for `caches`. @@ -78313,7 +78406,7 @@ exports = module.exports = FragmentCache; /***/ }), -/* 652 */ +/* 654 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78326,14 +78419,14 @@ var path = __webpack_require__(4); * Module dependencies */ -var isWindows = __webpack_require__(653)(); -var Snapdragon = __webpack_require__(570); -utils.define = __webpack_require__(654); -utils.diff = __webpack_require__(655); -utils.extend = __webpack_require__(644); -utils.pick = __webpack_require__(656); -utils.typeOf = __webpack_require__(657); -utils.unique = __webpack_require__(549); +var isWindows = __webpack_require__(655)(); +var Snapdragon = __webpack_require__(572); +utils.define = __webpack_require__(656); +utils.diff = __webpack_require__(657); +utils.extend = __webpack_require__(646); +utils.pick = __webpack_require__(658); +utils.typeOf = __webpack_require__(659); +utils.unique = __webpack_require__(551); /** * Returns true if the given value is effectively an empty string @@ -78699,7 +78792,7 @@ utils.unixify = function(options) { /***/ }), -/* 653 */ +/* 655 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*! @@ -78727,7 +78820,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /***/ }), -/* 654 */ +/* 656 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78740,8 +78833,8 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ -var isobject = __webpack_require__(535); -var isDescriptor = __webpack_require__(536); +var isobject = __webpack_require__(537); +var isDescriptor = __webpack_require__(538); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -78772,7 +78865,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 655 */ +/* 657 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78826,7 +78919,7 @@ function diffArray(one, two) { /***/ }), -/* 656 */ +/* 658 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -78839,7 +78932,7 @@ function diffArray(one, two) { -var isObject = __webpack_require__(535); +var isObject = __webpack_require__(537); module.exports = function pick(obj, keys) { if (!isObject(obj) && typeof obj !== 'function') { @@ -78868,7 +78961,7 @@ module.exports = function pick(obj, keys) { /***/ }), -/* 657 */ +/* 659 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -79003,7 +79096,7 @@ function isBuffer(val) { /***/ }), -/* 658 */ +/* 660 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79013,18 +79106,18 @@ function isBuffer(val) { * Module dependencies */ -var extend = __webpack_require__(550); -var unique = __webpack_require__(549); -var toRegex = __webpack_require__(527); +var extend = __webpack_require__(552); +var unique = __webpack_require__(551); +var toRegex = __webpack_require__(529); /** * Local dependencies */ -var compilers = __webpack_require__(659); -var parsers = __webpack_require__(665); -var Extglob = __webpack_require__(668); -var utils = __webpack_require__(667); +var compilers = __webpack_require__(661); +var parsers = __webpack_require__(667); +var Extglob = __webpack_require__(670); +var utils = __webpack_require__(669); var MAX_LENGTH = 1024 * 64; /** @@ -79341,13 +79434,13 @@ module.exports = extglob; /***/ }), -/* 659 */ +/* 661 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(660); +var brackets = __webpack_require__(662); /** * Extglob compilers @@ -79517,7 +79610,7 @@ module.exports = function(extglob) { /***/ }), -/* 660 */ +/* 662 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79527,17 +79620,17 @@ module.exports = function(extglob) { * Local dependencies */ -var compilers = __webpack_require__(661); -var parsers = __webpack_require__(663); +var compilers = __webpack_require__(663); +var parsers = __webpack_require__(665); /** * Module dependencies */ -var debug = __webpack_require__(611)('expand-brackets'); -var extend = __webpack_require__(550); -var Snapdragon = __webpack_require__(570); -var toRegex = __webpack_require__(527); +var debug = __webpack_require__(613)('expand-brackets'); +var extend = __webpack_require__(552); +var Snapdragon = __webpack_require__(572); +var toRegex = __webpack_require__(529); /** * Parses the given POSIX character class `pattern` and returns a @@ -79735,13 +79828,13 @@ module.exports = brackets; /***/ }), -/* 661 */ +/* 663 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var posix = __webpack_require__(662); +var posix = __webpack_require__(664); module.exports = function(brackets) { brackets.compiler @@ -79829,7 +79922,7 @@ module.exports = function(brackets) { /***/ }), -/* 662 */ +/* 664 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -79858,14 +79951,14 @@ module.exports = { /***/ }), -/* 663 */ +/* 665 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(664); -var define = __webpack_require__(598); +var utils = __webpack_require__(666); +var define = __webpack_require__(600); /** * Text regex @@ -80084,14 +80177,14 @@ module.exports.TEXT_REGEX = TEXT_REGEX; /***/ }), -/* 664 */ +/* 666 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var toRegex = __webpack_require__(527); -var regexNot = __webpack_require__(546); +var toRegex = __webpack_require__(529); +var regexNot = __webpack_require__(548); var cached; /** @@ -80125,15 +80218,15 @@ exports.createRegex = function(pattern, include) { /***/ }), -/* 665 */ +/* 667 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(660); -var define = __webpack_require__(666); -var utils = __webpack_require__(667); +var brackets = __webpack_require__(662); +var define = __webpack_require__(668); +var utils = __webpack_require__(669); /** * Characters to use in text regex (we want to "not" match @@ -80288,7 +80381,7 @@ module.exports = parsers; /***/ }), -/* 666 */ +/* 668 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80301,7 +80394,7 @@ module.exports = parsers; -var isDescriptor = __webpack_require__(536); +var isDescriptor = __webpack_require__(538); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -80326,14 +80419,14 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 667 */ +/* 669 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var regex = __webpack_require__(546); -var Cache = __webpack_require__(651); +var regex = __webpack_require__(548); +var Cache = __webpack_require__(653); /** * Utils @@ -80402,7 +80495,7 @@ utils.createRegex = function(str) { /***/ }), -/* 668 */ +/* 670 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80412,16 +80505,16 @@ utils.createRegex = function(str) { * Module dependencies */ -var Snapdragon = __webpack_require__(570); -var define = __webpack_require__(666); -var extend = __webpack_require__(550); +var Snapdragon = __webpack_require__(572); +var define = __webpack_require__(668); +var extend = __webpack_require__(552); /** * Local dependencies */ -var compilers = __webpack_require__(659); -var parsers = __webpack_require__(665); +var compilers = __webpack_require__(661); +var parsers = __webpack_require__(667); /** * Customize Snapdragon parser and renderer @@ -80487,16 +80580,16 @@ module.exports = Extglob; /***/ }), -/* 669 */ +/* 671 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extglob = __webpack_require__(658); -var nanomatch = __webpack_require__(643); -var regexNot = __webpack_require__(546); -var toRegex = __webpack_require__(527); +var extglob = __webpack_require__(660); +var nanomatch = __webpack_require__(645); +var regexNot = __webpack_require__(548); +var toRegex = __webpack_require__(529); var not; /** @@ -80577,14 +80670,14 @@ function textRegex(pattern) { /***/ }), -/* 670 */ +/* 672 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(651))(); +module.exports = new (__webpack_require__(653))(); /***/ }), -/* 671 */ +/* 673 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80597,13 +80690,13 @@ var path = __webpack_require__(4); * Module dependencies */ -var Snapdragon = __webpack_require__(570); -utils.define = __webpack_require__(672); -utils.diff = __webpack_require__(655); -utils.extend = __webpack_require__(640); -utils.pick = __webpack_require__(656); -utils.typeOf = __webpack_require__(673); -utils.unique = __webpack_require__(549); +var Snapdragon = __webpack_require__(572); +utils.define = __webpack_require__(674); +utils.diff = __webpack_require__(657); +utils.extend = __webpack_require__(642); +utils.pick = __webpack_require__(658); +utils.typeOf = __webpack_require__(675); +utils.unique = __webpack_require__(551); /** * Returns true if the platform is windows, or `path.sep` is `\\`. @@ -80900,7 +80993,7 @@ utils.unixify = function(options) { /***/ }), -/* 672 */ +/* 674 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -80913,8 +81006,8 @@ utils.unixify = function(options) { -var isobject = __webpack_require__(535); -var isDescriptor = __webpack_require__(536); +var isobject = __webpack_require__(537); +var isDescriptor = __webpack_require__(538); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -80945,7 +81038,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 673 */ +/* 675 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -81080,7 +81173,7 @@ function isBuffer(val) { /***/ }), -/* 674 */ +/* 676 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81099,9 +81192,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(675); -var reader_1 = __webpack_require__(688); -var fs_stream_1 = __webpack_require__(692); +var readdir = __webpack_require__(677); +var reader_1 = __webpack_require__(690); +var fs_stream_1 = __webpack_require__(694); var ReaderAsync = /** @class */ (function (_super) { __extends(ReaderAsync, _super); function ReaderAsync() { @@ -81162,15 +81255,15 @@ exports.default = ReaderAsync; /***/ }), -/* 675 */ +/* 677 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readdirSync = __webpack_require__(676); -const readdirAsync = __webpack_require__(684); -const readdirStream = __webpack_require__(687); +const readdirSync = __webpack_require__(678); +const readdirAsync = __webpack_require__(686); +const readdirStream = __webpack_require__(689); module.exports = exports = readdirAsyncPath; exports.readdir = exports.readdirAsync = exports.async = readdirAsyncPath; @@ -81254,7 +81347,7 @@ function readdirStreamStat (dir, options) { /***/ }), -/* 676 */ +/* 678 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81262,11 +81355,11 @@ function readdirStreamStat (dir, options) { module.exports = readdirSync; -const DirectoryReader = __webpack_require__(677); +const DirectoryReader = __webpack_require__(679); let syncFacade = { - fs: __webpack_require__(682), - forEach: __webpack_require__(683), + fs: __webpack_require__(684), + forEach: __webpack_require__(685), sync: true }; @@ -81295,7 +81388,7 @@ function readdirSync (dir, options, internalOptions) { /***/ }), -/* 677 */ +/* 679 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -81304,9 +81397,9 @@ function readdirSync (dir, options, internalOptions) { const Readable = __webpack_require__(138).Readable; const EventEmitter = __webpack_require__(156).EventEmitter; const path = __webpack_require__(4); -const normalizeOptions = __webpack_require__(678); -const stat = __webpack_require__(680); -const call = __webpack_require__(681); +const normalizeOptions = __webpack_require__(680); +const stat = __webpack_require__(682); +const call = __webpack_require__(683); /** * Asynchronously reads the contents of a directory and streams the results @@ -81682,14 +81775,14 @@ module.exports = DirectoryReader; /***/ }), -/* 678 */ +/* 680 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const globToRegExp = __webpack_require__(679); +const globToRegExp = __webpack_require__(681); module.exports = normalizeOptions; @@ -81866,7 +81959,7 @@ function normalizeOptions (options, internalOptions) { /***/ }), -/* 679 */ +/* 681 */ /***/ (function(module, exports) { module.exports = function (glob, opts) { @@ -82003,13 +82096,13 @@ module.exports = function (glob, opts) { /***/ }), -/* 680 */ +/* 682 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const call = __webpack_require__(681); +const call = __webpack_require__(683); module.exports = stat; @@ -82084,7 +82177,7 @@ function symlinkStat (fs, path, lstats, callback) { /***/ }), -/* 681 */ +/* 683 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82145,14 +82238,14 @@ function callOnce (fn) { /***/ }), -/* 682 */ +/* 684 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); -const call = __webpack_require__(681); +const call = __webpack_require__(683); /** * A facade around {@link fs.readdirSync} that allows it to be called @@ -82216,7 +82309,7 @@ exports.lstat = function (path, callback) { /***/ }), -/* 683 */ +/* 685 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82245,7 +82338,7 @@ function syncForEach (array, iterator, done) { /***/ }), -/* 684 */ +/* 686 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82253,12 +82346,12 @@ function syncForEach (array, iterator, done) { module.exports = readdirAsync; -const maybe = __webpack_require__(685); -const DirectoryReader = __webpack_require__(677); +const maybe = __webpack_require__(687); +const DirectoryReader = __webpack_require__(679); let asyncFacade = { fs: __webpack_require__(134), - forEach: __webpack_require__(686), + forEach: __webpack_require__(688), async: true }; @@ -82300,7 +82393,7 @@ function readdirAsync (dir, options, callback, internalOptions) { /***/ }), -/* 685 */ +/* 687 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82327,7 +82420,7 @@ module.exports = function maybe (cb, promise) { /***/ }), -/* 686 */ +/* 688 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82363,7 +82456,7 @@ function asyncForEach (array, iterator, done) { /***/ }), -/* 687 */ +/* 689 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82371,11 +82464,11 @@ function asyncForEach (array, iterator, done) { module.exports = readdirStream; -const DirectoryReader = __webpack_require__(677); +const DirectoryReader = __webpack_require__(679); let streamFacade = { fs: __webpack_require__(134), - forEach: __webpack_require__(686), + forEach: __webpack_require__(688), async: true }; @@ -82395,16 +82488,16 @@ function readdirStream (dir, options, internalOptions) { /***/ }), -/* 688 */ +/* 690 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(4); -var deep_1 = __webpack_require__(689); -var entry_1 = __webpack_require__(691); -var pathUtil = __webpack_require__(690); +var deep_1 = __webpack_require__(691); +var entry_1 = __webpack_require__(693); +var pathUtil = __webpack_require__(692); var Reader = /** @class */ (function () { function Reader(options) { this.options = options; @@ -82470,14 +82563,14 @@ exports.default = Reader; /***/ }), -/* 689 */ +/* 691 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(690); -var patternUtils = __webpack_require__(521); +var pathUtils = __webpack_require__(692); +var patternUtils = __webpack_require__(523); var DeepFilter = /** @class */ (function () { function DeepFilter(options, micromatchOptions) { this.options = options; @@ -82560,7 +82653,7 @@ exports.default = DeepFilter; /***/ }), -/* 690 */ +/* 692 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82591,14 +82684,14 @@ exports.makeAbsolute = makeAbsolute; /***/ }), -/* 691 */ +/* 693 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(690); -var patternUtils = __webpack_require__(521); +var pathUtils = __webpack_require__(692); +var patternUtils = __webpack_require__(523); var EntryFilter = /** @class */ (function () { function EntryFilter(options, micromatchOptions) { this.options = options; @@ -82683,7 +82776,7 @@ exports.default = EntryFilter; /***/ }), -/* 692 */ +/* 694 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82703,8 +82796,8 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(138); -var fsStat = __webpack_require__(693); -var fs_1 = __webpack_require__(697); +var fsStat = __webpack_require__(695); +var fs_1 = __webpack_require__(699); var FileSystemStream = /** @class */ (function (_super) { __extends(FileSystemStream, _super); function FileSystemStream() { @@ -82754,14 +82847,14 @@ exports.default = FileSystemStream; /***/ }), -/* 693 */ +/* 695 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const optionsManager = __webpack_require__(694); -const statProvider = __webpack_require__(696); +const optionsManager = __webpack_require__(696); +const statProvider = __webpack_require__(698); /** * Asynchronous API. */ @@ -82792,13 +82885,13 @@ exports.statSync = statSync; /***/ }), -/* 694 */ +/* 696 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsAdapter = __webpack_require__(695); +const fsAdapter = __webpack_require__(697); function prepare(opts) { const options = Object.assign({ fs: fsAdapter.getFileSystemAdapter(opts ? opts.fs : undefined), @@ -82811,7 +82904,7 @@ exports.prepare = prepare; /***/ }), -/* 695 */ +/* 697 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82834,7 +82927,7 @@ exports.getFileSystemAdapter = getFileSystemAdapter; /***/ }), -/* 696 */ +/* 698 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82886,7 +82979,7 @@ exports.isFollowedSymlink = isFollowedSymlink; /***/ }), -/* 697 */ +/* 699 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82917,7 +83010,7 @@ exports.default = FileSystem; /***/ }), -/* 698 */ +/* 700 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -82937,9 +83030,9 @@ var __extends = (this && this.__extends) || (function () { })(); Object.defineProperty(exports, "__esModule", { value: true }); var stream = __webpack_require__(138); -var readdir = __webpack_require__(675); -var reader_1 = __webpack_require__(688); -var fs_stream_1 = __webpack_require__(692); +var readdir = __webpack_require__(677); +var reader_1 = __webpack_require__(690); +var fs_stream_1 = __webpack_require__(694); var TransformStream = /** @class */ (function (_super) { __extends(TransformStream, _super); function TransformStream(reader) { @@ -83007,7 +83100,7 @@ exports.default = ReaderStream; /***/ }), -/* 699 */ +/* 701 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83026,9 +83119,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(675); -var reader_1 = __webpack_require__(688); -var fs_sync_1 = __webpack_require__(700); +var readdir = __webpack_require__(677); +var reader_1 = __webpack_require__(690); +var fs_sync_1 = __webpack_require__(702); var ReaderSync = /** @class */ (function (_super) { __extends(ReaderSync, _super); function ReaderSync() { @@ -83088,7 +83181,7 @@ exports.default = ReaderSync; /***/ }), -/* 700 */ +/* 702 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83107,8 +83200,8 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var fsStat = __webpack_require__(693); -var fs_1 = __webpack_require__(697); +var fsStat = __webpack_require__(695); +var fs_1 = __webpack_require__(699); var FileSystemSync = /** @class */ (function (_super) { __extends(FileSystemSync, _super); function FileSystemSync() { @@ -83154,7 +83247,7 @@ exports.default = FileSystemSync; /***/ }), -/* 701 */ +/* 703 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83170,7 +83263,7 @@ exports.flatten = flatten; /***/ }), -/* 702 */ +/* 704 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83191,13 +83284,13 @@ exports.merge = merge; /***/ }), -/* 703 */ +/* 705 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); -const pathType = __webpack_require__(704); +const pathType = __webpack_require__(706); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; @@ -83263,13 +83356,13 @@ module.exports.sync = (input, opts) => { /***/ }), -/* 704 */ +/* 706 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); -const pify = __webpack_require__(705); +const pify = __webpack_require__(707); function type(fn, fn2, fp) { if (typeof fp !== 'string') { @@ -83312,7 +83405,7 @@ exports.symlinkSync = typeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 705 */ +/* 707 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -83403,17 +83496,17 @@ module.exports = (obj, opts) => { /***/ }), -/* 706 */ +/* 708 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(134); const path = __webpack_require__(4); -const fastGlob = __webpack_require__(517); -const gitIgnore = __webpack_require__(707); -const pify = __webpack_require__(708); -const slash = __webpack_require__(709); +const fastGlob = __webpack_require__(519); +const gitIgnore = __webpack_require__(709); +const pify = __webpack_require__(710); +const slash = __webpack_require__(711); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -83511,7 +83604,7 @@ module.exports.sync = options => { /***/ }), -/* 707 */ +/* 709 */ /***/ (function(module, exports) { // A simple implementation of make-array @@ -83980,7 +84073,7 @@ module.exports = options => new IgnoreBase(options) /***/ }), -/* 708 */ +/* 710 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84055,7 +84148,7 @@ module.exports = (input, options) => { /***/ }), -/* 709 */ +/* 711 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84073,7 +84166,7 @@ module.exports = input => { /***/ }), -/* 710 */ +/* 712 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84086,7 +84179,7 @@ module.exports = input => { -var isGlob = __webpack_require__(711); +var isGlob = __webpack_require__(713); module.exports = function hasGlob(val) { if (val == null) return false; @@ -84106,7 +84199,7 @@ module.exports = function hasGlob(val) { /***/ }), -/* 711 */ +/* 713 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -84137,17 +84230,17 @@ module.exports = function isGlob(str) { /***/ }), -/* 712 */ +/* 714 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(4); const {constants: fsConstants} = __webpack_require__(134); -const pEvent = __webpack_require__(713); -const CpFileError = __webpack_require__(716); -const fs = __webpack_require__(718); -const ProgressEmitter = __webpack_require__(721); +const pEvent = __webpack_require__(715); +const CpFileError = __webpack_require__(718); +const fs = __webpack_require__(720); +const ProgressEmitter = __webpack_require__(723); const cpFileAsync = async (source, destination, options, progressEmitter) => { let readError; @@ -84261,12 +84354,12 @@ module.exports.sync = (source, destination, options) => { /***/ }), -/* 713 */ +/* 715 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pTimeout = __webpack_require__(714); +const pTimeout = __webpack_require__(716); const symbolAsyncIterator = Symbol.asyncIterator || '@@asyncIterator'; @@ -84557,12 +84650,12 @@ module.exports.iterator = (emitter, event, options) => { /***/ }), -/* 714 */ +/* 716 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pFinally = __webpack_require__(715); +const pFinally = __webpack_require__(717); class TimeoutError extends Error { constructor(message) { @@ -84608,7 +84701,7 @@ module.exports.TimeoutError = TimeoutError; /***/ }), -/* 715 */ +/* 717 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84630,12 +84723,12 @@ module.exports = (promise, onFinally) => { /***/ }), -/* 716 */ +/* 718 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(717); +const NestedError = __webpack_require__(719); class CpFileError extends NestedError { constructor(message, nested) { @@ -84649,7 +84742,7 @@ module.exports = CpFileError; /***/ }), -/* 717 */ +/* 719 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(112).inherits; @@ -84705,16 +84798,16 @@ module.exports = NestedError; /***/ }), -/* 718 */ +/* 720 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const {promisify} = __webpack_require__(112); const fs = __webpack_require__(133); -const makeDir = __webpack_require__(719); -const pEvent = __webpack_require__(713); -const CpFileError = __webpack_require__(716); +const makeDir = __webpack_require__(721); +const pEvent = __webpack_require__(715); +const CpFileError = __webpack_require__(718); const stat = promisify(fs.stat); const lstat = promisify(fs.lstat); @@ -84811,7 +84904,7 @@ exports.copyFileSync = (source, destination, flags) => { /***/ }), -/* 719 */ +/* 721 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -84819,7 +84912,7 @@ exports.copyFileSync = (source, destination, flags) => { const fs = __webpack_require__(134); const path = __webpack_require__(4); const {promisify} = __webpack_require__(112); -const semver = __webpack_require__(720); +const semver = __webpack_require__(722); const useNativeRecursiveOption = semver.satisfies(process.version, '>=10.12.0'); @@ -84974,7 +85067,7 @@ module.exports.sync = (input, options) => { /***/ }), -/* 720 */ +/* 722 */ /***/ (function(module, exports) { exports = module.exports = SemVer @@ -86576,7 +86669,7 @@ function coerce (version, options) { /***/ }), -/* 721 */ +/* 723 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86617,7 +86710,7 @@ module.exports = ProgressEmitter; /***/ }), -/* 722 */ +/* 724 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86663,12 +86756,12 @@ exports.default = module.exports; /***/ }), -/* 723 */ +/* 725 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const pMap = __webpack_require__(724); +const pMap = __webpack_require__(726); const pFilter = async (iterable, filterer, options) => { const values = await pMap( @@ -86685,7 +86778,7 @@ module.exports.default = pFilter; /***/ }), -/* 724 */ +/* 726 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -86764,12 +86857,12 @@ module.exports.default = pMap; /***/ }), -/* 725 */ +/* 727 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(717); +const NestedError = __webpack_require__(719); class CpyError extends NestedError { constructor(message, nested) { diff --git a/packages/kbn-pm/src/cli.ts b/packages/kbn-pm/src/cli.ts index a27587085eab1..e6be8d1821d01 100644 --- a/packages/kbn-pm/src/cli.ts +++ b/packages/kbn-pm/src/cli.ts @@ -41,6 +41,9 @@ function help() { --debug Set log level to debug --quiet Set log level to error --silent Disable log output + + "run" options: + --skip-missing Ignore packages which don't have the requested script ` + '\n' ); } @@ -49,7 +52,7 @@ export async function run(argv: string[]) { log.setLogLevel( pickLevelFromFlags( getopts(argv, { - boolean: ['verbose', 'debug', 'quiet', 'silent'], + boolean: ['verbose', 'debug', 'quiet', 'silent', 'skip-missing'], }) ) ); diff --git a/packages/kbn-pm/src/commands/bootstrap.ts b/packages/kbn-pm/src/commands/bootstrap.ts index 0ad420899870d..8cd346a56f278 100644 --- a/packages/kbn-pm/src/commands/bootstrap.ts +++ b/packages/kbn-pm/src/commands/bootstrap.ts @@ -17,12 +17,13 @@ import { getAllChecksums } from '../utils/project_checksums'; import { BootstrapCacheFile } from '../utils/bootstrap_cache_file'; import { readYarnLock } from '../utils/yarn_lock'; import { validateDependencies } from '../utils/validate_dependencies'; +import { installBazelTools } from '../utils/bazel'; export const BootstrapCommand: ICommand = { description: 'Install dependencies and crosslink projects', name: 'bootstrap', - async run(projects, projectGraph, { options, kbn }) { + async run(projects, projectGraph, { options, kbn, rootPath }) { const batchedProjects = topologicallyBatchProjects(projects, projectGraph); const kibanaProjectPath = projects.get('kibana')?.path; const extraArgs = [ @@ -30,6 +31,10 @@ export const BootstrapCommand: ICommand = { ...(options['prefer-offline'] === true ? ['--prefer-offline'] : []), ]; + // Install bazel machinery tools if needed + await installBazelTools(rootPath); + + // Install monorepo npm dependencies for (const batch of batchedProjects) { for (const project of batch) { const isExternalPlugin = project.path.includes(`${kibanaProjectPath}${sep}plugins`); diff --git a/packages/kbn-pm/src/commands/run.ts b/packages/kbn-pm/src/commands/run.ts index acbafe07b9a84..fb306f37082fe 100644 --- a/packages/kbn-pm/src/commands/run.ts +++ b/packages/kbn-pm/src/commands/run.ts @@ -16,7 +16,7 @@ export const RunCommand: ICommand = { description: 'Run script defined in package.json in each package that contains that script.', name: 'run', - async run(projects, projectGraph, { extraArgs }) { + async run(projects, projectGraph, { extraArgs, options }) { const batchedProjects = topologicallyBatchProjects(projects, projectGraph); if (extraArgs.length === 0) { @@ -27,13 +27,21 @@ export const RunCommand: ICommand = { const scriptArgs = extraArgs.slice(1); await parallelizeBatches(batchedProjects, async (project) => { - if (project.hasScript(scriptName)) { - log.info(`[${project.name}] running "${scriptName}" script`); - await project.runScriptStreaming(scriptName, { - args: scriptArgs, - }); - log.success(`[${project.name}] complete`); + if (!project.hasScript(scriptName)) { + if (!!options['skip-missing']) { + return; + } + + throw new CliError( + `[${project.name}] no "${scriptName}" script defined. To skip packages without the "${scriptName}" script pass --skip-missing` + ); } + + log.info(`[${project.name}] running "${scriptName}" script`); + await project.runScriptStreaming(scriptName, { + args: scriptArgs, + }); + log.success(`[${project.name}] complete`); }); }, }; diff --git a/src/plugins/maps_oss/common/index.ts b/packages/kbn-pm/src/utils/bazel/index.ts similarity index 91% rename from src/plugins/maps_oss/common/index.ts rename to packages/kbn-pm/src/utils/bazel/index.ts index 7f60b8508dc27..957c4bdf7f6aa 100644 --- a/src/plugins/maps_oss/common/index.ts +++ b/packages/kbn-pm/src/utils/bazel/index.ts @@ -6,4 +6,4 @@ * Public License, v 1. */ -export * from './constants'; +export * from './install_tools'; diff --git a/packages/kbn-pm/src/utils/bazel/install_tools.ts b/packages/kbn-pm/src/utils/bazel/install_tools.ts new file mode 100644 index 0000000000000..4e19974590e83 --- /dev/null +++ b/packages/kbn-pm/src/utils/bazel/install_tools.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { resolve } from 'path'; +import { spawn } from '../child_process'; +import { readFile } from '../fs'; +import { log } from '../log'; + +async function readBazelToolsVersionFile(repoRootPath: string, versionFilename: string) { + const version = (await readFile(resolve(repoRootPath, versionFilename))) + .toString() + .split('\n')[0]; + + if (!version) { + throw new Error( + `[bazel_tools] Failed on reading bazel tools versions\n ${versionFilename} file do not contain any version set` + ); + } + + return version; +} + +export async function installBazelTools(repoRootPath: string) { + log.debug(`[bazel_tools] reading bazel tools versions from version files`); + const bazeliskVersion = await readBazelToolsVersionFile(repoRootPath, '.bazeliskversion'); + const bazelVersion = await readBazelToolsVersionFile(repoRootPath, '.bazelversion'); + + // Check what globals are installed + log.debug(`[bazel_tools] verify if bazelisk is installed`); + const { stdout } = await spawn('yarn', ['global', 'list'], { stdio: 'pipe' }); + + // Install bazelisk if not installed + if (!stdout.includes(`@bazel/bazelisk@${bazeliskVersion}`)) { + log.info(`[bazel_tools] installing Bazel tools`); + + log.debug( + `[bazel_tools] bazelisk is not installed. Installing @bazel/bazelisk@${bazeliskVersion} and bazel@${bazelVersion}` + ); + await spawn('yarn', ['global', 'add', `@bazel/bazelisk@${bazeliskVersion}`], { + env: { + USE_BAZEL_VERSION: bazelVersion, + }, + stdio: 'pipe', + }); + } + + log.success(`[bazel_tools] all bazel tools are correctly installed`); +} diff --git a/packages/kbn-telemetry-tools/src/tools/serializer.ts b/packages/kbn-telemetry-tools/src/tools/serializer.ts index a941fa2a9d01f..bad40b5388407 100644 --- a/packages/kbn-telemetry-tools/src/tools/serializer.ts +++ b/packages/kbn-telemetry-tools/src/tools/serializer.ts @@ -19,7 +19,7 @@ export enum TelemetryKinds { Date = 10001, } -interface DescriptorValue { +export interface DescriptorValue { kind: ts.SyntaxKind | TelemetryKinds; type: keyof typeof ts.SyntaxKind | keyof typeof TelemetryKinds; } @@ -42,6 +42,13 @@ export function isObjectDescriptor(value: any) { return false; } +export function descriptorToObject(descriptor: Descriptor | DescriptorValue) { + return Object.entries(descriptor).reduce((acc, [key, value]) => { + acc[key] = value.kind ? kindToDescriptorName(value.kind) : descriptorToObject(value); + return acc; + }, {} as Record); +} + export function kindToDescriptorName(kind: number) { switch (kind) { case ts.SyntaxKind.StringKeyword: @@ -158,6 +165,16 @@ export function getDescriptor(node: ts.Node, program: ts.Program): Descriptor | if (symbolName === 'Date') { return { kind: TelemetryKinds.Date, type: 'Date' }; } + + // Support Array + if (symbolName === 'Array') { + if (node.typeArguments?.length !== 1) { + throw Error('Array type only supports 1 type parameter Array'); + } + const typeArgument = node.typeArguments[0]; + return { items: getDescriptor(typeArgument, program) }; + } + // Support `Record` if (symbolName === 'Record') { const descriptor = getDescriptor(node.typeArguments![1], program); @@ -243,7 +260,7 @@ export function getDescriptor(node: ts.Node, program: ts.Program): Descriptor | case ts.SyntaxKind.UnionType: case ts.SyntaxKind.AnyKeyword: default: - throw new Error(`Unknown type ${ts.SyntaxKind[node.kind]}; ${node.getText()}`); + throw new Error(`Unknown type ${ts.SyntaxKind[node.kind]}`); } } diff --git a/packages/kbn-test/src/functional_test_runner/fake_mocha_types.d.ts b/packages/kbn-test/src/functional_test_runner/fake_mocha_types.d.ts index 6efc8774b0139..e78cbbb3aed02 100644 --- a/packages/kbn-test/src/functional_test_runner/fake_mocha_types.d.ts +++ b/packages/kbn-test/src/functional_test_runner/fake_mocha_types.d.ts @@ -20,6 +20,8 @@ export interface Suite { title: string; file?: string; parent?: Suite; + eachTest: (cb: (test: Test) => void) => void; + root: boolean; } export interface Test { diff --git a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.test.ts b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.test.ts index 2a238cdeb5385..a8b0252e6f51c 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.test.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.test.ts @@ -6,21 +6,44 @@ * Public License, v 1. */ -import { Test } from '../../fake_mocha_types'; +import { Suite, Test } from '../../fake_mocha_types'; import { Lifecycle } from '../lifecycle'; import { decorateSnapshotUi, expectSnapshot } from './decorate_snapshot_ui'; import path from 'path'; import fs from 'fs'; +const createMockSuite = ({ tests, root = true }: { tests: Test[]; root?: boolean }) => { + const suite = { + tests, + root, + eachTest: (cb: (test: Test) => void) => { + suite.tests.forEach((test) => cb(test)); + }, + } as Suite; + + return suite; +}; + const createMockTest = ({ title = 'Test', passed = true, -}: { title?: string; passed?: boolean } = {}) => { - return { + filename = __filename, + parent, +}: { title?: string; passed?: boolean; filename?: string; parent?: Suite } = {}) => { + const test = ({ + file: filename, fullTitle: () => title, isPassed: () => passed, - parent: {}, - } as Test; + } as unknown) as Test; + + if (parent) { + parent.tests.push(test); + test.parent = parent; + } else { + test.parent = createMockSuite({ tests: [test] }); + } + + return test; }; describe('decorateSnapshotUi', () => { @@ -211,7 +234,7 @@ exports[\`Test2 1\`] = \`"bar"\`; expectSnapshot('bar').toMatch(); }).not.toThrow(); - const afterTestSuite = lifecycle.afterTestSuite.trigger({}); + const afterTestSuite = lifecycle.afterTestSuite.trigger(test.parent); await expect(afterTestSuite).resolves.toBe(undefined); }); @@ -225,7 +248,7 @@ exports[\`Test2 1\`] = \`"bar"\`; expectSnapshot('foo').toMatch(); }).not.toThrow(); - const afterTestSuite = lifecycle.afterTestSuite.trigger({}); + const afterTestSuite = lifecycle.afterTestSuite.trigger(test.parent); await expect(afterTestSuite).rejects.toMatchInlineSnapshot(` [Error: 1 obsolete snapshot(s) found: @@ -234,6 +257,32 @@ exports[\`Test2 1\`] = \`"bar"\`; Run tests again with \`--updateSnapshots\` to remove them.] `); }); + + it('does not throw on unused when some tests are skipped', async () => { + const root = createMockSuite({ tests: [] }); + + const test = createMockTest({ + title: 'Test', + parent: root, + passed: true, + }); + + createMockTest({ + title: 'Test2', + parent: root, + passed: false, + }); + + await lifecycle.beforeEachTest.trigger(test); + + expect(() => { + expectSnapshot('foo').toMatch(); + }).not.toThrow(); + + const afterTestSuite = lifecycle.afterTestSuite.trigger(root); + + await expect(afterTestSuite).resolves.toBeUndefined(); + }); }); }); }); diff --git a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts index 2111f1a6e5e90..4a52299ecf6d1 100644 --- a/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts +++ b/packages/kbn-test/src/functional_test_runner/lib/snapshots/decorate_snapshot_ui.ts @@ -16,9 +16,8 @@ import path from 'path'; import prettier from 'prettier'; import babelTraverse from '@babel/traverse'; import { once } from 'lodash'; -import callsites from 'callsites'; import { Lifecycle } from '../lifecycle'; -import { Test } from '../../fake_mocha_types'; +import { Suite, Test } from '../../fake_mocha_types'; type ISnapshotState = InstanceType; @@ -33,12 +32,12 @@ const globalState: { updateSnapshot: SnapshotUpdateState; registered: boolean; currentTest: Test | null; - snapshots: Array<{ tests: Test[]; file: string; snapshotState: ISnapshotState }>; + snapshotStates: Record; } = { updateSnapshot: 'none', registered: false, currentTest: null, - snapshots: [], + snapshotStates: {}, }; const modifyStackTracePrepareOnce = once(() => { @@ -73,7 +72,7 @@ export function decorateSnapshotUi({ isCi: boolean; }) { globalState.registered = true; - globalState.snapshots.length = 0; + globalState.snapshotStates = {}; globalState.currentTest = null; if (isCi) { @@ -102,32 +101,36 @@ export function decorateSnapshotUi({ globalState.currentTest = test; }); - lifecycle.afterTestSuite.add(function (testSuite) { + lifecycle.afterTestSuite.add(function (testSuite: Suite) { // save snapshot & check unused after top-level test suite completes - if (testSuite.parent?.parent) { + if (!testSuite.root) { return; } - const unused: string[] = []; + testSuite.eachTest((test) => { + const file = test.file; - globalState.snapshots.forEach((snapshot) => { - const { tests, snapshotState } = snapshot; - tests.forEach((test) => { - const title = test.fullTitle(); - // If test is failed or skipped, mark snapshots as used. Otherwise, - // running a test in isolation will generate false positives. - if (!test.isPassed()) { - snapshotState.markSnapshotsAsCheckedForTest(title); - } - }); + if (!file) { + return; + } + + const snapshotState = globalState.snapshotStates[file]; + + if (snapshotState && !test.isPassed()) { + snapshotState.markSnapshotsAsCheckedForTest(test.fullTitle()); + } + }); + + const unused: string[] = []; - if (globalState.updateSnapshot !== 'all') { - unused.push(...snapshotState.getUncheckedKeys()); - } else { - snapshotState.removeUncheckedKeys(); + Object.values(globalState.snapshotStates).forEach((state) => { + if (globalState.updateSnapshot === 'all') { + state.removeUncheckedKeys(); } - snapshotState.save(); + unused.push(...state.getUncheckedKeys()); + + state.save(); }); if (unused.length) { @@ -138,7 +141,7 @@ export function decorateSnapshotUi({ ); } - globalState.snapshots.length = 0; + globalState.snapshotStates = {}; }); } @@ -161,43 +164,29 @@ function getSnapshotState(file: string, updateSnapshot: SnapshotUpdateState) { export function expectSnapshot(received: any) { if (!globalState.registered) { - throw new Error( - 'Mocha hooks were not registered before expectSnapshot was used. Call `registerMochaHooksForSnapshots` in your top-level describe().' - ); - } - - if (!globalState.currentTest) { - throw new Error('expectSnapshot can only be called inside of an it()'); + throw new Error('expectSnapshot UI was not initialized before calling expectSnapshot()'); } - const [, fileOfTest] = callsites().map((site) => site.getFileName()); + const test = globalState.currentTest; - if (!fileOfTest) { - throw new Error("Couldn't infer a filename for the current test"); + if (!test) { + throw new Error('expectSnapshot can only be called inside of an it()'); } - let snapshot = globalState.snapshots.find(({ file }) => file === fileOfTest); - - if (!snapshot) { - snapshot = { - file: fileOfTest, - tests: [], - snapshotState: getSnapshotState(fileOfTest, globalState.updateSnapshot), - }; - globalState.snapshots.unshift(snapshot!); + if (!test.file) { + throw new Error('File for test not found'); } - if (!snapshot) { - throw new Error('Snapshot is undefined'); - } + let snapshotState = globalState.snapshotStates[test.file]; - if (!snapshot.tests.includes(globalState.currentTest)) { - snapshot.tests.push(globalState.currentTest); + if (!snapshotState) { + snapshotState = getSnapshotState(test.file, globalState.updateSnapshot); + globalState.snapshotStates[test.file] = snapshotState; } const context: SnapshotContext = { - snapshotState: snapshot.snapshotState, - currentTestName: globalState.currentTest.fullTitle(), + snapshotState, + currentTestName: test.fullTitle(), }; return { diff --git a/packages/kbn-tinymath/README.md b/packages/kbn-tinymath/README.md new file mode 100644 index 0000000000000..1094c4286c851 --- /dev/null +++ b/packages/kbn-tinymath/README.md @@ -0,0 +1,72 @@ +# kbn-tinymath + +kbn-tinymath is a tiny arithmetic and function evaluator for simple numbers and arrays. Named properties can be accessed from an optional scope parameter. +It's available as an expression function called `math` in Canvas, and the grammar/AST structure is available +for use by Kibana plugins that want to use math. + +See [Function Documentation](/docs/functions.md) for details on built-in functions available in Tinymath. + +```javascript +const { evaluate } = require('@kbn/tinymath'); + +// Simple math +evaluate('10 + 20'); // 30 +evaluate('round(3.141592)') // 3 + +// Named properties +evaluate('foo + 20', {foo: 5}); // 25 + +// Arrays +evaluate('bar + 20', {bar: [1, 2, 3]}); // [21, 22, 23] +evaluate('bar + baz', {bar: [1, 2, 3], baz: [4, 5, 6]}); // [5, 7, 9] +evaluate('multiply(bar, baz) / 10', {bar: [1, 2, 3], baz: [4, 5, 6]}); // [0.4, 1, 1.8] +``` + +### Adding Functions + +Functions can be injected, and built in function overwritten, via the 3rd argument to `evaluate`: + +```javascript +const { evaluate } = require('@kbn/tinymath'); + +evaluate('plustwo(foo)', {foo: 5}, { + plustwo: function(a) { + return a + 2; + } +}); // 7 +``` + +### Parsing + +You can get to the parsed AST by importing `parse` + +```javascript +const { parse } = require('@kbn/tinymath'); + +parse('1 + random()') +/* +{ + "name": "add", + "args": [ + 1, + { + "name": "random", + "args": [] + } + ] +} +*/ +``` + +#### Notes + +* Floating point operations have the normal Javascript limitations + +### Building kbn-tinymath + +This package is rebuilt when running `yarn kbn bootstrap`, but can also be build directly +using `yarn build` from the `packages/kbn-tinymath` directory. +### Running tests + +To test `@kbn/tinymath` from Kibana, run `yarn run jest --watch packages/kbn-tinymath` from +the top level of Kibana. diff --git a/packages/kbn-tinymath/babel.config.js b/packages/kbn-tinymath/babel.config.js new file mode 100644 index 0000000000000..c578a02ede1fb --- /dev/null +++ b/packages/kbn-tinymath/babel.config.js @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +module.exports = { + env: { + web: { + presets: ['@kbn/babel-preset/webpack_preset'], + }, + node: { + presets: ['@kbn/babel-preset/node_preset'], + }, + }, + ignore: ['**/*.test.ts', '**/*.test.tsx'], +}; diff --git a/packages/kbn-tinymath/docs/functions.md b/packages/kbn-tinymath/docs/functions.md new file mode 100644 index 0000000000000..0c7460a8189dd --- /dev/null +++ b/packages/kbn-tinymath/docs/functions.md @@ -0,0 +1,687 @@ +# TinyMath Functions +This document provides detailed information about the functions available in Tinymath and lists what parameters each function accepts, the return value of that function, and examples of how each function behaves. Most of the functions below accept arrays and apply JavaScript Math methods to each element of that array. For the functions that accept multiple arrays as parameters, the function generally does calculation index by index. Any function below can be wrapped by another function as long as the return type of the inner function matches the acceptable parameter type of the outer function. + +## _abs(_ _a_ _)_ +Calculates the absolute value of a number. For arrays, the function will be applied index-wise to each element. + + +| Param | Type | Description | +| --- | --- | --- | +| a | number \| Array.<number> | a number or an array of numbers | + +**Returns**: number \| Array.<number> - The absolute value of `a`. Returns an array with the the absolute values of each element if `a` is an array. +**Example** +```js +abs(-1) // returns 1 +abs(2) // returns 2 +abs([-1 , -2, 3, -4]) // returns [1, 2, 3, 4] +``` +*** +## _add(_ ..._args_ _)_ +Calculates the sum of one or more numbers/arrays passed into the function. If at least one array of numbers is passed into the function, the function will calculate the sum by index. + + +| Param | Type | Description | +| --- | --- | --- | +| ...args | number \| Array.<number> | one or more numbers or arrays of numbers | + +**Returns**: number \| Array.<number> - The sum of all numbers in `args` if `args` contains only numbers. Returns an array of sums of the elements at each index, including all scalar numbers in `args` in the calculation at each index if `args` contains at least one array. +**Throws**: + +- `'Array length mismatch'` if `args` contains arrays of different lengths + +**Example** +```js +add(1, 2, 3) // returns 6 +add([10, 20, 30, 40], 10, 20, 30) // returns [70, 80, 90, 100] +add([1, 2], 3, [4, 5], 6) // returns [(1 + 3 + 4 + 6), (2 + 3 + 5 + 6)] = [14, 16] +``` +*** +## _cbrt(_ _a_ _)_ +Calculates the cube root of a number. For arrays, the function will be applied index-wise to each element. + + +| Param | Type | Description | +| --- | --- | --- | +| a | number \| Array.<number> | a number or an array of numbers | + +**Returns**: number \| Array.<number> - The cube root of `a`. Returns an array with the the cube roots of each element if `a` is an array. +**Example** +```js +cbrt(-27) // returns -3 +cbrt(94) // returns 4.546835943776344 +cbrt([27, 64, 125]) // returns [3, 4, 5] +``` +*** +## _ceil(_ _a_ _)_ +Calculates the ceiling of a number, i.e. rounds a number towards positive infinity. For arrays, the function will be applied index-wise to each element. + + +| Param | Type | Description | +| --- | --- | --- | +| a | number \| Array.<number> | a number or an array of numbers | + +**Returns**: number \| Array.<number> - The ceiling of `a`. Returns an array with the the ceilings of each element if `a` is an array. +**Example** +```js +ceil(1.2) // returns 2 +ceil(-1.8) // returns -1 +ceil([1.1, 2.2, 3.3]) // returns [2, 3, 4] +``` +*** +## _clamp(_ ..._a_, _min_, _max_ _)_ +Restricts value to a given range and returns closed available value. If only min is provided, values are restricted to only a lower bound. + + +| Param | Type | Description | +| --- | --- | --- | +| ...a | number \| Array.<number> | one or more numbers or arrays of numbers | +| min | number \| Array.<number> | The minimum value this function will return. | +| max | number \| Array.<number> | The maximum value this function will return. | + +**Returns**: number \| Array.<number> - The closest value between `min` (inclusive) and `max` (inclusive). Returns an array with values greater than or equal to `min` and less than or equal to `max` (if provided) at each index. +**Throws**: + +- `'Array length mismatch'` if `a`, `min`, and/or `max` are arrays of different lengths +- `'Min must be less than max'` if `max` is less than `min` +- `'Missing minimum value. You may want to use the 'max' function instead'` if min is not provided +- `'Missing maximum value. You may want to use the 'min' function instead'` if max is not provided + +**Example** +```js +clamp(1, 2, 3) // returns 2 +clamp([10, 20, 30, 40], 15, 25) // returns [15, 20, 25, 25] +clamp(10, [15, 2, 4, 20], 25) // returns [15, 10, 10, 20] +clamp(35, 10, [20, 30, 40, 50]) // returns [20, 30, 35, 35] +clamp([1, 9], 3, [4, 5]) // returns [clamp([1, 3, 4]), clamp([9, 3, 5])] = [3, 5] +``` +*** +## _cos(_ _a_ _)_ +Calculates the the cosine of a number. For arrays, the function will be applied index-wise to each element. + + +| Param | Type | Description | +| --- | --- | --- | +| a | number \| Array.<number> | a number or an array of numbers, `a` is expected to be given in radians. | + +**Returns**: number \| Array.<number> - The cosine of `a`. Returns an array with the the cosine of each element if `a` is an array. +**Example** +```js +cos(0) // returns 1 +cos(1.5707963267948966) // returns 6.123233995736766e-17 +cos([0, 1.5707963267948966]) // returns [1, 6.123233995736766e-17] +``` +*** +## _count(_ _a_ _)_ +Returns the length of an array. Alias for size + + +| Param | Type | Description | +| --- | --- | --- | +| a | Array.<any> | array of any values | + +**Returns**: number - The length of the array. Returns 1 if `a` is not an array. +**Throws**: + +- `'Must pass an array'` if `a` is not an array + +**Example** +```js +count([]) // returns 0 +count([-1, -2, -3, -4]) // returns 4 +count(100) // returns 1 +``` +*** +## _cube(_ _a_ _)_ +Calculates the cube of a number. For arrays, the function will be applied index-wise to each element. + + +| Param | Type | Description | +| --- | --- | --- | +| a | number \| Array.<number> | a number or an array of numbers | + +**Returns**: number \| Array.<number> - The cube of `a`. Returns an array with the the cubes of each element if `a` is an array. +**Example** +```js +cube(-3) // returns -27 +cube([3, 4, 5]) // returns [27, 64, 125] +``` +*** +## _degtorad(_ _a_ _)_ +Converts degrees to radians for a number. For arrays, the function will be applied index-wise to each element. + + +| Param | Type | Description | +| --- | --- | --- | +| a | number \| Array.<number> | a number or an array of numbers, `a` is expected to be given in degrees. | + +**Returns**: number \| Array.<number> - The radians of `a`. Returns an array with the the radians of each element if `a` is an array. +**Example** +```js +degtorad(0) // returns 0 +degtorad(90) // returns 1.5707963267948966 +degtorad([0, 90, 180, 360]) // returns [0, 1.5707963267948966, 3.141592653589793, 6.283185307179586] +``` +*** +## _divide(_ _a_, _b_ _)_ +Divides two numbers. If at least one array of numbers is passed into the function, the function will be applied index-wise to each element. + + +| Param | Type | Description | +| --- | --- | --- | +| a | number \| Array.<number> | dividend, a number or an array of numbers | +| b | number \| Array.<number> | divisor, a number or an array of numbers, `b` != 0 | + +**Returns**: number \| Array.<number> - The quotient of `a` and `b` if both are numbers. Returns an array with the quotients applied index-wise to each element if `a` or `b` is an array. +**Throws**: + +- `'Array length mismatch'` if `a` and `b` are arrays with different lengths +- `'Cannot divide by 0'` if `b` equals 0 or contains 0 + +**Example** +```js +divide(6, 3) // returns 2 +divide([10, 20, 30, 40], 10) // returns [1, 2, 3, 4] +divide(10, [1, 2, 5, 10]) // returns [10, 5, 2, 1] +divide([14, 42, 65, 108], [2, 7, 5, 12]) // returns [7, 6, 13, 9] +``` +*** +## _exp(_ _a_ _)_ +Calculates _e^x_ where _e_ is Euler's number. For arrays, the function will be applied index-wise to each element. + + +| Param | Type | Description | +| --- | --- | --- | +| a | number \| Array.<number> | a number or an array of numbers | + +**Returns**: number \| Array.<number> - `e^a`. Returns an array with the values of `e^x` evaluated where `x` is each element of `a` if `a` is an array. +**Example** +```js +exp(2) // returns e^2 = 7.3890560989306495 +exp([1, 2, 3]) // returns [e^1, e^2, e^3] = [2.718281828459045, 7.3890560989306495, 20.085536923187668] +``` +*** +## _first(_ _a_ _)_ +Returns the first element of an array. If anything other than an array is passed in, the input is returned. + + +| Param | Type | Description | +| --- | --- | --- | +| a | Array.<any> | array of any values | + +**Returns**: \* - The first element of `a`. Returns `a` if `a` is not an array. +**Example** +```js +first(2) // returns 2 +first([1, 2, 3]) // returns 1 +``` +*** +## _fix(_ _a_ _)_ +Calculates the fix of a number, i.e. rounds a number towards 0. For arrays, the function will be applied index-wise to each element. + + +| Param | Type | Description | +| --- | --- | --- | +| a | number \| Array.<number> | a number or an array of numbers | + +**Returns**: number \| Array.<number> - The fix of `a`. Returns an array with the the fixes for each element if `a` is an array. +**Example** +```js +fix(1.2) // returns 1 +fix(-1.8) // returns -1 +fix([1.8, 2.9, -3.7, -4.6]) // returns [1, 2, -3, -4] +``` +*** +## _floor(_ _a_ _)_ +Calculates the floor of a number, i.e. rounds a number towards negative infinity. For arrays, the function will be applied index-wise to each element. + + +| Param | Type | Description | +| --- | --- | --- | +| a | number \| Array.<number> | a number or an array of numbers | + +**Returns**: number \| Array.<number> - The floor of `a`. Returns an array with the the floor of each element if `a` is an array. +**Example** +```js +floor(1.8) // returns 1 +floor(-1.2) // returns -2 +floor([1.7, 2.8, 3.9]) // returns [1, 2, 3] +``` +*** +## _last(_ _a_ _)_ +Returns the last element of an array. If anything other than an array is passed in, the input is returned. + + +| Param | Type | Description | +| --- | --- | --- | +| a | Array.<any> | array of any values | + +**Returns**: \* - The last element of `a`. Returns `a` if `a` is not an array. +**Example** +```js +last(2) // returns 2 +last([1, 2, 3]) // returns 3 +``` +*** +## _log(_ _a_, _b_ _)_ +Calculates the logarithm of a number. For arrays, the function will be applied index-wise to each element. + + +| Param | Type | Description | +| --- | --- | --- | +| a | number \| Array.<number> | a number or an array of numbers, `a` must be greater than 0 | +| b | Object | (optional) base for the logarithm. If not provided a value, the default base is e, and the natural log is calculated. | + +**Returns**: number \| Array.<number> - The logarithm of `a`. Returns an array with the the logarithms of each element if `a` is an array. +**Throws**: + +- `'Base out of range'` if `b` <= 0 +- 'Must be greater than 0' if `a` > 0 + +**Example** +```js +log(1) // returns 0 +log(64, 8) // returns 2 +log(42, 5) // returns 2.322344707681546 +log([2, 4, 8, 16, 32], 2) // returns [1, 2, 3, 4, 5] +``` +*** +## _log10(_ _a_ _)_ +Calculates the logarithm base 10 of a number. For arrays, the function will be applied index-wise to each element. + + +| Param | Type | Description | +| --- | --- | --- | +| a | number \| Array.<number> | a number or an array of numbers, `a` must be greater than 0 | + +**Returns**: number \| Array.<number> - The logarithm of `a`. Returns an array with the the logarithms base 10 of each element if `a` is an array. +**Throws**: + +- `'Must be greater than 0'` if `a` < 0 + +**Example** +```js +log(10) // returns 1 +log(100) // returns 2 +log(80) // returns 1.9030899869919433 +log([10, 100, 1000, 10000, 100000]) // returns [1, 2, 3, 4, 5] +``` +*** +## _max(_ ..._args_ _)_ +Finds the maximum value of one of more numbers/arrays of numbers into the function. If at least one array of numbers is passed into the function, the function will find the maximum by index. + + +| Param | Type | Description | +| --- | --- | --- | +| ...args | number \| Array.<number> | one or more numbers or arrays of numbers | + +**Returns**: number \| Array.<number> - The maximum value of all numbers if `args` contains only numbers. Returns an array with the the maximum values at each index, including all scalar numbers in `args` in the calculation at each index if `args` contains at least one array. +**Throws**: + +- `'Array length mismatch'` if `args` contains arrays of different lengths + +**Example** +```js +max(1, 2, 3) // returns 3 +max([10, 20, 30, 40], 15) // returns [15, 20, 30, 40] +max([1, 9], 4, [3, 5]) // returns [max([1, 4, 3]), max([9, 4, 5])] = [4, 9] +``` +*** +## _mean(_ ..._args_ _)_ +Finds the mean value of one of more numbers/arrays of numbers into the function. If at least one array of numbers is passed into the function, the function will find the mean by index. + + +| Param | Type | Description | +| --- | --- | --- | +| ...args | number \| Array.<number> | one or more numbers or arrays of numbers | + +**Returns**: number \| Array.<number> - The mean value of all numbers if `args` contains only numbers. Returns an array with the the mean values of each index, including all scalar numbers in `args` in the calculation at each index if `args` contains at least one array. +**Example** +```js +mean(1, 2, 3) // returns 2 +mean([10, 20, 30, 40], 20) // returns [15, 20, 25, 30] +mean([1, 9], 5, [3, 4]) // returns [mean([1, 5, 3]), mean([9, 5, 4])] = [3, 6] +``` +*** +## _median(_ ..._args_ _)_ +Finds the median value(s) of one of more numbers/arrays of numbers into the function. If at least one array of numbers is passed into the function, the function will find the median by index. + + +| Param | Type | Description | +| --- | --- | --- | +| ...args | number \| Array.<number> | one or more numbers or arrays of numbers | + +**Returns**: number \| Array.<number> - The median value of all numbers if `args` contains only numbers. Returns an array with the the median values of each index, including all scalar numbers in `args` in the calculation at each index if `args` contains at least one array. +**Example** +```js +median(1, 1, 2, 3) // returns 1.5 +median(1, 1, 2, 2, 3) // returns 2 +median([10, 20, 30, 40], 10, 20, 30) // returns [15, 20, 25, 25] +median([1, 9], 2, 4, [3, 5]) // returns [median([1, 2, 4, 3]), median([9, 2, 4, 5])] = [2.5, 4.5] +``` +*** +## _min(_ ..._args_ _)_ +Finds the minimum value of one of more numbers/arrays of numbers into the function. If at least one array of numbers is passed into the function, the function will find the minimum by index. + + +| Param | Type | Description | +| --- | --- | --- | +| ...args | number \| Array.<number> | one or more numbers or arrays of numbers | + +**Returns**: number \| Array.<number> - The minimum value of all numbers if `args` contains only numbers. Returns an array with the the minimum values of each index, including all scalar numbers in `args` in the calculation at each index if `a` is an array. +**Throws**: + +- `'Array length mismatch'` if `args` contains arrays of different lengths + +**Example** +```js +min(1, 2, 3) // returns 1 +min([10, 20, 30, 40], 25) // returns [10, 20, 25, 25] +min([1, 9], 4, [3, 5]) // returns [min([1, 4, 3]), min([9, 4, 5])] = [1, 4] +``` +*** +## _mod(_ _a_, _b_ _)_ +Remainder after dividing two numbers. If at least one array of numbers is passed into the function, the function will be applied index-wise to each element. + + +| Param | Type | Description | +| --- | --- | --- | +| a | number \| Array.<number> | dividend, a number or an array of numbers | +| b | number \| Array.<number> | divisor, a number or an array of numbers, `b` != 0 | + +**Returns**: number \| Array.<number> - The remainder of `a` divided by `b` if both are numbers. Returns an array with the the remainders applied index-wise to each element if `a` or `b` is an array. +**Throws**: + +- `'Array length mismatch'` if `a` and `b` are arrays with different lengths +- `'Cannot divide by 0'` if `b` equals 0 or contains 0 + +**Example** +```js +mod(10, 7) // returns 3 +mod([11, 22, 33, 44], 10) // returns [1, 2, 3, 4] +mod(100, [3, 7, 11, 23]) // returns [1, 2, 1, 8] +mod([14, 42, 65, 108], [5, 4, 14, 2]) // returns [5, 2, 9, 0] +``` +*** +## _mode(_ ..._args_ _)_ +Finds the mode value(s) of one of more numbers/arrays of numbers into the function. If at least one array of numbers is passed into the function, the function will find the mode by index. + + +| Param | Type | Description | +| --- | --- | --- | +| ...args | number \| Array.<number> | one or more numbers or arrays of numbers | + +**Returns**: Array.<number> \| Array.<Array.<number>> - An array mode value(s) of all numbers if `args` contains only numbers. Returns an array of arrays with mode value(s) of each index, including all scalar numbers in `args` in the calculation at each index if `args` contains at least one array. +**Example** +```js +mode(1, 1, 2, 3) // returns [1] +mode(1, 1, 2, 2, 3) // returns [1,2] +mode([10, 20, 30, 40], 10, 20, 30) // returns [[10], [20], [30], [10, 20, 30, 40]] +mode([1, 9], 1, 4, [3, 5]) // returns [mode([1, 1, 4, 3]), mode([9, 1, 4, 5])] = [[1], [4, 5, 9]] +``` +*** +## _multiply(_ _a_, _b_ _)_ +Multiplies two numbers. If at least one array of numbers is passed into the function, the function will be applied index-wise to each element. + + +| Param | Type | Description | +| --- | --- | --- | +| a | number \| Array.<number> | a number or an array of numbers | +| b | number \| Array.<number> | a number or an array of numbers | + +**Returns**: number \| Array.<number> - The product of `a` and `b` if both are numbers. Returns an array with the the products applied index-wise to each element if `a` or `b` is an array. +**Throws**: + +- `'Array length mismatch'` if `a` and `b` are arrays with different lengths + +**Example** +```js +multiply(6, 3) // returns 18 +multiply([10, 20, 30, 40], 10) // returns [100, 200, 300, 400] +multiply(10, [1, 2, 5, 10]) // returns [10, 20, 50, 100] +multiply([1, 2, 3, 4], [2, 7, 5, 12]) // returns [2, 14, 15, 48] +``` +*** +## _pi(__)_ +Returns the mathematical constant PI + +**Returns**: number - The mathematical constant PI +**Example** +```js +pi() // 3.141592653589793 +``` +*** +## _pow(_ _a_, _b_ _)_ +Raises a number to a given exponent. For arrays, the function will be applied index-wise to each element. + + +| Param | Type | Description | +| --- | --- | --- | +| a | number \| Array.<number> | a number or an array of numbers | +| b | number | the power that `a` is raised to | + +**Returns**: number \| Array.<number> - `a` raised to the power of `b`. Returns an array with the each element raised to the power of `b` if `a` is an array. +**Throws**: + +- `'Missing exponent'` if `b` is not provided + +**Example** +```js +pow(2,3) // returns 8 +pow([1, 2, 3], 4) // returns [1, 16, 81] +``` +*** +## _radtodeg(_ _a_ _)_ +Converts radians to degrees for a number. For arrays, the function will be applied index-wise to each element. + + +| Param | Type | Description | +| --- | --- | --- | +| a | number \| Array.<number> | a number or an array of numbers, `a` is expected to be given in radians. | + +**Returns**: number \| Array.<number> - The degrees of `a`. Returns an array with the the degrees of each element if `a` is an array. +**Example** +```js +radtodeg(0) // returns 0 +radtodeg(1.5707963267948966) // returns 90 +radtodeg([0, 1.5707963267948966, 3.141592653589793, 6.283185307179586]) // returns [0, 90, 180, 360] +``` +*** +## _random(_ _a_, _b_ _)_ +Generates a random number within the given range where the lower bound is inclusive and the upper bound is exclusive. If no numbers are passed in, it will return a number between 0 and 1. If only one number is passed in, it will return . + + +| Param | Type | Description | +| --- | --- | --- | +| a | number | (optional) must be greater than 0 if `b` is not provided | +| b | number | (optional) must be greater | + +**Returns**: number - A random number between 0 and 1 if no numbers are passed in. Returns a random number between 0 and `a` if only one number is passed in. Returns a random number between `a` and `b` if two numbers are passed in. +**Throws**: + +- `'Min is be greater than max'` if `a` < 0 when only `a` is passed in or if `a` > `b` when both `a` and `b` are passed in + +**Example** +```js +random() // returns a random number between 0 (inclusive) and 1 (exclusive) +random(10) // returns a random number between 0 (inclusive) and 10 (exclusive) +random(-10,10) // returns a random number between -10 (inclusive) and 10 (exclusive) +``` +*** +## _range(_ ..._args_ _)_ +Finds the range of one of more numbers/arrays of numbers into the function. If at least one array of numbers is passed into the function, the function will find the range by index. + + +| Param | Type | Description | +| --- | --- | --- | +| ...args | number \| Array.<number> | one or more numbers or arrays of numbers | + +**Returns**: number \| Array.<number> - The range value of all numbers if `args` contains only numbers. Returns an array with the the range values at each index, including all scalar numbers in `args` in the calculation at each index if `args` contains at least one array. +**Example** +```js +range(1, 2, 3) // returns 2 +range([10, 20, 30, 40], 15) // returns [5, 5, 15, 25] +range([1, 9], 4, [3, 5]) // returns [range([1, 4, 3]), range([9, 4, 5])] = [3, 5] +``` +*** +## _round(_ _a_, _b_ _)_ +Rounds a number towards the nearest integer by default or decimal place if specified. For arrays, the function will be applied index-wise to each element. + + +| Param | Type | Description | +| --- | --- | --- | +| a | number \| Array.<number> | a number or an array of numbers | +| b | number | (optional) number of decimal places, default value: 0 | + +**Returns**: number \| Array.<number> - The rounded value of `a`. Returns an array with the the rounded values of each element if `a` is an array. +**Example** +```js +round(1.2) // returns 2 +round(-10.51) // returns -11 +round(-10.1, 2) // returns -10.1 +round(10.93745987, 4) // returns 10.9375 +round([2.9234, 5.1234, 3.5234, 4.49234324], 2) // returns [2.92, 5.12, 3.52, 4.49] +``` +*** +## _sin(_ _a_ _)_ +Calculates the the sine of a number. For arrays, the function will be applied index-wise to each element. + + +| Param | Type | Description | +| --- | --- | --- | +| a | number \| Array.<number> | a number or an array of numbers, `a` is expected to be given in radians. | + +**Returns**: number \| Array.<number> - The sine of `a`. Returns an array with the the sine of each element if `a` is an array. +**Example** +```js +sin(0) // returns 0 +sin(1.5707963267948966) // returns 1 +sin([0, 1.5707963267948966]) // returns [0, 1] +``` +*** +## _size(_ _a_ _)_ +Returns the length of an array. Alias for count + + +| Param | Type | Description | +| --- | --- | --- | +| a | Array.<any> | array of any values | + +**Returns**: number - The length of the array. Returns 1 if `a` is not an array. +**Throws**: + +- `'Must pass an array'` if `a` is not an array + +**Example** +```js +size([]) // returns 0 +size([-1, -2, -3, -4]) // returns 4 +size(100) // returns 1 +``` +*** +## _sqrt(_ _a_ _)_ +Calculates the square root of a number. For arrays, the function will be applied index-wise to each element. + + +| Param | Type | Description | +| --- | --- | --- | +| a | number \| Array.<number> | a number or an array of numbers | + +**Returns**: number \| Array.<number> - The square root of `a`. Returns an array with the the square roots of each element if `a` is an array. +**Throws**: + +- `'Unable find the square root of a negative number'` if `a` < 0 + +**Example** +```js +sqrt(9) // returns 3 +sqrt(30) //5.477225575051661 +sqrt([9, 16, 25]) // returns [3, 4, 5] +``` +*** +## _square(_ _a_ _)_ +Calculates the square of a number. For arrays, the function will be applied index-wise to each element. + + +| Param | Type | Description | +| --- | --- | --- | +| a | number \| Array.<number> | a number or an array of numbers | + +**Returns**: number \| Array.<number> - The square of `a`. Returns an array with the the squares of each element if `a` is an array. +**Example** +```js +square(-3) // returns 9 +square([3, 4, 5]) // returns [9, 16, 25] +``` +*** +## _subtract(_ _a_, _b_ _)_ +Subtracts two numbers. If at least one array of numbers is passed into the function, the function will be applied index-wise to each element. + + +| Param | Type | Description | +| --- | --- | --- | +| a | number \| Array.<number> | a number or an array of numbers | +| b | number \| Array.<number> | a number or an array of numbers | + +**Returns**: number \| Array.<number> - The difference of `a` and `b` if both are numbers or an array of differences applied index-wise to each element. +**Throws**: + +- `'Array length mismatch'` if `a` and `b` are arrays with different lengths + +**Example** +```js +subtract(6, 3) // returns 3 +subtract([10, 20, 30, 40], 10) // returns [0, 10, 20, 30] +subtract(10, [1, 2, 5, 10]) // returns [9, 8, 5, 0] +subtract([14, 42, 65, 108], [2, 7, 5, 12]) // returns [12, 35, 52, 96] +``` +*** +## _sum(_ ..._args_ _)_ +Calculates the sum of one or more numbers/arrays passed into the function. If at least one array is passed, the function will sum up one or more numbers/arrays of numbers and distinct values of an array. Sum accepts arrays of different lengths. + + +| Param | Type | Description | +| --- | --- | --- | +| ...args | number \| Array.<number> | one or more numbers or arrays of numbers | + +**Returns**: number - The sum of one or more numbers/arrays of numbers including distinct values in arrays +**Example** +```js +sum(1, 2, 3) // returns 6 +sum([10, 20, 30, 40], 10, 20, 30) // returns 160 +sum([1, 2], 3, [4, 5], 6) // returns sum(1, 2, 3, 4, 5, 6) = 21 +sum([10, 20, 30, 40], 10, [1, 2, 3], 22) // returns sum(10, 20, 30, 40, 10, 1, 2, 3, 22) = 138 +``` +*** +## _tan(_ _a_ _)_ +Calculates the the tangent of a number. For arrays, the function will be applied index-wise to each element. + + +| Param | Type | Description | +| --- | --- | --- | +| a | number \| Array.<number> | a number or an array of numbers, `a` is expected to be given in radians. | + +**Returns**: number \| Array.<number> - The tangent of `a`. Returns an array with the the tangent of each element if `a` is an array. +**Example** +```js +tan(0) // returns 0 +tan(1) // returns 1.5574077246549023 +tan([0, 1, -1]) // returns [0, 1.5574077246549023, -1.5574077246549023] +``` +*** +## _unique(_ _a_ _)_ +Counts the number of unique values in an array + + +| Param | Type | Description | +| --- | --- | --- | +| a | Array.<any> | array of any values | + +**Returns**: number - The number of unique values in the array. Returns 1 if `a` is not an array. +**Example** +```js +unique(100) // returns 1 +unique([]) // returns 0 +unique([1, 2, 3, 4]) // returns 4 +unique([1, 2, 3, 4, 2, 2, 2, 3, 4, 2, 4, 5, 2, 1, 4, 2]) // returns 5 +``` diff --git a/packages/kbn-tinymath/docs/template/functions.hbs b/packages/kbn-tinymath/docs/template/functions.hbs new file mode 100644 index 0000000000000..60f821e6d15bf --- /dev/null +++ b/packages/kbn-tinymath/docs/template/functions.hbs @@ -0,0 +1,15 @@ +# TinyMath Functions +This document provides detailed information about the functions available in Tinymath and lists what parameters each function accepts, the return value of that function, and examples of how each function behaves. Most of the functions below accept arrays and apply JavaScript Math methods to each element of that array. For the functions that accept multiple arrays as parameters, the function generally does calculation index by index. Any function below can be wrapped by another function as long as the return type of the inner function matches the acceptable parameter type of the outer function. + +{{#functions}} +## _{{name}}(_{{#each params}} {{#if variable}}...{{/if}}_{{name}}_{{#unless @last}},{{/unless}} {{/each}}_)_ +{{description}} + +{{>params~}} +{{>returns~}} +{{>throws~}} +{{>examples~}} +{{#unless @last}} +*** +{{/unless}} +{{/functions}} diff --git a/src/plugins/lens_oss/public/index.ts b/packages/kbn-tinymath/jest.config.js similarity index 75% rename from src/plugins/lens_oss/public/index.ts rename to packages/kbn-tinymath/jest.config.js index 2f68f6d183a22..2fb97d8aa416a 100644 --- a/src/plugins/lens_oss/public/index.ts +++ b/packages/kbn-tinymath/jest.config.js @@ -6,6 +6,8 @@ * Public License, v 1. */ -import { LensOSSPlugin } from './plugin'; - -export const plugin = () => new LensOSSPlugin(); +module.exports = { + preset: '@kbn/test', + rootDir: '../..', + roots: ['/packages/kbn-tinymath'], +}; diff --git a/packages/kbn-tinymath/package.json b/packages/kbn-tinymath/package.json new file mode 100644 index 0000000000000..34fd593672b5a --- /dev/null +++ b/packages/kbn-tinymath/package.json @@ -0,0 +1,12 @@ +{ + "name": "@kbn/tinymath", + "version": "2.0.0", + "license": "SSPL-1.0 OR Elastic License", + "private": true, + "main": "src/index.js", + "scripts": { + "kbn:bootstrap": "yarn build", + "build": "../../node_modules/.bin/pegjs -o src/grammar.js src/grammar.pegjs" + }, + "dependencies": {} +} \ No newline at end of file diff --git a/packages/kbn-tinymath/src/functions/abs.js b/packages/kbn-tinymath/src/functions/abs.js new file mode 100644 index 0000000000000..aa9eaba1ce3b2 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/abs.js @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * Calculates the absolute value of a number. For arrays, the function will be applied index-wise to each element. + * @param {(number|number[])} a a number or an array of numbers + * @return {(number|number[])} The absolute value of `a`. Returns an array with the the absolute values of each element if `a` is an array. + * + * @example + * abs(-1) // returns 1 + * abs(2) // returns 2 + * abs([-1 , -2, 3, -4]) // returns [1, 2, 3, 4] + */ + +module.exports = { abs }; + +function abs(a) { + if (Array.isArray(a)) { + return a.map((a) => Math.abs(a)); + } + return Math.abs(a); +} diff --git a/packages/kbn-tinymath/src/functions/add.js b/packages/kbn-tinymath/src/functions/add.js new file mode 100644 index 0000000000000..5a4d6802a85ea --- /dev/null +++ b/packages/kbn-tinymath/src/functions/add.js @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * Calculates the sum of one or more numbers/arrays passed into the function. If at least one array of numbers is passed into the function, the function will calculate the sum by index. + * @param {...(number|number[])} args one or more numbers or arrays of numbers + * @return {(number|number[])} The sum of all numbers in `args` if `args` contains only numbers. Returns an array of sums of the elements at each index, including all scalar numbers in `args` in the calculation at each index if `args` contains at least one array. + * @throws `'Array length mismatch'` if `args` contains arrays of different lengths + * @example + * add(1, 2, 3) // returns 6 + * add([10, 20, 30, 40], 10, 20, 30) // returns [70, 80, 90, 100] + * add([1, 2], 3, [4, 5], 6) // returns [(1 + 3 + 4 + 6), (2 + 3 + 5 + 6)] = [14, 16] + */ + +module.exports = { add }; + +function add(...args) { + if (args.length === 1) { + if (Array.isArray(args[0])) return args[0].reduce((result, current) => result + current); + return args[0]; + } + + return args.reduce((result, current) => { + if (Array.isArray(result) && Array.isArray(current)) { + if (current.length !== result.length) throw new Error('Array length mismatch'); + return result.map((val, i) => val + current[i]); + } + if (Array.isArray(result)) return result.map((val) => val + current); + if (Array.isArray(current)) return current.map((val) => val + result); + return result + current; + }); +} diff --git a/packages/kbn-tinymath/src/functions/cbrt.js b/packages/kbn-tinymath/src/functions/cbrt.js new file mode 100644 index 0000000000000..017a661702761 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/cbrt.js @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * Calculates the cube root of a number. For arrays, the function will be applied index-wise to each element. + * @param {(number|number[])} a a number or an array of numbers + * @return {(number|number[])} The cube root of `a`. Returns an array with the the cube roots of each element if `a` is an array. + * + * @example + * cbrt(-27) // returns -3 + * cbrt(94) // returns 4.546835943776344 + * cbrt([27, 64, 125]) // returns [3, 4, 5] + */ + +module.exports = { cbrt }; + +function cbrt(a) { + if (Array.isArray(a)) { + return a.map((a) => Math.cbrt(a)); + } + return Math.cbrt(a); +} diff --git a/packages/kbn-tinymath/src/functions/ceil.js b/packages/kbn-tinymath/src/functions/ceil.js new file mode 100644 index 0000000000000..7fbbabe481073 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/ceil.js @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * Calculates the ceiling of a number, i.e. rounds a number towards positive infinity. For arrays, the function will be applied index-wise to each element. + * @param {(number|number[])} a a number or an array of numbers + * @return {(number|number[])} The ceiling of `a`. Returns an array with the the ceilings of each element if `a` is an array. + * + * @example + * ceil(1.2) // returns 2 + * ceil(-1.8) // returns -1 + * ceil([1.1, 2.2, 3.3]) // returns [2, 3, 4] + */ + +module.exports = { ceil }; + +function ceil(a) { + if (Array.isArray(a)) { + return a.map((a) => Math.ceil(a)); + } + return Math.ceil(a); +} diff --git a/packages/kbn-tinymath/src/functions/clamp.js b/packages/kbn-tinymath/src/functions/clamp.js new file mode 100644 index 0000000000000..66b9e9eaf4f0d --- /dev/null +++ b/packages/kbn-tinymath/src/functions/clamp.js @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const findClamp = (a, min, max) => { + if (min > max) throw new Error('Min must be less than max'); + return Math.min(Math.max(a, min), max); +}; + +/** + * Restricts value to a given range and returns closed available value. If only min is provided, values are restricted to only a lower bound. + * @param {...(number|number[])} a one or more numbers or arrays of numbers + * @param {(number|number[])} min The minimum value this function will return. + * @param {(number|number[])} max The maximum value this function will return. + * @return {(number|number[])} The closest value between `min` (inclusive) and `max` (inclusive). Returns an array with values greater than or equal to `min` and less than or equal to `max` (if provided) at each index. + * @throws `'Array length mismatch'` if `a`, `min`, and/or `max` are arrays of different lengths + * @throws `'Min must be less than max'` if `max` is less than `min` + * @throws `'Missing minimum value. You may want to use the 'max' function instead'` if min is not provided + * @throws `'Missing maximum value. You may want to use the 'min' function instead'` if max is not provided + * + * @example + * clamp(1, 2, 3) // returns 2 + * clamp([10, 20, 30, 40], 15, 25) // returns [15, 20, 25, 25] + * clamp(10, [15, 2, 4, 20], 25) // returns [15, 10, 10, 20] + * clamp(35, 10, [20, 30, 40, 50]) // returns [20, 30, 35, 35] + * clamp([1, 9], 3, [4, 5]) // returns [clamp([1, 3, 4]), clamp([9, 3, 5])] = [3, 5] + */ + +module.exports = { clamp }; + +function clamp(a, min, max) { + if (max === null) + throw new Error("Missing maximum value. You may want to use the 'min' function instead"); + if (min === null) + throw new Error("Missing minimum value. You may want to use the 'max' function instead"); + + if (Array.isArray(max)) { + if (Array.isArray(a) && Array.isArray(min)) { + if (a.length !== max.length || a.length !== min.length) + throw new Error('Array length mismatch'); + return max.map((max, i) => findClamp(a[i], min[i], max)); + } + + if (Array.isArray(a)) { + if (a.length !== max.length) throw new Error('Array length mismatch'); + return max.map((max, i) => findClamp(a[i], min, max)); + } + + if (Array.isArray(min)) { + if (min.length !== max.length) throw new Error('Array length mismatch'); + return max.map((max, i) => findClamp(a, min[i], max)); + } + + return max.map((max) => findClamp(a, min, max)); + } + + if (Array.isArray(a) && Array.isArray(min)) { + if (a.length !== min.length) throw new Error('Array length mismatch'); + return a.map((a, i) => findClamp(a, min[i])); + } + + if (Array.isArray(a)) { + return a.map((a) => findClamp(a, min, max)); + } + + if (Array.isArray(min)) { + return min.map((min) => findClamp(a, min, max)); + } + + return findClamp(a, min, max); +} diff --git a/packages/kbn-tinymath/src/functions/cos.js b/packages/kbn-tinymath/src/functions/cos.js new file mode 100644 index 0000000000000..0385f52793c27 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/cos.js @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * Calculates the the cosine of a number. For arrays, the function will be applied index-wise to each element. + * @param {(number|number[])} a a number or an array of numbers, `a` is expected to be given in radians. + * @return {(number|number[])} The cosine of `a`. Returns an array with the the cosine of each element if `a` is an array. + * @example + * cos(0) // returns 1 + * cos(1.5707963267948966) // returns 6.123233995736766e-17 + * cos([0, 1.5707963267948966]) // returns [1, 6.123233995736766e-17] + */ + +module.exports = { cos }; + +function cos(a) { + if (Array.isArray(a)) { + return a.map((a) => Math.cos(a)); + } + return Math.cos(a); +} diff --git a/packages/kbn-tinymath/src/functions/count.js b/packages/kbn-tinymath/src/functions/count.js new file mode 100644 index 0000000000000..b037999b7ac8a --- /dev/null +++ b/packages/kbn-tinymath/src/functions/count.js @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { size } = require('./size.js'); + +/** + * Returns the length of an array. Alias for size + * @param {any[]} a array of any values + * @return {(number)} The length of the array. Returns 1 if `a` is not an array. + * @throws `'Must pass an array'` if `a` is not an array + * @example + * count([]) // returns 0 + * count([-1, -2, -3, -4]) // returns 4 + * count(100) // returns 1 + */ + +module.exports = { count }; + +function count(a) { + return size(a); +} + +count.skipNumberValidation = true; diff --git a/packages/kbn-tinymath/src/functions/cube.js b/packages/kbn-tinymath/src/functions/cube.js new file mode 100644 index 0000000000000..de14ac8749ae1 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/cube.js @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { pow } = require('./pow.js'); + +/** + * Calculates the cube of a number. For arrays, the function will be applied index-wise to each element. + * @param {(number|number[])} a a number or an array of numbers + * @return {(number|number[])} The cube of `a`. Returns an array with the the cubes of each element if `a` is an array. + * + * @example + * cube(-3) // returns -27 + * cube([3, 4, 5]) // returns [27, 64, 125] + */ + +module.exports = { cube }; + +function cube(a) { + return pow(a, 3); +} diff --git a/packages/kbn-tinymath/src/functions/degtorad.js b/packages/kbn-tinymath/src/functions/degtorad.js new file mode 100644 index 0000000000000..20fd8ac9e2060 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/degtorad.js @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * Converts degrees to radians for a number. For arrays, the function will be applied index-wise to each element. + * @param {(number|number[])} a a number or an array of numbers, `a` is expected to be given in degrees. + * @return {(number|number[])} The radians of `a`. Returns an array with the the radians of each element if `a` is an array. + * @example + * degtorad(0) // returns 0 + * degtorad(90) // returns 1.5707963267948966 + * degtorad([0, 90, 180, 360]) // returns [0, 1.5707963267948966, 3.141592653589793, 6.283185307179586] + */ + +module.exports = { degtorad }; + +function degtorad(a) { + if (Array.isArray(a)) { + return a.map((a) => (a * Math.PI) / 180); + } + return (a * Math.PI) / 180; +} diff --git a/packages/kbn-tinymath/src/functions/divide.js b/packages/kbn-tinymath/src/functions/divide.js new file mode 100644 index 0000000000000..889e2305cbd9e --- /dev/null +++ b/packages/kbn-tinymath/src/functions/divide.js @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * Divides two numbers. If at least one array of numbers is passed into the function, the function will be applied index-wise to each element. + * @param {(number|number[])} a dividend, a number or an array of numbers + * @param {(number|number[])} b divisor, a number or an array of numbers, `b` != 0 + * @return {(number|number[])} The quotient of `a` and `b` if both are numbers. Returns an array with the quotients applied index-wise to each element if `a` or `b` is an array. + * @throws `'Array length mismatch'` if `a` and `b` are arrays with different lengths + * - `'Cannot divide by 0'` if `b` equals 0 or contains 0 + * @example + * divide(6, 3) // returns 2 + * divide([10, 20, 30, 40], 10) // returns [1, 2, 3, 4] + * divide(10, [1, 2, 5, 10]) // returns [10, 5, 2, 1] + * divide([14, 42, 65, 108], [2, 7, 5, 12]) // returns [7, 6, 13, 9] + */ + +module.exports = { divide }; + +function divide(a, b) { + if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) throw new Error('Array length mismatch'); + return a.map((val, i) => { + if (b[i] === 0) throw new Error('Cannot divide by 0'); + return val / b[i]; + }); + } + if (Array.isArray(b)) return b.map((b) => a / b); + if (b === 0) throw new Error('Cannot divide by 0'); + if (Array.isArray(a)) return a.map((a) => a / b); + return a / b; +} diff --git a/packages/kbn-tinymath/src/functions/exp.js b/packages/kbn-tinymath/src/functions/exp.js new file mode 100644 index 0000000000000..d7fd3877001c9 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/exp.js @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * Calculates _e^x_ where _e_ is Euler's number. For arrays, the function will be applied index-wise to each element. + * @param {(number|number[])} a a number or an array of numbers + * @return {(number|number[])} `e^a`. Returns an array with the values of `e^x` evaluated where `x` is each element of `a` if `a` is an array. + * + * @example + * exp(2) // returns e^2 = 7.3890560989306495 + * exp([1, 2, 3]) // returns [e^1, e^2, e^3] = [2.718281828459045, 7.3890560989306495, 20.085536923187668] + */ + +module.exports = { exp }; + +function exp(a) { + if (Array.isArray(a)) { + return a.map((a) => Math.exp(a)); + } + return Math.exp(a); +} diff --git a/packages/kbn-tinymath/src/functions/first.js b/packages/kbn-tinymath/src/functions/first.js new file mode 100644 index 0000000000000..911482541b1d3 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/first.js @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * Returns the first element of an array. If anything other than an array is passed in, the input is returned. + * @param {any[]} a array of any values + * @return {*} The first element of `a`. Returns `a` if `a` is not an array. + * + * @example + * first(2) // returns 2 + * first([1, 2, 3]) // returns 1 + */ + +module.exports = { first }; + +function first(a) { + if (Array.isArray(a)) { + return a[0]; + } + return a; +} + +first.skipNumberValidation = true; diff --git a/packages/kbn-tinymath/src/functions/fix.js b/packages/kbn-tinymath/src/functions/fix.js new file mode 100644 index 0000000000000..16ed2d0dcb54f --- /dev/null +++ b/packages/kbn-tinymath/src/functions/fix.js @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const fixer = (a) => { + if (a > 0) { + return Math.floor(a); + } + return Math.ceil(a); +}; + +/** + * Calculates the fix of a number, i.e. rounds a number towards 0. For arrays, the function will be applied index-wise to each element. + * @param {(number|number[])} a a number or an array of numbers + * @return {(number|number[])} The fix of `a`. Returns an array with the the fixes for each element if `a` is an array. + * + * @example + * fix(1.2) // returns 1 + * fix(-1.8) // returns -1 + * fix([1.8, 2.9, -3.7, -4.6]) // returns [1, 2, -3, -4] + */ + +module.exports = { fix }; + +function fix(a) { + if (Array.isArray(a)) { + return a.map((a) => fixer(a)); + } + return fixer(a); +} diff --git a/packages/kbn-tinymath/src/functions/floor.js b/packages/kbn-tinymath/src/functions/floor.js new file mode 100644 index 0000000000000..db90697edc346 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/floor.js @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * Calculates the floor of a number, i.e. rounds a number towards negative infinity. For arrays, the function will be applied index-wise to each element. + * @param {(number|number[])} a a number or an array of numbers + * @return {(number|number[])} The floor of `a`. Returns an array with the the floor of each element if `a` is an array. + * + * @example + * floor(1.8) // returns 1 + * floor(-1.2) // returns -2 + * floor([1.7, 2.8, 3.9]) // returns [1, 2, 3] + */ + +module.exports = { floor }; + +function floor(a) { + if (Array.isArray(a)) { + return a.map((a) => Math.floor(a)); + } + return Math.floor(a); +} diff --git a/packages/kbn-tinymath/src/functions/index.js b/packages/kbn-tinymath/src/functions/index.js new file mode 100644 index 0000000000000..ab5805cc0a77e --- /dev/null +++ b/packages/kbn-tinymath/src/functions/index.js @@ -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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { abs } = require('./abs'); +const { add } = require('./add'); +const { cbrt } = require('./cbrt'); +const { ceil } = require('./ceil'); +const { clamp } = require('./clamp'); +const { cos } = require('./cos'); +const { count } = require('./count'); +const { cube } = require('./cube'); +const { degtorad } = require('./degtorad'); +const { divide } = require('./divide'); +const { exp } = require('./exp'); +const { first } = require('./first'); +const { fix } = require('./fix'); +const { floor } = require('./floor'); +const { last } = require('./last'); +const { log } = require('./log'); +const { log10 } = require('./log10'); +const { max } = require('./max'); +const { mean } = require('./mean'); +const { median } = require('./median'); +const { min } = require('./min'); +const { mod } = require('./mod'); +const { mode } = require('./mode'); +const { multiply } = require('./multiply'); +const { pi } = require('./pi'); +const { pow } = require('./pow'); +const { radtodeg } = require('./radtodeg'); +const { random } = require('./random'); +const { range } = require('./range'); +const { round } = require('./round'); +const { sin } = require('./sin'); +const { size } = require('./size'); +const { sqrt } = require('./sqrt'); +const { square } = require('./square'); +const { subtract } = require('./subtract'); +const { sum } = require('./sum'); +const { tan } = require('./tan'); +const { unique } = require('./unique'); + +module.exports = { + functions: { + abs, + add, + cbrt, + ceil, + clamp, + cos, + count, + cube, + degtorad, + divide, + exp, + first, + fix, + floor, + last, + log, + log10, + max, + mean, + median, + min, + mod, + mode, + multiply, + pi, + pow, + radtodeg, + random, + range, + round, + sin, + size, + sqrt, + square, + subtract, + sum, + tan, + unique, + }, +}; diff --git a/packages/kbn-tinymath/src/functions/last.js b/packages/kbn-tinymath/src/functions/last.js new file mode 100644 index 0000000000000..08964c784ba88 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/last.js @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * Returns the last element of an array. If anything other than an array is passed in, the input is returned. + * @param {any[]} a array of any values + * @return {*} The last element of `a`. Returns `a` if `a` is not an array. + * + * @example + * last(2) // returns 2 + * last([1, 2, 3]) // returns 3 + */ + +module.exports = { last }; + +function last(a) { + if (Array.isArray(a)) { + return a[a.length - 1]; + } + return a; +} + +last.skipNumberValidation = true; diff --git a/packages/kbn-tinymath/src/functions/lib/transpose.js b/packages/kbn-tinymath/src/functions/lib/transpose.js new file mode 100644 index 0000000000000..6a771f4f54336 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/lib/transpose.js @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * Transposes a 2D array, i.e. turns the rows into columns and vice versa. Scalar values are also included in the transpose. + * @param {any[][]} args an array or an array that contains arrays + * @param {number} index index of the first array element in args + * @return {any[][]} transpose of args + * @throws `'Array length mismatch'` if `args` contains arrays of different lengths + * @example + * transpose([[1,2], [3,4], [5,6]], 0) // returns [[1, 3, 5], [2, 4, 6]] + * transpose([10, 20, [10, 20, 30, 40], 30], 2) // returns [[10, 20, 10, 30], [10, 20, 20, 30], [10, 20, 30, 30], [10, 20, 40, 30]] + * transpose([4, [1, 9], [3, 5]], 1) // returns [[4, 1, 3], [4, 9, 5]] + */ + +module.exports = { transpose }; + +function transpose(args, index) { + const len = args[index].length; + return args[index].map((col, i) => + args.map((row) => { + if (Array.isArray(row)) { + if (row.length !== len) throw new Error('Array length mismatch'); + return row[i]; + } + return row; + }) + ); +} diff --git a/packages/kbn-tinymath/src/functions/log.js b/packages/kbn-tinymath/src/functions/log.js new file mode 100644 index 0000000000000..07fb8376438d6 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/log.js @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const changeOfBase = (a, b) => Math.log(a) / Math.log(b); + +/** + * Calculates the logarithm of a number. For arrays, the function will be applied index-wise to each element. + * @param {(number|number[])} a a number or an array of numbers, `a` must be greater than 0 + * @param {{number}} b (optional) base for the logarithm. If not provided a value, the default base is e, and the natural log is calculated. + * @return {(number|number[])} The logarithm of `a`. Returns an array with the the logarithms of each element if `a` is an array. + * @throws `'Base out of range'` if `b` <= 0 + * - 'Must be greater than 0' if `a` > 0 + * @example + * log(1) // returns 0 + * log(64, 8) // returns 2 + * log(42, 5) // returns 2.322344707681546 + * log([2, 4, 8, 16, 32], 2) // returns [1, 2, 3, 4, 5] + */ + +module.exports = { log }; + +function log(a, b = Math.E) { + if (b <= 0) throw new Error('Base out of range'); + + if (Array.isArray(a)) { + return a.map((a) => { + if (a < 0) throw new Error('Must be greater than 0'); + return changeOfBase(a, b); + }); + } + if (a < 0) throw new Error('Must be greater than 0'); + return changeOfBase(a, b); +} diff --git a/packages/kbn-tinymath/src/functions/log10.js b/packages/kbn-tinymath/src/functions/log10.js new file mode 100644 index 0000000000000..79417031d5ed8 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/log10.js @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { log } = require('./log.js'); + +/** + * Calculates the logarithm base 10 of a number. For arrays, the function will be applied index-wise to each element. + * @param {(number|number[])} a a number or an array of numbers, `a` must be greater than 0 + * @return {(number|number[])} The logarithm of `a`. Returns an array with the the logarithms base 10 of each element if `a` is an array. + * @throws `'Must be greater than 0'` if `a` < 0 + * @example + * log(10) // returns 1 + * log(100) // returns 2 + * log(80) // returns 1.9030899869919433 + * log([10, 100, 1000, 10000, 100000]) // returns [1, 2, 3, 4, 5] + */ + +module.exports = { log10 }; + +function log10(a) { + return log(a, 10); +} diff --git a/packages/kbn-tinymath/src/functions/max.js b/packages/kbn-tinymath/src/functions/max.js new file mode 100644 index 0000000000000..13cebbfdf662a --- /dev/null +++ b/packages/kbn-tinymath/src/functions/max.js @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * Finds the maximum value of one of more numbers/arrays of numbers into the function. If at least one array of numbers is passed into the function, the function will find the maximum by index. + * @param {...(number|number[])} args one or more numbers or arrays of numbers + * @return {(number|number[])} The maximum value of all numbers if `args` contains only numbers. Returns an array with the the maximum values at each index, including all scalar numbers in `args` in the calculation at each index if `args` contains at least one array. + * @throws `'Array length mismatch'` if `args` contains arrays of different lengths + * @example + * max(1, 2, 3) // returns 3 + * max([10, 20, 30, 40], 15) // returns [15, 20, 30, 40] + * max([1, 9], 4, [3, 5]) // returns [max([1, 4, 3]), max([9, 4, 5])] = [4, 9] + */ + +module.exports = { max }; + +function max(...args) { + if (args.length === 1) { + if (Array.isArray(args[0])) + return args[0].reduce((result, current) => Math.max(result, current)); + return args[0]; + } + + return args.reduce((result, current) => { + if (Array.isArray(result) && Array.isArray(current)) { + if (current.length !== result.length) throw new Error('Array length mismatch'); + return result.map((val, i) => Math.max(val, current[i])); + } + if (Array.isArray(result)) return result.map((val) => Math.max(val, current)); + if (Array.isArray(current)) return current.map((val) => Math.max(val, result)); + return Math.max(result, current); + }); +} diff --git a/packages/kbn-tinymath/src/functions/mean.js b/packages/kbn-tinymath/src/functions/mean.js new file mode 100644 index 0000000000000..ee37d77b10e71 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/mean.js @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { add } = require('./add.js'); + +/** + * Finds the mean value of one of more numbers/arrays of numbers into the function. If at least one array of numbers is passed into the function, the function will find the mean by index. + * @param {...(number|number[])} args one or more numbers or arrays of numbers + * @return {(number|number[])} The mean value of all numbers if `args` contains only numbers. Returns an array with the the mean values of each index, including all scalar numbers in `args` in the calculation at each index if `args` contains at least one array. + * + * @example + * mean(1, 2, 3) // returns 2 + * mean([10, 20, 30, 40], 20) // returns [15, 20, 25, 30] + * mean([1, 9], 5, [3, 4]) // returns [mean([1, 5, 3]), mean([9, 5, 4])] = [3, 6] + */ + +module.exports = { mean }; + +function mean(...args) { + if (args.length === 1) { + if (Array.isArray(args[0])) return add(args[0]) / args[0].length; + return args[0]; + } + const sum = add(...args); + + if (Array.isArray(sum)) { + return sum.map((val) => val / args.length); + } + + return sum / args.length; +} diff --git a/packages/kbn-tinymath/src/functions/median.js b/packages/kbn-tinymath/src/functions/median.js new file mode 100644 index 0000000000000..6f1e3cd4972e5 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/median.js @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { transpose } = require('./lib/transpose'); + +const findMedian = (a) => { + const len = a.length; + const half = Math.floor(len / 2); + + a.sort((a, b) => b - a); + + if (len % 2 === 0) { + return (a[half] + a[half - 1]) / 2; + } + + return a[half]; +}; + +/** + * Finds the median value(s) of one of more numbers/arrays of numbers into the function. If at least one array of numbers is passed into the function, the function will find the median by index. + * @param {...(number|number[])} args one or more numbers or arrays of numbers + * @return {(number|number[])} The median value of all numbers if `args` contains only numbers. Returns an array with the the median values of each index, including all scalar numbers in `args` in the calculation at each index if `args` contains at least one array. + * + * @example + * median(1, 1, 2, 3) // returns 1.5 + * median(1, 1, 2, 2, 3) // returns 2 + * median([10, 20, 30, 40], 10, 20, 30) // returns [15, 20, 25, 25] + * median([1, 9], 2, 4, [3, 5]) // returns [median([1, 2, 4, 3]), median([9, 2, 4, 5])] = [2.5, 4.5] + */ + +module.exports = { median }; + +function median(...args) { + if (args.length === 1) { + if (Array.isArray(args[0])) return findMedian(args[0]); + return args[0]; + } + + const firstArray = args.findIndex((element) => Array.isArray(element)); + if (firstArray !== -1) { + const result = transpose(args, firstArray); + return result.map((val) => findMedian(val)); + } + return findMedian(args); +} diff --git a/packages/kbn-tinymath/src/functions/min.js b/packages/kbn-tinymath/src/functions/min.js new file mode 100644 index 0000000000000..44509bedfd088 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/min.js @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * Finds the minimum value of one of more numbers/arrays of numbers into the function. If at least one array of numbers is passed into the function, the function will find the minimum by index. + * @param {...(number|number[])} args one or more numbers or arrays of numbers + * @return {(number|number[])} The minimum value of all numbers if `args` contains only numbers. Returns an array with the the minimum values of each index, including all scalar numbers in `args` in the calculation at each index if `a` is an array. + * @throws `'Array length mismatch'` if `args` contains arrays of different lengths + * @example + * min(1, 2, 3) // returns 1 + * min([10, 20, 30, 40], 25) // returns [10, 20, 25, 25] + * min([1, 9], 4, [3, 5]) // returns [min([1, 4, 3]), min([9, 4, 5])] = [1, 4] + */ + +module.exports = { min }; + +function min(...args) { + if (args.length === 1) { + if (Array.isArray(args[0])) + return args[0].reduce((result, current) => Math.min(result, current)); + return args[0]; + } + + return args.reduce((result, current) => { + if (Array.isArray(result) && Array.isArray(current)) { + if (current.length !== result.length) throw new Error('Array length mismatch'); + return result.map((val, i) => Math.min(val, current[i])); + } + if (Array.isArray(result)) return result.map((val) => Math.min(val, current)); + if (Array.isArray(current)) return current.map((val) => Math.min(val, result)); + return Math.min(result, current); + }); +} diff --git a/packages/kbn-tinymath/src/functions/mod.js b/packages/kbn-tinymath/src/functions/mod.js new file mode 100644 index 0000000000000..93c23077a9d69 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/mod.js @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * Remainder after dividing two numbers. If at least one array of numbers is passed into the function, the function will be applied index-wise to each element. + * @param {(number|number[])} a dividend, a number or an array of numbers + * @param {(number|number[])} b divisor, a number or an array of numbers, `b` != 0 + * @return {(number|number[])} The remainder of `a` divided by `b` if both are numbers. Returns an array with the the remainders applied index-wise to each element if `a` or `b` is an array. + * @throws `'Array length mismatch'` if `a` and `b` are arrays with different lengths + * - `'Cannot divide by 0'` if `b` equals 0 or contains 0 + * @example + * mod(10, 7) // returns 3 + * mod([11, 22, 33, 44], 10) // returns [1, 2, 3, 4] + * mod(100, [3, 7, 11, 23]) // returns [1, 2, 1, 8] + * mod([14, 42, 65, 108], [5, 4, 14, 2]) // returns [5, 2, 9, 0] + */ + +module.exports = { mod }; + +function mod(a, b) { + if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) throw new Error('Array length mismatch'); + return a.map((val, i) => { + if (b[i] === 0) throw new Error('Cannot divide by 0'); + return val % b[i]; + }); + } + if (Array.isArray(b)) return b.map((b) => a % b); + if (b === 0) throw new Error('Cannot divide by 0'); + if (Array.isArray(a)) return a.map((a) => a % b); + return a % b; +} diff --git a/packages/kbn-tinymath/src/functions/mode.js b/packages/kbn-tinymath/src/functions/mode.js new file mode 100644 index 0000000000000..4c7d8414602df --- /dev/null +++ b/packages/kbn-tinymath/src/functions/mode.js @@ -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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { transpose } = require('./lib/transpose'); + +const findMode = (a) => { + let maxFreq = 0; + const mapping = {}; + + a.map((val) => { + if (mapping[val] === undefined) { + mapping[val] = 0; + } + mapping[val] += 1; + if (mapping[val] > maxFreq) { + maxFreq = mapping[val]; + } + }); + + return Object.keys(mapping) + .filter((key) => mapping[key] === maxFreq) + .map((val) => parseFloat(val)) + .sort((a, b) => a - b); +}; + +/** + * Finds the mode value(s) of one of more numbers/arrays of numbers into the function. If at least one array of numbers is passed into the function, the function will find the mode by index. + * @param {...(number|number[])} args one or more numbers or arrays of numbers + * @return {(number[]|number[][])} An array mode value(s) of all numbers if `args` contains only numbers. Returns an array of arrays with mode value(s) of each index, including all scalar numbers in `args` in the calculation at each index if `args` contains at least one array. + * + * @example + * mode(1, 1, 2, 3) // returns [1] + * mode(1, 1, 2, 2, 3) // returns [1,2] + * mode([10, 20, 30, 40], 10, 20, 30) // returns [[10], [20], [30], [10, 20, 30, 40]] + * mode([1, 9], 1, 4, [3, 5]) // returns [mode([1, 1, 4, 3]), mode([9, 1, 4, 5])] = [[1], [4, 5, 9]] + */ + +module.exports = { mode }; + +function mode(...args) { + if (args.length === 1) { + if (Array.isArray(args[0])) return findMode(args[0]); + return args[0]; + } + + const firstArray = args.findIndex((element) => Array.isArray(element)); + if (firstArray !== -1) { + const result = transpose(args, firstArray); + return result.map((val) => findMode(val)); + } + return findMode(args); +} diff --git a/packages/kbn-tinymath/src/functions/multiply.js b/packages/kbn-tinymath/src/functions/multiply.js new file mode 100644 index 0000000000000..6334b510e550b --- /dev/null +++ b/packages/kbn-tinymath/src/functions/multiply.js @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * Multiplies two numbers. If at least one array of numbers is passed into the function, the function will be applied index-wise to each element. + * @param {(number|number[])} a a number or an array of numbers + * @param {(number|number[])} b a number or an array of numbers + * @return {(number|number[])} The product of `a` and `b` if both are numbers. Returns an array with the the products applied index-wise to each element if `a` or `b` is an array. + * @throws `'Array length mismatch'` if `a` and `b` are arrays with different lengths + * @example + * multiply(6, 3) // returns 18 + * multiply([10, 20, 30, 40], 10) // returns [100, 200, 300, 400] + * multiply(10, [1, 2, 5, 10]) // returns [10, 20, 50, 100] + * multiply([1, 2, 3, 4], [2, 7, 5, 12]) // returns [2, 14, 15, 48] + */ + +module.exports = { multiply }; + +function multiply(...args) { + return args.reduce((result, current) => { + if (Array.isArray(result) && Array.isArray(current)) { + if (current.length !== result.length) throw new Error('Array length mismatch'); + return result.map((val, i) => val * current[i]); + } + if (Array.isArray(result)) return result.map((val) => val * current); + if (Array.isArray(current)) return current.map((val) => val * result); + return result * current; + }); +} diff --git a/packages/kbn-tinymath/src/functions/pi.js b/packages/kbn-tinymath/src/functions/pi.js new file mode 100644 index 0000000000000..5dd625cf7f0d3 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/pi.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * Returns the mathematical constant PI + * @return {(number)} The mathematical constant PI + * + * @example + * pi() // 3.141592653589793 + */ + +module.exports = { pi }; + +function pi() { + return Math.PI; +} diff --git a/packages/kbn-tinymath/src/functions/pow.js b/packages/kbn-tinymath/src/functions/pow.js new file mode 100644 index 0000000000000..b44b9679fc7f8 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/pow.js @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * Raises a number to a given exponent. For arrays, the function will be applied index-wise to each element. + * @param {(number|number[])} a a number or an array of numbers + * @param {(number)} b the power that `a` is raised to + * @return {(number|number[])} `a` raised to the power of `b`. Returns an array with the each element raised to the power of `b` if `a` is an array. + * @throws `'Missing exponent'` if `b` is not provided + * @example + * pow(2,3) // returns 8 + * pow([1, 2, 3], 4) // returns [1, 16, 81] + */ + +module.exports = { pow }; + +function pow(a, b) { + if (b == null) throw new Error('Missing exponent'); + if (Array.isArray(a)) { + return a.map((a) => Math.pow(a, b)); + } + return Math.pow(a, b); +} diff --git a/packages/kbn-tinymath/src/functions/radtodeg.js b/packages/kbn-tinymath/src/functions/radtodeg.js new file mode 100644 index 0000000000000..51f911e2dcad0 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/radtodeg.js @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * Converts radians to degrees for a number. For arrays, the function will be applied index-wise to each element. + * @param {(number|number[])} a a number or an array of numbers, `a` is expected to be given in radians. + * @return {(number|number[])} The degrees of `a`. Returns an array with the the degrees of each element if `a` is an array. + * @example + * radtodeg(0) // returns 0 + * radtodeg(1.5707963267948966) // returns 90 + * radtodeg([0, 1.5707963267948966, 3.141592653589793, 6.283185307179586]) // returns [0, 90, 180, 360] + */ + +module.exports = { radtodeg }; + +function radtodeg(a) { + if (Array.isArray(a)) { + return a.map((a) => (a * 180) / Math.PI); + } + return (a * 180) / Math.PI; +} diff --git a/packages/kbn-tinymath/src/functions/random.js b/packages/kbn-tinymath/src/functions/random.js new file mode 100644 index 0000000000000..ffe5c3a9cb8e2 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/random.js @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * Generates a random number within the given range where the lower bound is inclusive and the upper bound is exclusive. If no numbers are passed in, it will return a number between 0 and 1. If only one number is passed in, it will return . + * @param {number} a (optional) must be greater than 0 if `b` is not provided + * @param {number} b (optional) must be greater + * @return {number} A random number between 0 and 1 if no numbers are passed in. Returns a random number between 0 and `a` if only one number is passed in. Returns a random number between `a` and `b` if two numbers are passed in. + * @throws `'Min is be greater than max'` if `a` < 0 when only `a` is passed in or if `a` > `b` when both `a` and `b` are passed in + * @example + * random() // returns a random number between 0 (inclusive) and 1 (exclusive) + * random(10) // returns a random number between 0 (inclusive) and 10 (exclusive) + * random(-10,10) // returns a random number between -10 (inclusive) and 10 (exclusive) + */ + +module.exports = { random }; + +function random(a, b) { + if (a == null) return Math.random(); + + // a: max, generate random number between 0 and a + if (b == null) { + if (a < 0) throw new Error(`Min is greater than max`); + return Math.random() * a; + } + + // a: min, b: max, generate random number between a and b + if (a > b) throw new Error(`Min is greater than max`); + return Math.random() * (b - a) + a; +} diff --git a/packages/kbn-tinymath/src/functions/range.js b/packages/kbn-tinymath/src/functions/range.js new file mode 100644 index 0000000000000..31f9b618bb1db --- /dev/null +++ b/packages/kbn-tinymath/src/functions/range.js @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { max } = require('./max.js'); +const { min } = require('./min.js'); +const { subtract } = require('./subtract.js'); + +/** + * Finds the range of one of more numbers/arrays of numbers into the function. If at least one array of numbers is passed into the function, the function will find the range by index. + * @param {...(number|number[])} args one or more numbers or arrays of numbers + * @return {(number|number[])} The range value of all numbers if `args` contains only numbers. Returns an array with the the range values at each index, including all scalar numbers in `args` in the calculation at each index if `args` contains at least one array. + * + * @example + * range(1, 2, 3) // returns 2 + * range([10, 20, 30, 40], 15) // returns [5, 5, 15, 25] + * range([1, 9], 4, [3, 5]) // returns [range([1, 4, 3]), range([9, 4, 5])] = [3, 5] + */ + +module.exports = { range }; + +function range(...args) { + return subtract(max(...args), min(...args)); +} diff --git a/packages/kbn-tinymath/src/functions/round.js b/packages/kbn-tinymath/src/functions/round.js new file mode 100644 index 0000000000000..1e8847e6dfd2b --- /dev/null +++ b/packages/kbn-tinymath/src/functions/round.js @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const rounder = (a, b) => Math.round(a * Math.pow(10, b)) / Math.pow(10, b); + +/** + * Rounds a number towards the nearest integer by default or decimal place if specified. For arrays, the function will be applied index-wise to each element. + * @param {(number|number[])} a a number or an array of numbers + * @param {(number)} b (optional) number of decimal places, default value: 0 + * @return {(number|number[])} The rounded value of `a`. Returns an array with the the rounded values of each element if `a` is an array. + * + * @example + * round(1.2) // returns 2 + * round(-10.51) // returns -11 + * round(-10.1, 2) // returns -10.1 + * round(10.93745987, 4) // returns 10.9375 + * round([2.9234, 5.1234, 3.5234, 4.49234324], 2) // returns [2.92, 5.12, 3.52, 4.49] + */ + +module.exports = { round }; + +function round(a, b = 0) { + if (Array.isArray(a)) { + return a.map((a) => rounder(a, b)); + } + return rounder(a, b); +} diff --git a/packages/kbn-tinymath/src/functions/sin.js b/packages/kbn-tinymath/src/functions/sin.js new file mode 100644 index 0000000000000..f08ffa8bdc197 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/sin.js @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * Calculates the the sine of a number. For arrays, the function will be applied index-wise to each element. + * @param {(number|number[])} a a number or an array of numbers, `a` is expected to be given in radians. + * @return {(number|number[])} The sine of `a`. Returns an array with the the sine of each element if `a` is an array. + * @example + * sin(0) // returns 0 + * sin(1.5707963267948966) // returns 1 + * sin([0, 1.5707963267948966]) // returns [0, 1] + */ + +module.exports = { sin }; + +function sin(a) { + if (Array.isArray(a)) { + return a.map((a) => Math.sin(a)); + } + return Math.sin(a); +} diff --git a/packages/kbn-tinymath/src/functions/size.js b/packages/kbn-tinymath/src/functions/size.js new file mode 100644 index 0000000000000..5156a70b38d69 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/size.js @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * Returns the length of an array. Alias for count + * @param {any[]} a array of any values + * @return {(number)} The length of the array. Returns 1 if `a` is not an array. + * @throws `'Must pass an array'` if `a` is not an array + * @example + * size([]) // returns 0 + * size([-1, -2, -3, -4]) // returns 4 + * size(100) // returns 1 + */ + +module.exports = { size }; + +function size(a) { + if (Array.isArray(a)) return a.length; + throw new Error('Must pass an array'); +} + +size.skipNumberValidation = true; diff --git a/packages/kbn-tinymath/src/functions/sqrt.js b/packages/kbn-tinymath/src/functions/sqrt.js new file mode 100644 index 0000000000000..2c55b2256e0f6 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/sqrt.js @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * Calculates the square root of a number. For arrays, the function will be applied index-wise to each element. + * @param {(number|number[])} a a number or an array of numbers + * @return {(number|number[])} The square root of `a`. Returns an array with the the square roots of each element if `a` is an array. + * @throws `'Unable find the square root of a negative number'` if `a` < 0 + * @example + * sqrt(9) // returns 3 + * sqrt(30) //5.477225575051661 + * sqrt([9, 16, 25]) // returns [3, 4, 5] + */ + +module.exports = { sqrt }; + +function sqrt(a) { + if (Array.isArray(a)) { + return a.map((a) => { + if (a < 0) throw new Error('Unable find the square root of a negative number'); + return Math.sqrt(a); + }); + } + + if (a < 0) throw new Error('Unable find the square root of a negative number'); + return Math.sqrt(a); +} diff --git a/packages/kbn-tinymath/src/functions/square.js b/packages/kbn-tinymath/src/functions/square.js new file mode 100644 index 0000000000000..a5bccdef7661b --- /dev/null +++ b/packages/kbn-tinymath/src/functions/square.js @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { pow } = require('./pow.js'); + +/** + * Calculates the square of a number. For arrays, the function will be applied index-wise to each element. + * @param {(number|number[])} a a number or an array of numbers + * @return {(number|number[])} The square of `a`. Returns an array with the the squares of each element if `a` is an array. + * + * @example + * square(-3) // returns 9 + * square([3, 4, 5]) // returns [9, 16, 25] + */ + +module.exports = { square }; + +function square(a) { + return pow(a, 2); +} diff --git a/packages/kbn-tinymath/src/functions/subtract.js b/packages/kbn-tinymath/src/functions/subtract.js new file mode 100644 index 0000000000000..8e5fd256bf158 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/subtract.js @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * Subtracts two numbers. If at least one array of numbers is passed into the function, the function will be applied index-wise to each element. + * @param {(number|number[])} a a number or an array of numbers + * @param {(number|number[])} b a number or an array of numbers + * @return {(number|number[])} The difference of `a` and `b` if both are numbers or an array of differences applied index-wise to each element. + * @throws `'Array length mismatch'` if `a` and `b` are arrays with different lengths + * @example + * subtract(6, 3) // returns 3 + * subtract([10, 20, 30, 40], 10) // returns [0, 10, 20, 30] + * subtract(10, [1, 2, 5, 10]) // returns [9, 8, 5, 0] + * subtract([14, 42, 65, 108], [2, 7, 5, 12]) // returns [12, 35, 52, 96] + */ + +module.exports = { subtract }; + +function subtract(a, b) { + if (Array.isArray(a) && Array.isArray(b)) { + if (a.length !== b.length) throw new Error('Array length mismatch'); + return a.map((val, i) => val - b[i]); + } + if (Array.isArray(a)) return a.map((a) => a - b); + if (Array.isArray(b)) return b.map((b) => a - b); + return a - b; +} diff --git a/packages/kbn-tinymath/src/functions/sum.js b/packages/kbn-tinymath/src/functions/sum.js new file mode 100644 index 0000000000000..b13a86d5c2122 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/sum.js @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const findSum = (total, current) => total + current; + +/** + * Calculates the sum of one or more numbers/arrays passed into the function. If at least one array is passed, the function will sum up one or more numbers/arrays of numbers and distinct values of an array. Sum accepts arrays of different lengths. + * @param {...(number|number[])} args one or more numbers or arrays of numbers + * @return {number} The sum of one or more numbers/arrays of numbers including distinct values in arrays + * + * @example + * sum(1, 2, 3) // returns 6 + * sum([10, 20, 30, 40], 10, 20, 30) // returns 160 + * sum([1, 2], 3, [4, 5], 6) // returns sum(1, 2, 3, 4, 5, 6) = 21 + * sum([10, 20, 30, 40], 10, [1, 2, 3], 22) // returns sum(10, 20, 30, 40, 10, 1, 2, 3, 22) = 138 + */ + +module.exports = { sum }; + +function sum(...args) { + return args.reduce((total, current) => { + if (Array.isArray(current)) { + return total + current.reduce(findSum, 0); + } + return total + current; + }, 0); +} diff --git a/packages/kbn-tinymath/src/functions/tan.js b/packages/kbn-tinymath/src/functions/tan.js new file mode 100644 index 0000000000000..56ea4c35f1459 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/tan.js @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * Calculates the the tangent of a number. For arrays, the function will be applied index-wise to each element. + * @param {(number|number[])} a a number or an array of numbers, `a` is expected to be given in radians. + * @return {(number|number[])} The tangent of `a`. Returns an array with the the tangent of each element if `a` is an array. + * @example + * tan(0) // returns 0 + * tan(1) // returns 1.5574077246549023 + * tan([0, 1, -1]) // returns [0, 1.5574077246549023, -1.5574077246549023] + */ + +module.exports = { tan }; + +function tan(a) { + if (Array.isArray(a)) { + return a.map((a) => Math.tan(a)); + } + return Math.tan(a); +} diff --git a/packages/kbn-tinymath/src/functions/unique.js b/packages/kbn-tinymath/src/functions/unique.js new file mode 100644 index 0000000000000..60196e8568855 --- /dev/null +++ b/packages/kbn-tinymath/src/functions/unique.js @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * Counts the number of unique values in an array + * @param {any[]} a array of any values + * @return {number} The number of unique values in the array. Returns 1 if `a` is not an array. + * + * @example + * unique(100) // returns 1 + * unique([]) // returns 0 + * unique([1, 2, 3, 4]) // returns 4 + * unique([1, 2, 3, 4, 2, 2, 2, 3, 4, 2, 4, 5, 2, 1, 4, 2]) // returns 5 + */ + +module.exports = { unique }; + +function unique(a) { + if (Array.isArray(a)) { + return a.filter((val, i) => a.indexOf(val) === i).length; + } + return 1; +} + +unique.skipNumberValidation = true; diff --git a/packages/kbn-tinymath/src/grammar.js b/packages/kbn-tinymath/src/grammar.js new file mode 100644 index 0000000000000..60dfcf4800631 --- /dev/null +++ b/packages/kbn-tinymath/src/grammar.js @@ -0,0 +1,1385 @@ +/* + * Generated by PEG.js 0.10.0. + * + * http://pegjs.org/ + */ + +"use strict"; + +function peg$subclass(child, parent) { + function ctor() { this.constructor = child; } + ctor.prototype = parent.prototype; + child.prototype = new ctor(); +} + +function peg$SyntaxError(message, expected, found, location) { + this.message = message; + this.expected = expected; + this.found = found; + this.location = location; + this.name = "SyntaxError"; + + if (typeof Error.captureStackTrace === "function") { + Error.captureStackTrace(this, peg$SyntaxError); + } +} + +peg$subclass(peg$SyntaxError, Error); + +peg$SyntaxError.buildMessage = function(expected, found) { + var DESCRIBE_EXPECTATION_FNS = { + literal: function(expectation) { + return "\"" + literalEscape(expectation.text) + "\""; + }, + + "class": function(expectation) { + var escapedParts = "", + i; + + for (i = 0; i < expectation.parts.length; i++) { + escapedParts += expectation.parts[i] instanceof Array + ? classEscape(expectation.parts[i][0]) + "-" + classEscape(expectation.parts[i][1]) + : classEscape(expectation.parts[i]); + } + + return "[" + (expectation.inverted ? "^" : "") + escapedParts + "]"; + }, + + any: function(expectation) { + return "any character"; + }, + + end: function(expectation) { + return "end of input"; + }, + + other: function(expectation) { + return expectation.description; + } + }; + + function hex(ch) { + return ch.charCodeAt(0).toString(16).toUpperCase(); + } + + function literalEscape(s) { + return s + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/\0/g, '\\0') + .replace(/\t/g, '\\t') + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + .replace(/[\x00-\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) + .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return '\\x' + hex(ch); }); + } + + function classEscape(s) { + return s + .replace(/\\/g, '\\\\') + .replace(/\]/g, '\\]') + .replace(/\^/g, '\\^') + .replace(/-/g, '\\-') + .replace(/\0/g, '\\0') + .replace(/\t/g, '\\t') + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + .replace(/[\x00-\x0F]/g, function(ch) { return '\\x0' + hex(ch); }) + .replace(/[\x10-\x1F\x7F-\x9F]/g, function(ch) { return '\\x' + hex(ch); }); + } + + function describeExpectation(expectation) { + return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation); + } + + function describeExpected(expected) { + var descriptions = new Array(expected.length), + i, j; + + for (i = 0; i < expected.length; i++) { + descriptions[i] = describeExpectation(expected[i]); + } + + descriptions.sort(); + + if (descriptions.length > 0) { + for (i = 1, j = 1; i < descriptions.length; i++) { + if (descriptions[i - 1] !== descriptions[i]) { + descriptions[j] = descriptions[i]; + j++; + } + } + descriptions.length = j; + } + + switch (descriptions.length) { + case 1: + return descriptions[0]; + + case 2: + return descriptions[0] + " or " + descriptions[1]; + + default: + return descriptions.slice(0, -1).join(", ") + + ", or " + + descriptions[descriptions.length - 1]; + } + } + + function describeFound(found) { + return found ? "\"" + literalEscape(found) + "\"" : "end of input"; + } + + return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found."; +}; + +function peg$parse(input, options) { + options = options !== void 0 ? options : {}; + + var peg$FAILED = {}, + + peg$startRuleFunctions = { start: peg$parsestart }, + peg$startRuleFunction = peg$parsestart, + + peg$c0 = peg$otherExpectation("whitespace"), + peg$c1 = /^[ \t\n\r]/, + peg$c2 = peg$classExpectation([" ", "\t", "\n", "\r"], false, false), + peg$c3 = /^[ ]/, + peg$c4 = peg$classExpectation([" "], false, false), + peg$c5 = /^["']/, + peg$c6 = peg$classExpectation(["\"", "'"], false, false), + peg$c7 = /^[A-Za-z_@.[\]\-]/, + peg$c8 = peg$classExpectation([["A", "Z"], ["a", "z"], "_", "@", ".", "[", "]", "-"], false, false), + peg$c9 = /^[0-9A-Za-z._@[\]\-]/, + peg$c10 = peg$classExpectation([["0", "9"], ["A", "Z"], ["a", "z"], ".", "_", "@", "[", "]", "-"], false, false), + peg$c11 = peg$otherExpectation("literal"), + peg$c12 = function(literal) { + return literal; + }, + peg$c13 = function(first, rest) { // We can open this up later. Strict for now. + return first + rest.join(''); + }, + peg$c14 = function(first, mid) { + return first + mid.map(m => m[0].join('') + m[1].join('')).join('') + }, + peg$c15 = "+", + peg$c16 = peg$literalExpectation("+", false), + peg$c17 = "-", + peg$c18 = peg$literalExpectation("-", false), + peg$c19 = function(left, rest) { + return rest.reduce((acc, curr) => ({ + name: curr[0] === '+' ? 'add' : 'subtract', + args: [acc, curr[1]] + }), left) + }, + peg$c20 = "*", + peg$c21 = peg$literalExpectation("*", false), + peg$c22 = "/", + peg$c23 = peg$literalExpectation("/", false), + peg$c24 = function(left, rest) { + return rest.reduce((acc, curr) => ({ + name: curr[0] === '*' ? 'multiply' : 'divide', + args: [acc, curr[1]] + }), left) + }, + peg$c25 = "(", + peg$c26 = peg$literalExpectation("(", false), + peg$c27 = ")", + peg$c28 = peg$literalExpectation(")", false), + peg$c29 = function(expr) { + return expr + }, + peg$c30 = peg$otherExpectation("arguments"), + peg$c31 = ",", + peg$c32 = peg$literalExpectation(",", false), + peg$c33 = function(first, arg) {return arg}, + peg$c34 = function(first, rest) { + return [first].concat(rest); + }, + peg$c35 = peg$otherExpectation("function"), + peg$c36 = /^[a-z]/, + peg$c37 = peg$classExpectation([["a", "z"]], false, false), + peg$c38 = function(name, args) { + return {name: name.join(''), args: args || []}; + }, + peg$c39 = peg$otherExpectation("number"), + peg$c40 = function() { return parseFloat(text()); }, + peg$c41 = /^[eE]/, + peg$c42 = peg$classExpectation(["e", "E"], false, false), + peg$c43 = peg$otherExpectation("exponent"), + peg$c44 = ".", + peg$c45 = peg$literalExpectation(".", false), + peg$c46 = "0", + peg$c47 = peg$literalExpectation("0", false), + peg$c48 = /^[1-9]/, + peg$c49 = peg$classExpectation([["1", "9"]], false, false), + peg$c50 = /^[0-9]/, + peg$c51 = peg$classExpectation([["0", "9"]], false, false), + + peg$currPos = 0, + peg$savedPos = 0, + peg$posDetailsCache = [{ line: 1, column: 1 }], + peg$maxFailPos = 0, + peg$maxFailExpected = [], + peg$silentFails = 0, + + peg$result; + + if ("startRule" in options) { + if (!(options.startRule in peg$startRuleFunctions)) { + throw new Error("Can't start parsing from rule \"" + options.startRule + "\"."); + } + + peg$startRuleFunction = peg$startRuleFunctions[options.startRule]; + } + + function text() { + return input.substring(peg$savedPos, peg$currPos); + } + + function location() { + return peg$computeLocation(peg$savedPos, peg$currPos); + } + + function expected(description, location) { + location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos) + + throw peg$buildStructuredError( + [peg$otherExpectation(description)], + input.substring(peg$savedPos, peg$currPos), + location + ); + } + + function error(message, location) { + location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos) + + throw peg$buildSimpleError(message, location); + } + + function peg$literalExpectation(text, ignoreCase) { + return { type: "literal", text: text, ignoreCase: ignoreCase }; + } + + function peg$classExpectation(parts, inverted, ignoreCase) { + return { type: "class", parts: parts, inverted: inverted, ignoreCase: ignoreCase }; + } + + function peg$anyExpectation() { + return { type: "any" }; + } + + function peg$endExpectation() { + return { type: "end" }; + } + + function peg$otherExpectation(description) { + return { type: "other", description: description }; + } + + function peg$computePosDetails(pos) { + var details = peg$posDetailsCache[pos], p; + + if (details) { + return details; + } else { + p = pos - 1; + while (!peg$posDetailsCache[p]) { + p--; + } + + details = peg$posDetailsCache[p]; + details = { + line: details.line, + column: details.column + }; + + while (p < pos) { + if (input.charCodeAt(p) === 10) { + details.line++; + details.column = 1; + } else { + details.column++; + } + + p++; + } + + peg$posDetailsCache[pos] = details; + return details; + } + } + + function peg$computeLocation(startPos, endPos) { + var startPosDetails = peg$computePosDetails(startPos), + endPosDetails = peg$computePosDetails(endPos); + + return { + start: { + offset: startPos, + line: startPosDetails.line, + column: startPosDetails.column + }, + end: { + offset: endPos, + line: endPosDetails.line, + column: endPosDetails.column + } + }; + } + + function peg$fail(expected) { + if (peg$currPos < peg$maxFailPos) { return; } + + if (peg$currPos > peg$maxFailPos) { + peg$maxFailPos = peg$currPos; + peg$maxFailExpected = []; + } + + peg$maxFailExpected.push(expected); + } + + function peg$buildSimpleError(message, location) { + return new peg$SyntaxError(message, null, null, location); + } + + function peg$buildStructuredError(expected, found, location) { + return new peg$SyntaxError( + peg$SyntaxError.buildMessage(expected, found), + expected, + found, + location + ); + } + + function peg$parsestart() { + var s0; + + s0 = peg$parseAddSubtract(); + + return s0; + } + + function peg$parse_() { + var s0, s1; + + peg$silentFails++; + s0 = []; + if (peg$c1.test(input.charAt(peg$currPos))) { + s1 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c2); } + } + while (s1 !== peg$FAILED) { + s0.push(s1); + if (peg$c1.test(input.charAt(peg$currPos))) { + s1 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c2); } + } + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c0); } + } + + return s0; + } + + function peg$parseSpace() { + var s0; + + if (peg$c3.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c4); } + } + + return s0; + } + + function peg$parseQuote() { + var s0; + + if (peg$c5.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c6); } + } + + return s0; + } + + function peg$parseStartChar() { + var s0; + + if (peg$c7.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c8); } + } + + return s0; + } + + function peg$parseValidChar() { + var s0; + + if (peg$c9.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c10); } + } + + return s0; + } + + function peg$parseLiteral() { + var s0, s1, s2, s3; + + peg$silentFails++; + s0 = peg$currPos; + s1 = peg$parse_(); + if (s1 !== peg$FAILED) { + s2 = peg$parseNumber(); + if (s2 === peg$FAILED) { + s2 = peg$parseVariableWithQuote(); + if (s2 === peg$FAILED) { + s2 = peg$parseVariable(); + } + } + if (s2 !== peg$FAILED) { + s3 = peg$parse_(); + if (s3 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c12(s2); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c11); } + } + + return s0; + } + + function peg$parseVariable() { + var s0, s1, s2, s3, s4; + + s0 = peg$currPos; + s1 = peg$parse_(); + if (s1 !== peg$FAILED) { + s2 = peg$parseStartChar(); + if (s2 !== peg$FAILED) { + s3 = []; + s4 = peg$parseValidChar(); + while (s4 !== peg$FAILED) { + s3.push(s4); + s4 = peg$parseValidChar(); + } + if (s3 !== peg$FAILED) { + s4 = peg$parse_(); + if (s4 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c13(s2, s3); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseVariableWithQuote() { + var s0, s1, s2, s3, s4, s5, s6, s7, s8; + + s0 = peg$currPos; + s1 = peg$parse_(); + if (s1 !== peg$FAILED) { + s2 = peg$parseQuote(); + if (s2 !== peg$FAILED) { + s3 = peg$parseStartChar(); + if (s3 !== peg$FAILED) { + s4 = []; + s5 = peg$currPos; + s6 = []; + s7 = peg$parseSpace(); + while (s7 !== peg$FAILED) { + s6.push(s7); + s7 = peg$parseSpace(); + } + if (s6 !== peg$FAILED) { + s7 = []; + s8 = peg$parseValidChar(); + if (s8 !== peg$FAILED) { + while (s8 !== peg$FAILED) { + s7.push(s8); + s8 = peg$parseValidChar(); + } + } else { + s7 = peg$FAILED; + } + if (s7 !== peg$FAILED) { + s6 = [s6, s7]; + s5 = s6; + } else { + peg$currPos = s5; + s5 = peg$FAILED; + } + } else { + peg$currPos = s5; + s5 = peg$FAILED; + } + while (s5 !== peg$FAILED) { + s4.push(s5); + s5 = peg$currPos; + s6 = []; + s7 = peg$parseSpace(); + while (s7 !== peg$FAILED) { + s6.push(s7); + s7 = peg$parseSpace(); + } + if (s6 !== peg$FAILED) { + s7 = []; + s8 = peg$parseValidChar(); + if (s8 !== peg$FAILED) { + while (s8 !== peg$FAILED) { + s7.push(s8); + s8 = peg$parseValidChar(); + } + } else { + s7 = peg$FAILED; + } + if (s7 !== peg$FAILED) { + s6 = [s6, s7]; + s5 = s6; + } else { + peg$currPos = s5; + s5 = peg$FAILED; + } + } else { + peg$currPos = s5; + s5 = peg$FAILED; + } + } + if (s4 !== peg$FAILED) { + s5 = peg$parseQuote(); + if (s5 !== peg$FAILED) { + s6 = peg$parse_(); + if (s6 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c14(s3, s4); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseAddSubtract() { + var s0, s1, s2, s3, s4, s5, s6; + + s0 = peg$currPos; + s1 = peg$parse_(); + if (s1 !== peg$FAILED) { + s2 = peg$parseMultiplyDivide(); + if (s2 !== peg$FAILED) { + s3 = []; + s4 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 43) { + s5 = peg$c15; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c16); } + } + if (s5 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 45) { + s5 = peg$c17; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c18); } + } + } + if (s5 !== peg$FAILED) { + s6 = peg$parseMultiplyDivide(); + if (s6 !== peg$FAILED) { + s5 = [s5, s6]; + s4 = s5; + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + while (s4 !== peg$FAILED) { + s3.push(s4); + s4 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 43) { + s5 = peg$c15; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c16); } + } + if (s5 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 45) { + s5 = peg$c17; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c18); } + } + } + if (s5 !== peg$FAILED) { + s6 = peg$parseMultiplyDivide(); + if (s6 !== peg$FAILED) { + s5 = [s5, s6]; + s4 = s5; + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + } + if (s3 !== peg$FAILED) { + s4 = peg$parse_(); + if (s4 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c19(s2, s3); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseMultiplyDivide() { + var s0, s1, s2, s3, s4, s5, s6; + + s0 = peg$currPos; + s1 = peg$parse_(); + if (s1 !== peg$FAILED) { + s2 = peg$parseFactor(); + if (s2 !== peg$FAILED) { + s3 = []; + s4 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 42) { + s5 = peg$c20; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c21); } + } + if (s5 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 47) { + s5 = peg$c22; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c23); } + } + } + if (s5 !== peg$FAILED) { + s6 = peg$parseFactor(); + if (s6 !== peg$FAILED) { + s5 = [s5, s6]; + s4 = s5; + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + while (s4 !== peg$FAILED) { + s3.push(s4); + s4 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 42) { + s5 = peg$c20; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c21); } + } + if (s5 === peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 47) { + s5 = peg$c22; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c23); } + } + } + if (s5 !== peg$FAILED) { + s6 = peg$parseFactor(); + if (s6 !== peg$FAILED) { + s5 = [s5, s6]; + s4 = s5; + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + } + if (s3 !== peg$FAILED) { + s4 = peg$parse_(); + if (s4 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c24(s2, s3); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseFactor() { + var s0; + + s0 = peg$parseGroup(); + if (s0 === peg$FAILED) { + s0 = peg$parseFunction(); + if (s0 === peg$FAILED) { + s0 = peg$parseLiteral(); + } + } + + return s0; + } + + function peg$parseGroup() { + var s0, s1, s2, s3, s4, s5, s6, s7; + + s0 = peg$currPos; + s1 = peg$parse_(); + if (s1 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 40) { + s2 = peg$c25; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c26); } + } + if (s2 !== peg$FAILED) { + s3 = peg$parse_(); + if (s3 !== peg$FAILED) { + s4 = peg$parseAddSubtract(); + if (s4 !== peg$FAILED) { + s5 = peg$parse_(); + if (s5 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 41) { + s6 = peg$c27; + peg$currPos++; + } else { + s6 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c28); } + } + if (s6 !== peg$FAILED) { + s7 = peg$parse_(); + if (s7 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c29(s4); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseArguments() { + var s0, s1, s2, s3, s4, s5, s6, s7, s8; + + peg$silentFails++; + s0 = peg$currPos; + s1 = peg$parse_(); + if (s1 !== peg$FAILED) { + s2 = peg$parseAddSubtract(); + if (s2 !== peg$FAILED) { + s3 = []; + s4 = peg$currPos; + s5 = peg$parse_(); + if (s5 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 44) { + s6 = peg$c31; + peg$currPos++; + } else { + s6 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c32); } + } + if (s6 !== peg$FAILED) { + s7 = peg$parse_(); + if (s7 !== peg$FAILED) { + s8 = peg$parseAddSubtract(); + if (s8 !== peg$FAILED) { + peg$savedPos = s4; + s5 = peg$c33(s2, s8); + s4 = s5; + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + while (s4 !== peg$FAILED) { + s3.push(s4); + s4 = peg$currPos; + s5 = peg$parse_(); + if (s5 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 44) { + s6 = peg$c31; + peg$currPos++; + } else { + s6 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c32); } + } + if (s6 !== peg$FAILED) { + s7 = peg$parse_(); + if (s7 !== peg$FAILED) { + s8 = peg$parseAddSubtract(); + if (s8 !== peg$FAILED) { + peg$savedPos = s4; + s5 = peg$c33(s2, s8); + s4 = s5; + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + } else { + peg$currPos = s4; + s4 = peg$FAILED; + } + } + if (s3 !== peg$FAILED) { + s4 = peg$parse_(); + if (s4 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 44) { + s5 = peg$c31; + peg$currPos++; + } else { + s5 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c32); } + } + if (s5 === peg$FAILED) { + s5 = null; + } + if (s5 !== peg$FAILED) { + s6 = peg$parse_(); + if (s6 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c34(s2, s3); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c30); } + } + + return s0; + } + + function peg$parseFunction() { + var s0, s1, s2, s3, s4, s5, s6, s7, s8; + + peg$silentFails++; + s0 = peg$currPos; + s1 = peg$parse_(); + if (s1 !== peg$FAILED) { + s2 = []; + if (peg$c36.test(input.charAt(peg$currPos))) { + s3 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c37); } + } + if (s3 !== peg$FAILED) { + while (s3 !== peg$FAILED) { + s2.push(s3); + if (peg$c36.test(input.charAt(peg$currPos))) { + s3 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c37); } + } + } + } else { + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 40) { + s3 = peg$c25; + peg$currPos++; + } else { + s3 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c26); } + } + if (s3 !== peg$FAILED) { + s4 = peg$parse_(); + if (s4 !== peg$FAILED) { + s5 = peg$parseArguments(); + if (s5 === peg$FAILED) { + s5 = null; + } + if (s5 !== peg$FAILED) { + s6 = peg$parse_(); + if (s6 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 41) { + s7 = peg$c27; + peg$currPos++; + } else { + s7 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c28); } + } + if (s7 !== peg$FAILED) { + s8 = peg$parse_(); + if (s8 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c38(s2, s5); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c35); } + } + + return s0; + } + + function peg$parseNumber() { + var s0, s1, s2, s3, s4; + + peg$silentFails++; + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 45) { + s1 = peg$c17; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c18); } + } + if (s1 === peg$FAILED) { + s1 = null; + } + if (s1 !== peg$FAILED) { + s2 = peg$parseInteger(); + if (s2 !== peg$FAILED) { + s3 = peg$parseFraction(); + if (s3 === peg$FAILED) { + s3 = null; + } + if (s3 !== peg$FAILED) { + s4 = peg$parseExp(); + if (s4 === peg$FAILED) { + s4 = null; + } + if (s4 !== peg$FAILED) { + peg$savedPos = s0; + s1 = peg$c40(); + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c39); } + } + + return s0; + } + + function peg$parseE() { + var s0; + + if (peg$c41.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c42); } + } + + return s0; + } + + function peg$parseExp() { + var s0, s1, s2, s3, s4; + + peg$silentFails++; + s0 = peg$currPos; + s1 = peg$parseE(); + if (s1 !== peg$FAILED) { + if (input.charCodeAt(peg$currPos) === 45) { + s2 = peg$c17; + peg$currPos++; + } else { + s2 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c18); } + } + if (s2 === peg$FAILED) { + s2 = null; + } + if (s2 !== peg$FAILED) { + s3 = []; + s4 = peg$parseDigit(); + if (s4 !== peg$FAILED) { + while (s4 !== peg$FAILED) { + s3.push(s4); + s4 = peg$parseDigit(); + } + } else { + s3 = peg$FAILED; + } + if (s3 !== peg$FAILED) { + s1 = [s1, s2, s3]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + peg$silentFails--; + if (s0 === peg$FAILED) { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c43); } + } + + return s0; + } + + function peg$parseFraction() { + var s0, s1, s2, s3; + + s0 = peg$currPos; + if (input.charCodeAt(peg$currPos) === 46) { + s1 = peg$c44; + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c45); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseDigit(); + if (s3 !== peg$FAILED) { + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseDigit(); + } + } else { + s2 = peg$FAILED; + } + if (s2 !== peg$FAILED) { + s1 = [s1, s2]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + + return s0; + } + + function peg$parseInteger() { + var s0, s1, s2, s3; + + if (input.charCodeAt(peg$currPos) === 48) { + s0 = peg$c46; + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c47); } + } + if (s0 === peg$FAILED) { + s0 = peg$currPos; + if (peg$c48.test(input.charAt(peg$currPos))) { + s1 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s1 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c49); } + } + if (s1 !== peg$FAILED) { + s2 = []; + s3 = peg$parseDigit(); + while (s3 !== peg$FAILED) { + s2.push(s3); + s3 = peg$parseDigit(); + } + if (s2 !== peg$FAILED) { + s1 = [s1, s2]; + s0 = s1; + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } else { + peg$currPos = s0; + s0 = peg$FAILED; + } + } + + return s0; + } + + function peg$parseDigit() { + var s0; + + if (peg$c50.test(input.charAt(peg$currPos))) { + s0 = input.charAt(peg$currPos); + peg$currPos++; + } else { + s0 = peg$FAILED; + if (peg$silentFails === 0) { peg$fail(peg$c51); } + } + + return s0; + } + + peg$result = peg$startRuleFunction(); + + if (peg$result !== peg$FAILED && peg$currPos === input.length) { + return peg$result; + } else { + if (peg$result !== peg$FAILED && peg$currPos < input.length) { + peg$fail(peg$endExpectation()); + } + + throw peg$buildStructuredError( + peg$maxFailExpected, + peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null, + peg$maxFailPos < input.length + ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) + : peg$computeLocation(peg$maxFailPos, peg$maxFailPos) + ); + } +} + +module.exports = { + SyntaxError: peg$SyntaxError, + parse: peg$parse +}; diff --git a/packages/kbn-tinymath/src/grammar.pegjs b/packages/kbn-tinymath/src/grammar.pegjs new file mode 100644 index 0000000000000..cab8e024e60b3 --- /dev/null +++ b/packages/kbn-tinymath/src/grammar.pegjs @@ -0,0 +1,100 @@ +// tinymath parsing grammar + +start + = Expression + +// characters + +_ "whitespace" + = [ \t\n\r]* + +Space + = [ ] + +Quote + = [\"\'] + +StartChar + = [A-Za-z_@.\[\]-] + +ValidChar + = [0-9A-Za-z._@\[\]-] + +// literals and variables + +Literal "literal" + = _ literal:(Number / VariableWithQuote / Variable) _ { + return literal; + } + +Variable + = _ first:StartChar rest:ValidChar* _ { // We can open this up later. Strict for now. + return first + rest.join(''); + } + +VariableWithQuote + = _ Quote first:StartChar mid:(Space* ValidChar+)* Quote _ { + return first + mid.map(m => m[0].join('') + m[1].join('')).join('') + } + +// expressions + +Expression + = AddSubtract + +AddSubtract + = _ left:MultiplyDivide rest:(('+' / '-') MultiplyDivide)* _ { + return rest.reduce((acc, curr) => ({ + name: curr[0] === '+' ? 'add' : 'subtract', + args: [acc, curr[1]] + }), left) + } + +MultiplyDivide + = _ left:Factor rest:(('*' / '/') Factor)* _ { + return rest.reduce((acc, curr) => ({ + name: curr[0] === '*' ? 'multiply' : 'divide', + args: [acc, curr[1]] + }), left) + } + +Factor + = Group + / Function + / Literal + +Group + = _ '(' _ expr:Expression _ ')' _ { + return expr + } + +Arguments "arguments" + = _ first:Expression rest:(_ ',' _ arg:Expression {return arg})* _ ','? _ { + return [first].concat(rest); + } + +Function "function" + = _ name:[a-z]+ '(' _ args:Arguments? _ ')' _ { + return {name: name.join(''), args: args || []}; + } + +// Numbers. Lol. + +Number "number" + = '-'? Integer Fraction? Exp? { return parseFloat(text()); } + +E + = [eE] + +Exp "exponent" + = E '-'? Digit+ + +Fraction + = '.' Digit+ + +Integer + = '0' + / ([1-9] Digit*) + +Digit + = [0-9] diff --git a/packages/kbn-tinymath/src/index.js b/packages/kbn-tinymath/src/index.js new file mode 100644 index 0000000000000..e61956bd63e55 --- /dev/null +++ b/packages/kbn-tinymath/src/index.js @@ -0,0 +1,86 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { get } = require('lodash'); +const { parse: parseFn } = require('./grammar'); +const { functions: includedFunctions } = require('./functions'); + +module.exports = { parse, evaluate, interpret }; + +function parse(input, options) { + if (input == null) { + throw new Error('Missing expression'); + } + + if (typeof input !== 'string') { + throw new Error('Expression must be a string'); + } + + try { + return parseFn(input, options); + } catch (e) { + throw new Error(`Failed to parse expression. ${e.message}`); + } +} + +function evaluate(expression, scope = {}, injectedFunctions = {}) { + scope = scope || {}; + return interpret(parse(expression), scope, injectedFunctions); +} + +function interpret(node, scope, injectedFunctions) { + const functions = Object.assign({}, includedFunctions, injectedFunctions); // eslint-disable-line + return exec(node); + + function exec(node) { + const type = getType(node); + + if (type === 'function') return invoke(node); + + if (type === 'string') { + const val = getValue(scope, node); + if (typeof val === 'undefined') throw new Error(`Unknown variable: ${node}`); + return val; + } + + return node; // Can only be a number at this point + } + + function invoke(node) { + const { name, args } = node; + const fn = functions[name]; + if (!fn) throw new Error(`No such function: ${name}`); + const execOutput = args.map(exec); + if (fn.skipNumberValidation || isOperable(execOutput)) return fn(...execOutput); + return NaN; + } +} + +function getValue(scope, node) { + // attempt to read value from nested object first, check for exact match if value is undefined + const val = get(scope, node); + return typeof val !== 'undefined' ? val : scope[node]; +} + +function getType(x) { + const type = typeof x; + if (type === 'object') { + const keys = Object.keys(x); + if (keys.length !== 2 || !x.name || !x.args) throw new Error('Invalid AST object'); + return 'function'; + } + if (type === 'string' || type === 'number') return type; + throw new Error(`Unknown AST property type: ${type}`); +} + +function isOperable(args) { + return args.every((arg) => { + if (Array.isArray(arg)) return isOperable(arg); + return typeof arg === 'number' && !isNaN(arg); + }); +} diff --git a/packages/kbn-tinymath/test/functions/abs.test.js b/packages/kbn-tinymath/test/functions/abs.test.js new file mode 100644 index 0000000000000..09ae042d23de6 --- /dev/null +++ b/packages/kbn-tinymath/test/functions/abs.test.js @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { abs } = require('../../src/functions/abs.js'); + +describe('Abs', () => { + it('numbers', () => { + expect(abs(-10)).toEqual(10); + expect(abs(10)).toEqual(10); + }); + + it('arrays', () => { + expect(abs([-1])).toEqual([1]); + expect(abs([-10, -20, -30, -40])).toEqual([10, 20, 30, 40]); + expect(abs([-13, 30, -90, 200])).toEqual([13, 30, 90, 200]); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/add.test.js b/packages/kbn-tinymath/test/functions/add.test.js new file mode 100644 index 0000000000000..56b4fc48a62ad --- /dev/null +++ b/packages/kbn-tinymath/test/functions/add.test.js @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { add } = require('../../src/functions/add.js'); + +describe('Add', () => { + it('numbers', () => { + expect(add(1)).toEqual(1); + expect(add(10, 2, 5, 8)).toEqual(25); + expect(add(0.1, 0.2, 0.4, 0.3)).toEqual(0.1 + 0.2 + 0.3 + 0.4); + }); + + it('arrays & numbers', () => { + expect(add([10, 20, 30, 40], 10, 20, 30)).toEqual([70, 80, 90, 100]); + expect(add(10, [10, 20, 30, 40], [1, 2, 3, 4], 22)).toEqual([43, 54, 65, 76]); + }); + + it('arrays', () => { + expect(add([1, 2, 3, 4])).toEqual(10); + expect(add([1, 2, 3, 4], [1, 2, 5, 10])).toEqual([2, 4, 8, 14]); + expect(add([1, 2, 3, 4], [1, 2, 5, 10], [10, 20, 30, 40])).toEqual([12, 24, 38, 54]); + expect(add([11, 48, 60, 72], [1, 2, 3, 4])).toEqual([12, 50, 63, 76]); + }); + + it('array length mismatch', () => { + expect(() => add([1, 2], [3])).toThrow('Array length mismatch'); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/cbrt.test.js b/packages/kbn-tinymath/test/functions/cbrt.test.js new file mode 100644 index 0000000000000..8b8b57c5a1ba1 --- /dev/null +++ b/packages/kbn-tinymath/test/functions/cbrt.test.js @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { cbrt } = require('../../src/functions/cbrt.js'); + +describe('Cbrt', () => { + it('numbers', () => { + expect(cbrt(27)).toEqual(3); + expect(cbrt(-1)).toEqual(-1); + expect(cbrt(94)).toEqual(4.546835943776344); + }); + + it('arrays', () => { + expect(cbrt([27, 64, 125])).toEqual([3, 4, 5]); + expect(cbrt([1, 8, 1000])).toEqual([1, 2, 10]); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/ceil.test.js b/packages/kbn-tinymath/test/functions/ceil.test.js new file mode 100644 index 0000000000000..0809c9ba1e9d5 --- /dev/null +++ b/packages/kbn-tinymath/test/functions/ceil.test.js @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { ceil } = require('../../src/functions/ceil.js'); + +describe('Ceil', () => { + it('numbers', () => { + expect(ceil(-10.5)).toEqual(-10); + expect(ceil(-10.1)).toEqual(-10); + expect(ceil(10.9)).toEqual(11); + }); + + it('arrays', () => { + expect(ceil([-10.5, -20.9, -30.1, -40.2])).toEqual([-10, -20, -30, -40]); + expect(ceil([2.9, 5.1, 3.5, 4.3])).toEqual([3, 6, 4, 5]); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/clamp.test.js b/packages/kbn-tinymath/test/functions/clamp.test.js new file mode 100644 index 0000000000000..7e6015bf304cf --- /dev/null +++ b/packages/kbn-tinymath/test/functions/clamp.test.js @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { clamp } = require('../../src/functions/clamp.js'); + +describe('Clamp', () => { + it('numbers', () => { + expect(clamp(10, 5, 8)).toEqual(8); + expect(clamp(1, 2, 3)).toEqual(2); + expect(clamp(0.5, 0.2, 0.4)).toEqual(0.4); + expect(clamp(3.58, 0, 1)).toEqual(1); + expect(clamp(-0.48, 0, 1)).toEqual(0); + expect(clamp(1.38, -1, 0)).toEqual(0); + }); + + it('arrays & numbers', () => { + expect(clamp([10, 20, 30, 40], 15, 25)).toEqual([15, 20, 25, 25]); + expect(clamp(10, [15, 2, 4, 20], 25)).toEqual([15, 10, 10, 20]); + expect(clamp(5, 10, [20, 30, 40, 50])).toEqual([10, 10, 10, 10]); + expect(clamp(35, 10, [20, 30, 40, 50])).toEqual([20, 30, 35, 35]); + expect(clamp([1, 9], 3, [4, 5])).toEqual([3, 5]); + }); + + it('arrays', () => { + expect(clamp([6, 28, 32, 10], [11, 2, 5, 10], [20, 21, 22, 23])).toEqual([11, 21, 22, 10]); + }); + + it('errors', () => { + expect(() => clamp(1, 4, 3)).toThrow('Min must be less than max'); + expect(() => clamp([1, 2], [3], 3)).toThrow('Array length mismatch'); + expect(() => clamp([1, 2], [3], 3)).toThrow('Array length mismatch'); + expect(() => clamp(10, 20, null)).toThrow( + "Missing maximum value. You may want to use the 'min' function instead" + ); + expect(() => clamp([10, 20, 30, 40], 15, null)).toThrow( + "Missing maximum value. You may want to use the 'min' function instead" + ); + expect(() => clamp(10, null, 30)).toThrow( + "Missing minimum value. You may want to use the 'max' function instead" + ); + expect(() => clamp([11, 28, 60, 10], null, [1, 48, 3, -17])).toThrow( + "Missing minimum value. You may want to use the 'max' function instead" + ); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/cos.test.js b/packages/kbn-tinymath/test/functions/cos.test.js new file mode 100644 index 0000000000000..9e4461512fe06 --- /dev/null +++ b/packages/kbn-tinymath/test/functions/cos.test.js @@ -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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { cos } = require('../../src/functions/cos.js'); + +describe('Cosine', () => { + it('numbers', () => { + expect(cos(0)).toEqual(1); + expect(cos(1.5707963267948966)).toEqual(6.123233995736766e-17); + }); + + it('arrays', () => { + expect(cos([0, 1.5707963267948966])).toEqual([1, 6.123233995736766e-17]); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/cube.test.js b/packages/kbn-tinymath/test/functions/cube.test.js new file mode 100644 index 0000000000000..f91cbd3c58059 --- /dev/null +++ b/packages/kbn-tinymath/test/functions/cube.test.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { cube } = require('../../src/functions/cube.js'); + +describe('Cube', () => { + it('numbers', () => { + expect(cube(3)).toEqual(27); + expect(cube(-1)).toEqual(-1); + }); + + it('arrays', () => { + expect(cube([3, 4, 5])).toEqual([27, 64, 125]); + expect(cube([1, 2, 10])).toEqual([1, 8, 1000]); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/degtorad.test.js b/packages/kbn-tinymath/test/functions/degtorad.test.js new file mode 100644 index 0000000000000..8ce78851e7844 --- /dev/null +++ b/packages/kbn-tinymath/test/functions/degtorad.test.js @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { degtorad } = require('../../src/functions/degtorad.js'); + +describe('Degrees to Radians', () => { + it('numbers', () => { + expect(degtorad(0)).toEqual(0); + expect(degtorad(90)).toEqual(1.5707963267948966); + }); + + it('arrays', () => { + expect(degtorad([0, 90, 180, 360])).toEqual([ + 0, + 1.5707963267948966, + 3.141592653589793, + 6.283185307179586, + ]); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/divide.test.js b/packages/kbn-tinymath/test/functions/divide.test.js new file mode 100644 index 0000000000000..f3eea83c3fb80 --- /dev/null +++ b/packages/kbn-tinymath/test/functions/divide.test.js @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { divide } = require('../../src/functions/divide.js'); + +describe('Divide', () => { + it('number, number', () => { + expect(divide(10, 2)).toEqual(5); + expect(divide(0.1, 0.02)).toEqual(0.1 / 0.02); + }); + + it('array, number', () => { + expect(divide([10, 20, 30, 40], 10)).toEqual([1, 2, 3, 4]); + }); + + it('number, array', () => { + expect(divide(10, [1, 2, 5, 10])).toEqual([10, 5, 2, 1]); + }); + + it('array, array', () => { + expect(divide([11, 48, 60, 72], [1, 2, 3, 4])).toEqual([11, 24, 20, 18]); + }); + + it('array length mismatch', () => { + expect(() => divide([1, 2], [3])).toThrow('Array length mismatch'); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/exp.test.js b/packages/kbn-tinymath/test/functions/exp.test.js new file mode 100644 index 0000000000000..0bb25d772ae2c --- /dev/null +++ b/packages/kbn-tinymath/test/functions/exp.test.js @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { exp } = require('../../src/functions/exp.js'); + +describe('Exp', () => { + it('numbers', () => { + expect(exp(3)).toEqual(Math.exp(3)); + expect(exp(0)).toEqual(Math.exp(0)); + expect(exp(5)).toEqual(Math.exp(5)); + }); + + it('arrays', () => { + expect(exp([3, 4, 5])).toEqual([Math.exp(3), Math.exp(4), Math.exp(5)]); + expect(exp([1, 2, 10])).toEqual([Math.exp(1), Math.exp(2), Math.exp(10)]); + expect(exp([10])).toEqual([Math.exp(10)]); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/first.test.js b/packages/kbn-tinymath/test/functions/first.test.js new file mode 100644 index 0000000000000..c977f68117724 --- /dev/null +++ b/packages/kbn-tinymath/test/functions/first.test.js @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { first } = require('../../src/functions/first.js'); + +describe('First', () => { + it('numbers', () => { + expect(first(-10)).toEqual(-10); + expect(first(10)).toEqual(10); + }); + + it('arrays', () => { + expect(first([])).toEqual(undefined); + expect(first([-1])).toEqual(-1); + expect(first([-10, -20, -30, -40])).toEqual(-10); + expect(first([-13, 30, -90, 200])).toEqual(-13); + }); + + it('skips number validation', () => { + expect(first).toHaveProperty('skipNumberValidation', true); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/fix.test.js b/packages/kbn-tinymath/test/functions/fix.test.js new file mode 100644 index 0000000000000..59a71352ac680 --- /dev/null +++ b/packages/kbn-tinymath/test/functions/fix.test.js @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { fix } = require('../../src/functions/fix.js'); + +describe('Fix', () => { + it('numbers', () => { + expect(fix(-10.5)).toEqual(-10); + expect(fix(-10.1)).toEqual(-10); + expect(fix(10.9)).toEqual(10); + }); + + it('arrays', () => { + expect(fix([-10.5, -20.9, -30.1, -40.2])).toEqual([-10, -20, -30, -40]); + expect(fix([2.9, 5.1, 3.5, 4.3])).toEqual([2, 5, 3, 4]); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/floor.test.js b/packages/kbn-tinymath/test/functions/floor.test.js new file mode 100644 index 0000000000000..19f80e9bb7b06 --- /dev/null +++ b/packages/kbn-tinymath/test/functions/floor.test.js @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { floor } = require('../../src/functions/floor.js'); + +describe('Floor', () => { + it('numbers', () => { + expect(floor(-10.5)).toEqual(-11); + expect(floor(-10.1)).toEqual(-11); + expect(floor(10.9)).toEqual(10); + }); + + it('arrays', () => { + expect(floor([-10.5, -20.9, -30.1, -40.2])).toEqual([-11, -21, -31, -41]); + expect(floor([2.9, 5.1, 3.5, 4.3])).toEqual([2, 5, 3, 4]); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/last.test.js b/packages/kbn-tinymath/test/functions/last.test.js new file mode 100644 index 0000000000000..a333541b147ea --- /dev/null +++ b/packages/kbn-tinymath/test/functions/last.test.js @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { last } = require('../../src/functions/last.js'); + +describe('Last', () => { + it('numbers', () => { + expect(last(-10)).toEqual(-10); + expect(last(10)).toEqual(10); + }); + + it('arrays', () => { + expect(last([])).toEqual(undefined); + expect(last([-1])).toEqual(-1); + expect(last([-10, -20, -30, -40])).toEqual(-40); + expect(last([-13, 30, -90, 200])).toEqual(200); + }); + + it('skips number validation', () => { + expect(last).toHaveProperty('skipNumberValidation', true); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/log.test.js b/packages/kbn-tinymath/test/functions/log.test.js new file mode 100644 index 0000000000000..de142b997039b --- /dev/null +++ b/packages/kbn-tinymath/test/functions/log.test.js @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { log } = require('../../src/functions/log.js'); + +describe('Log', () => { + it('numbers', () => { + expect(log(1)).toEqual(Math.log(1)); + expect(log(3, 2)).toEqual(Math.log(3) / Math.log(2)); + expect(log(11, 3)).toEqual(Math.log(11) / Math.log(3)); + expect(log(42, 5)).toEqual(2.322344707681546); + }); + + it('arrays', () => { + expect(log([3, 4, 5], 3)).toEqual([ + Math.log(3) / Math.log(3), + Math.log(4) / Math.log(3), + Math.log(5) / Math.log(3), + ]); + expect(log([1, 2, 10], 10)).toEqual([ + Math.log(1) / Math.log(10), + Math.log(2) / Math.log(10), + Math.log(10) / Math.log(10), + ]); + }); + + it('number less than 1', () => { + expect(() => log(-1)).toThrow('Must be greater than 0'); + }); + + it('base out of range', () => { + expect(() => log(1, -1)).toThrow('Base out of range'); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/log10.test.js b/packages/kbn-tinymath/test/functions/log10.test.js new file mode 100644 index 0000000000000..e0edfaa8388f0 --- /dev/null +++ b/packages/kbn-tinymath/test/functions/log10.test.js @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { log10 } = require('../../src/functions/log10.js'); + +describe('Log10', () => { + it('numbers', () => { + expect(log10(1)).toEqual(Math.log(1) / Math.log(10)); + expect(log10(3)).toEqual(Math.log(3) / Math.log(10)); + expect(log10(11)).toEqual(Math.log(11) / Math.log(10)); + expect(log10(80)).toEqual(1.9030899869919433); + }); + + it('arrays', () => { + expect(log10([3, 4, 5], 3)).toEqual([ + Math.log(3) / Math.log(10), + Math.log(4) / Math.log(10), + Math.log(5) / Math.log(10), + ]); + expect(log10([1, 2, 10], 10)).toEqual([ + Math.log(1) / Math.log(10), + Math.log(2) / Math.log(10), + Math.log(10) / Math.log(10), + ]); + }); + + it('number less than 1', () => { + expect(() => log10(-1)).toThrow('Must be greater than 0'); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/max.test.js b/packages/kbn-tinymath/test/functions/max.test.js new file mode 100644 index 0000000000000..ab4de7b958e68 --- /dev/null +++ b/packages/kbn-tinymath/test/functions/max.test.js @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { max } = require('../../src/functions/max.js'); + +describe('Max', () => { + it('numbers', () => { + expect(max(1)).toEqual(1); + expect(max(10, 2, 5, 8)).toEqual(10); + expect(max(0.1, 0.2, 0.4, 0.3)).toEqual(0.4); + }); + + it('arrays & numbers', () => { + expect(max([88, 20, 30, 40], 60, [30, 10, 70, 90])).toEqual([88, 60, 70, 90]); + expect(max(10, [10, 20, 30, 40], [1, 2, 3, 4], 22)).toEqual([22, 22, 30, 40]); + }); + + it('arrays', () => { + expect(max([1, 2, 3, 4])).toEqual(4); + expect(max([6, 2, 3, 10], [11, 2, 5, 10])).toEqual([11, 2, 5, 10]); + expect(max([30, 55, 9, 4], [72, 24, 48, 10], [10, 20, 30, 40])).toEqual([72, 55, 48, 40]); + expect(max([11, 28, 60, 10], [1, 48, 3, -17])).toEqual([11, 48, 60, 10]); + }); + + it('array length mismatch', () => { + expect(() => max([1, 2], [3])).toThrow('Array length mismatch'); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/mean.test.js b/packages/kbn-tinymath/test/functions/mean.test.js new file mode 100644 index 0000000000000..6fb1c1fa18b98 --- /dev/null +++ b/packages/kbn-tinymath/test/functions/mean.test.js @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { mean } = require('../../src/functions/mean.js'); + +describe('Mean', () => { + it('numbers', () => { + expect(mean(1)).toEqual(1); + expect(mean(10, 2, 5, 8)).toEqual(25 / 4); + expect(mean(0.1, 0.2, 0.4, 0.3)).toEqual((0.1 + 0.2 + 0.3 + 0.4) / 4); + }); + + it('arrays & numbers', () => { + expect(mean([10, 20, 30, 40], 10, 20, 30)).toEqual([70 / 4, 80 / 4, 90 / 4, 100 / 4]); + expect(mean(10, [10, 20, 30, 40], [1, 2, 3, 4], 22)).toEqual([43 / 4, 54 / 4, 65 / 4, 76 / 4]); + }); + + it('arrays', () => { + expect(mean([1, 2, 3, 4])).toEqual(10 / 4); + expect(mean([1, 2, 3, 4], [1, 2, 5, 10])).toEqual([2 / 2, 4 / 2, 8 / 2, 14 / 2]); + expect(mean([1, 2, 3, 4], [1, 2, 5, 10], [10, 20, 30, 40])).toEqual([ + 12 / 3, + 24 / 3, + 38 / 3, + 54 / 3, + ]); + expect(mean([11, 48, 60, 72], [1, 2, 3, 4])).toEqual([12 / 2, 50 / 2, 63 / 2, 76 / 2]); + }); + + it('array length mismatch', () => { + expect(() => mean([1, 2], [3])).toThrow(); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/median.test.js b/packages/kbn-tinymath/test/functions/median.test.js new file mode 100644 index 0000000000000..e7dd56b4c6fc4 --- /dev/null +++ b/packages/kbn-tinymath/test/functions/median.test.js @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { median } = require('../../src/functions/median.js'); + +describe('Median', () => { + it('numbers', () => { + expect(median(1)).toEqual(1); + expect(median(10, 2, 5, 8)).toEqual((8 + 5) / 2); + expect(median(0.1, 0.2, 0.4, 0.3)).toEqual((0.2 + 0.3) / 2); + }); + + it('arrays & numbers', () => { + expect(median([10, 20, 30, 40], 10, 20, 30)).toEqual([15, 20, 25, 25]); + expect(median(10, [10, 20, 30, 40], [1, 2, 3, 4], 22)).toEqual([10, 15, 16, 16]); + }); + + it('arrays', () => { + expect(median([1, 2, 3, 4], [1, 2, 5, 10])).toEqual([1, 2, 4, 7]); + expect(median([1, 2, 3, 4], [1, 2, 5, 10], [10, 20, 30, 40])).toEqual([1, 2, 5, 10]); + expect(median([11, 48, 60, 72], [1, 2, 3, 4])).toEqual([12 / 2, 50 / 2, 63 / 2, 76 / 2]); + }); + + it('array length mismatch', () => { + expect(() => median([1, 2], [3])).toThrow(); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/min.test.js b/packages/kbn-tinymath/test/functions/min.test.js new file mode 100644 index 0000000000000..9612ce4274d11 --- /dev/null +++ b/packages/kbn-tinymath/test/functions/min.test.js @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { min } = require('../../src/functions/min.js'); + +describe('Min', () => { + it('numbers', () => { + expect(min(1)).toEqual(1); + expect(min(10, 2, 5, 8)).toEqual(2); + expect(min(0.1, 0.2, 0.4, 0.3)).toEqual(0.1); + }); + + it('arrays & numbers', () => { + expect(min([88, 20, 30, 100], 60, [30, 10, 70, 90])).toEqual([30, 10, 30, 60]); + expect(min([50, 20, 3, 40], 10, [13, 2, 34, 4], 22)).toEqual([10, 2, 3, 4]); + expect(min(10, [50, 20, 3, 40], [13, 2, 34, 4], 22)).toEqual([10, 2, 3, 4]); + }); + + it('arrays', () => { + expect(min([1, 2, 3, 4])).toEqual(1); + expect(min([6, 2, 30, 10], [11, 2, 5, 15])).toEqual([6, 2, 5, 10]); + expect(min([30, 55, 9, 4], [72, 24, 48, 10], [10, 20, 30, 40])).toEqual([10, 20, 9, 4]); + expect(min([11, 28, 60, 10], [1, 48, 3, -17])).toEqual([1, 28, 3, -17]); + }); + + it('array length mismatch', () => { + expect(() => min([1, 2], [3])).toThrow('Array length mismatch'); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/mod.test.js b/packages/kbn-tinymath/test/functions/mod.test.js new file mode 100644 index 0000000000000..ba3fc35b7e70c --- /dev/null +++ b/packages/kbn-tinymath/test/functions/mod.test.js @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { mod } = require('../../src/functions/mod.js'); + +describe('Mod', () => { + it('number, number', () => { + expect(mod(13, 8)).toEqual(5); + expect(mod(0.1, 0.02)).toEqual(0.1 % 0.02); + }); + + it('array, number', () => { + expect(mod([13, 26, 34, 42], 10)).toEqual([3, 6, 4, 2]); + }); + + it('number, array', () => { + expect(mod(10, [3, 7, 2, 4])).toEqual([1, 3, 0, 2]); + }); + + it('array, array', () => { + expect(mod([11, 48, 60, 72], [4, 13, 9, 5])).toEqual([3, 9, 6, 2]); + }); + + it('array length mismatch', () => { + expect(() => mod([1, 2], [3])).toThrow('Array length mismatch'); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/mode.test.js b/packages/kbn-tinymath/test/functions/mode.test.js new file mode 100644 index 0000000000000..6f33140d41ef0 --- /dev/null +++ b/packages/kbn-tinymath/test/functions/mode.test.js @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { mode } = require('../../src/functions/mode.js'); + +describe('Mode', () => { + it('numbers', () => { + expect(mode(1)).toEqual(1); + expect(mode(10, 2, 5, 8)).toEqual([2, 5, 8, 10]); + expect(mode(0.1, 0.2, 0.4, 0.3)).toEqual([0.1, 0.2, 0.3, 0.4]); + expect(mode(1, 1, 2, 3, 1, 4, 3, 2, 4)).toEqual([1]); + }); + + it('arrays & numbers', () => { + expect(mode([10, 20, 30, 40], 10, 20, 30)).toEqual([[10], [20], [30], [10, 20, 30, 40]]); + expect(mode([1, 2, 3, 4], 2, 3, [3, 2, 4, 3])).toEqual([[3], [2], [3], [3]]); + }); + + it('arrays', () => { + expect(mode([1, 2, 3, 4], [1, 2, 5, 10])).toEqual([[1], [2], [3, 5], [4, 10]]); + expect(mode([1, 2, 3, 4], [1, 2, 1, 2], [2, 3, 2, 3], [4, 3, 2, 3])).toEqual([ + [1], + [2, 3], + [2], + [3], + ]); + }); + + it('array length mismatch', () => { + expect(() => mode([1, 2], [3])).toThrow(); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/multiply.test.js b/packages/kbn-tinymath/test/functions/multiply.test.js new file mode 100644 index 0000000000000..f3a35d1f45695 --- /dev/null +++ b/packages/kbn-tinymath/test/functions/multiply.test.js @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { multiply } = require('../../src/functions/multiply.js'); + +describe('Multiply', () => { + it('number, number', () => { + expect(multiply(10, 2)).toEqual(20); + expect(multiply(0.1, 0.2)).toEqual(0.1 * 0.2); + }); + + it('array, number', () => { + expect(multiply([10, 20, 30, 40], 10)).toEqual([100, 200, 300, 400]); + }); + + it('number, array', () => { + expect(multiply(10, [1, 2, 5, 10])).toEqual([10, 20, 50, 100]); + }); + + it('array, array', () => { + expect(multiply([11, 48, 60, 72], [1, 2, 3, 4])).toEqual([11, 96, 180, 288]); + }); + + it('array length mismatch', () => { + expect(() => multiply([1, 2], [3])).toThrow('Array length mismatch'); + }); +}); diff --git a/src/plugins/maps_oss/public/index.ts b/packages/kbn-tinymath/test/functions/pi.test.js similarity index 70% rename from src/plugins/maps_oss/public/index.ts rename to packages/kbn-tinymath/test/functions/pi.test.js index 1d27dc4b6d996..7f1cdd019401d 100644 --- a/src/plugins/maps_oss/public/index.ts +++ b/packages/kbn-tinymath/test/functions/pi.test.js @@ -6,6 +6,10 @@ * Public License, v 1. */ -import { MapsOSSPlugin } from './plugin'; +const { pi } = require('../../src/functions/pi.js'); -export const plugin = () => new MapsOSSPlugin(); +describe('PI', () => { + it('constant', () => { + expect(pi()).toEqual(Math.PI); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/pow.test.js b/packages/kbn-tinymath/test/functions/pow.test.js new file mode 100644 index 0000000000000..05193aa2177a6 --- /dev/null +++ b/packages/kbn-tinymath/test/functions/pow.test.js @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { pow } = require('../../src/functions/pow.js'); + +describe('Pow', () => { + it('numbers', () => { + expect(pow(3, 2)).toEqual(9); + expect(pow(-1, -1)).toEqual(-1); + expect(pow(5, 0)).toEqual(1); + }); + + it('arrays', () => { + expect(pow([3, 4, 5], 3)).toEqual([Math.pow(3, 3), Math.pow(4, 3), Math.pow(5, 3)]); + expect(pow([1, 2, 10], 10)).toEqual([Math.pow(1, 10), Math.pow(2, 10), Math.pow(10, 10)]); + }); + + it('missing exponent', () => { + expect(() => pow(1)).toThrow('Missing exponent'); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/radtodeg.test.js b/packages/kbn-tinymath/test/functions/radtodeg.test.js new file mode 100644 index 0000000000000..0b97d3d2695be --- /dev/null +++ b/packages/kbn-tinymath/test/functions/radtodeg.test.js @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { radtodeg } = require('../../src/functions/radtodeg.js'); + +describe('Radians to Degrees', () => { + it('numbers', () => { + expect(radtodeg(0)).toEqual(0); + expect(radtodeg(1.5707963267948966)).toEqual(90); + }); + + it('arrays', () => { + expect(radtodeg([0, 1.5707963267948966, 3.141592653589793, 6.283185307179586])).toEqual([ + 0, + 90, + 180, + 360, + ]); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/random.test.js b/packages/kbn-tinymath/test/functions/random.test.js new file mode 100644 index 0000000000000..2b259f2b84771 --- /dev/null +++ b/packages/kbn-tinymath/test/functions/random.test.js @@ -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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { random } = require('../../src/functions/random.js'); + +describe('Random', () => { + it('numbers', () => { + const random1 = random(); + expect(random1).toBeGreaterThanOrEqual(0); + expect(random1).toBeLessThan(1); + expect(random(0)).toEqual(0); + const random3 = random(3); + expect(random3).toBeGreaterThanOrEqual(0); + expect(random3).toBeLessThan(3); + const random100 = random(-100, 100); + expect(random100).toBeGreaterThanOrEqual(-100); + expect(random100).toBeLessThan(100); + expect(random(1, 1)).toEqual(1); + expect(random(100, 100)).toEqual(100); + }); + + it('min greater than max', () => { + expect(() => random(-1)).toThrow('Min is greater than max'); + expect(() => random(3, 1)).toThrow('Min is greater than max'); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/range.test.js b/packages/kbn-tinymath/test/functions/range.test.js new file mode 100644 index 0000000000000..920986d5e1368 --- /dev/null +++ b/packages/kbn-tinymath/test/functions/range.test.js @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { range } = require('../../src/functions/range.js'); + +describe('Range', () => { + it('numbers', () => { + expect(range(1)).toEqual(0); + expect(range(10, 2, 5, 8)).toEqual(8); + expect(range(0.1, 0.2, 0.4, 0.3)).toEqual(0.4 - 0.1); + }); + + it('arrays & numbers', () => { + expect(range([88, 20, 30, 40], 60, [30, 10, 70, 90])).toEqual([58, 50, 40, 50]); + expect(range(10, [10, 20, 30, 40], [1, 2, 3, 4], 22)).toEqual([21, 20, 27, 36]); + }); + + it('arrays', () => { + expect(range([1, 2, 3, 4])).toEqual(3); + expect(range([6, 2, 3, 10], [11, 2, 5, 10])).toEqual([5, 0, 2, 0]); + expect(range([30, 55, 9, 4], [72, 24, 48, 10], [10, 20, 30, 40])).toEqual([62, 35, 39, 36]); + expect(range([11, 28, 60, 10], [1, 48, 3, -17])).toEqual([10, 20, 57, 27]); + }); + + it('array length mismatch', () => { + expect(() => range([1, 2], [3])).toThrow(); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/round.test.js b/packages/kbn-tinymath/test/functions/round.test.js new file mode 100644 index 0000000000000..bea0a9a8377d7 --- /dev/null +++ b/packages/kbn-tinymath/test/functions/round.test.js @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { round } = require('../../src/functions/round.js'); + +describe('Round', () => { + it('numbers', () => { + expect(round(-10.51)).toEqual(-11); + expect(round(-10.1, 2)).toEqual(-10.1); + expect(round(10.93745987, 4)).toEqual(10.9375); + }); + + it('arrays', () => { + expect(round([-10.51, -20.9, -30.1, -40.2])).toEqual([-11, -21, -30, -40]); + expect(round([2.9234, 5.1234, 3.5234, 4.49234324], 2)).toEqual([2.92, 5.12, 3.52, 4.49]); + }); +}); diff --git a/src/plugins/maps_oss/config.ts b/packages/kbn-tinymath/test/functions/sin.test.js similarity index 56% rename from src/plugins/maps_oss/config.ts rename to packages/kbn-tinymath/test/functions/sin.test.js index 58c50f0104f46..35a37abe35a68 100644 --- a/src/plugins/maps_oss/config.ts +++ b/packages/kbn-tinymath/test/functions/sin.test.js @@ -6,10 +6,15 @@ * Public License, v 1. */ -import { schema, TypeOf } from '@kbn/config-schema'; +const { sin } = require('../../src/functions/sin.js'); -export const configSchema = schema.object({ - enabled: schema.boolean({ defaultValue: true }), -}); +describe('Sine', () => { + it('numbers', () => { + expect(sin(0)).toEqual(0); + expect(sin(1.5707963267948966)).toEqual(1); + }); -export type ConfigSchema = TypeOf; + it('arrays', () => { + expect(sin([0, 1.5707963267948966])).toEqual([0, 1]); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/size.test.js b/packages/kbn-tinymath/test/functions/size.test.js new file mode 100644 index 0000000000000..b4db587f30230 --- /dev/null +++ b/packages/kbn-tinymath/test/functions/size.test.js @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { size } = require('../../src/functions/size.js'); + +describe('Size (also Count)', () => { + it('array', () => { + expect(size([])).toEqual(0); + expect(size([10, 20, 30, 40])).toEqual(4); + }); + + it('not an array', () => { + expect(() => size(null)).toThrow('Must pass an array'); + expect(() => size(undefined)).toThrow('Must pass an array'); + expect(() => size('string')).toThrow('Must pass an array'); + expect(() => size(10)).toThrow('Must pass an array'); + expect(() => size(true)).toThrow('Must pass an array'); + expect(() => size({})).toThrow('Must pass an array'); + expect(() => size(function () {})).toThrow('Must pass an array'); + }); + + it('skips number validation', () => { + expect(size).toHaveProperty('skipNumberValidation', true); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/sqrt.test.js b/packages/kbn-tinymath/test/functions/sqrt.test.js new file mode 100644 index 0000000000000..a170140598d06 --- /dev/null +++ b/packages/kbn-tinymath/test/functions/sqrt.test.js @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { sqrt } = require('../../src/functions/sqrt.js'); + +describe('Sqrt', () => { + it('numbers', () => { + expect(sqrt(9)).toEqual(3); + expect(sqrt(0)).toEqual(0); + expect(sqrt(30)).toEqual(5.477225575051661); + }); + + it('arrays', () => { + expect(sqrt([49, 64, 81])).toEqual([7, 8, 9]); + expect(sqrt([1, 4, 100])).toEqual([1, 2, 10]); + }); + + it('Invalid negative number', () => { + expect(() => sqrt(-1)).toThrow('Unable find the square root of a negative number'); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/square.test.js b/packages/kbn-tinymath/test/functions/square.test.js new file mode 100644 index 0000000000000..3b91a5f79c8d3 --- /dev/null +++ b/packages/kbn-tinymath/test/functions/square.test.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { square } = require('../../src/functions/square.js'); + +describe('Square', () => { + it('numbers', () => { + expect(square(3)).toEqual(9); + expect(square(-1)).toEqual(1); + }); + + it('arrays', () => { + expect(square([3, 4, 5])).toEqual([9, 16, 25]); + expect(square([1, 2, 10])).toEqual([1, 4, 100]); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/subtract.test.js b/packages/kbn-tinymath/test/functions/subtract.test.js new file mode 100644 index 0000000000000..9cdc1fb85a562 --- /dev/null +++ b/packages/kbn-tinymath/test/functions/subtract.test.js @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { subtract } = require('../../src/functions/subtract.js'); + +describe('Subtract', () => { + it('number, number', () => { + expect(subtract(10, 2)).toEqual(8); + expect(subtract(0.1, 0.2)).toEqual(0.1 - 0.2); + }); + + it('array, number', () => { + expect(subtract([10, 20, 30, 40], 10)).toEqual([0, 10, 20, 30]); + }); + + it('number, array', () => { + expect(subtract(10, [1, 2, 5, 10])).toEqual([9, 8, 5, 0]); + }); + + it('array, array', () => { + expect(subtract([11, 48, 60, 72], [1, 2, 3, 4])).toEqual([10, 46, 57, 68]); + }); + + it('array length mismatch', () => { + expect(() => subtract([1, 2], [3])).toThrow('Array length mismatch'); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/sum.test.js b/packages/kbn-tinymath/test/functions/sum.test.js new file mode 100644 index 0000000000000..a7d8c3d135253 --- /dev/null +++ b/packages/kbn-tinymath/test/functions/sum.test.js @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { sum } = require('../../src/functions/sum.js'); + +describe('Sum', () => { + it('numbers', () => { + expect(sum(10, 2, 5, 8)).toEqual(25); + expect(sum(0.1, 0.2, 0.4, 0.3)).toEqual(0.1 + 0.2 + 0.3 + 0.4); + }); + + it('arrays & numbers', () => { + expect(sum([10, 20, 30, 40], 10, 20, 30)).toEqual(160); + expect(sum([10, 20, 30, 40], 10, [1, 2, 3], 22)).toEqual(138); + }); + + it('arrays', () => { + expect(sum([1, 2, 3, 4], [1, 2, 5, 10])).toEqual(28); + expect(sum([1, 2, 3, 4], [1, 2, 5, 10], [10, 20, 30, 40])).toEqual(128); + expect(sum([11, 48, 60, 72], [1, 2, 3, 4])).toEqual(201); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/tan.test.js b/packages/kbn-tinymath/test/functions/tan.test.js new file mode 100644 index 0000000000000..ba6960c0c1d8a --- /dev/null +++ b/packages/kbn-tinymath/test/functions/tan.test.js @@ -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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { tan } = require('../../src/functions/tan.js'); + +describe('Tangent', () => { + it('numbers', () => { + expect(tan(0)).toEqual(0); + expect(tan(1)).toEqual(1.5574077246549023); + }); + + it('arrays', () => { + expect(tan([0, 1])).toEqual([0, 1.5574077246549023]); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/transpose.test.js b/packages/kbn-tinymath/test/functions/transpose.test.js new file mode 100644 index 0000000000000..eb0b8d0c7a0e2 --- /dev/null +++ b/packages/kbn-tinymath/test/functions/transpose.test.js @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { transpose } = require('../../src/functions/lib/transpose'); + +describe('transpose', () => { + it('2D arrays', () => { + expect( + transpose( + [ + [1, 2], + [3, 4], + [5, 6], + ], + 0 + ) + ).toEqual([ + [1, 3, 5], + [2, 4, 6], + ]); + expect(transpose([10, 20, [10, 20, 30, 40], 30], 2)).toEqual([ + [10, 20, 10, 30], + [10, 20, 20, 30], + [10, 20, 30, 30], + [10, 20, 40, 30], + ]); + expect(transpose([4, [1, 9], [3, 5]], 1)).toEqual([ + [4, 1, 3], + [4, 9, 5], + ]); + }); + + it('array length mismatch', () => { + expect(() => transpose([[1], [2, 3]], 0)).toThrow('Array length mismatch'); + }); +}); diff --git a/packages/kbn-tinymath/test/functions/unique.test.js b/packages/kbn-tinymath/test/functions/unique.test.js new file mode 100644 index 0000000000000..d58c190876e2c --- /dev/null +++ b/packages/kbn-tinymath/test/functions/unique.test.js @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +const { unique } = require('../../src/functions/unique.js'); + +describe('Unique', () => { + it('numbers', () => { + expect(unique(1)).toEqual(1); + expect(unique(10000)).toEqual(1); + }); + + it('arrays', () => { + expect(unique([])).toEqual(0); + expect(unique([-10, -20, -30, -40])).toEqual(4); + expect(unique([-13, 30, -90, 200])).toEqual(4); + expect(unique([1, 2, 3, 4, 2, 2, 2, 3, 4, 2, 4, 5, 2, 1, 4, 2])).toEqual(5); + }); + + it('skips number validation', () => { + expect(unique).toHaveProperty('skipNumberValidation', true); + }); +}); diff --git a/packages/kbn-tinymath/test/library.test.js b/packages/kbn-tinymath/test/library.test.js new file mode 100644 index 0000000000000..7569cf90b2e35 --- /dev/null +++ b/packages/kbn-tinymath/test/library.test.js @@ -0,0 +1,273 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/* + TODO: These tests are wildly imcomplete + Need tests for spacing, etc +*/ + +const { evaluate, parse } = require('..'); + +describe('Parser', () => { + describe('Numbers', () => { + it('integers', () => { + expect(parse('10')).toEqual(10); + }); + + it('floats', () => { + expect(parse('10.5')).toEqual(10.5); + }); + + it('negatives', () => { + expect(parse('-10')).toEqual(-10); + expect(parse('-10.5')).toEqual(-10.5); + }); + }); + + describe('Variables', () => { + it('strings', () => { + expect(parse('f')).toEqual('f'); + expect(parse('foo')).toEqual('foo'); + }); + + it('allowed characters', () => { + expect(parse('_foo')).toEqual('_foo'); + expect(parse('@foo')).toEqual('@foo'); + expect(parse('.foo')).toEqual('.foo'); + expect(parse('-foo')).toEqual('-foo'); + expect(parse('_foo0')).toEqual('_foo0'); + expect(parse('@foo0')).toEqual('@foo0'); + expect(parse('.foo0')).toEqual('.foo0'); + expect(parse('-foo0')).toEqual('-foo0'); + }); + }); + + describe('quoted variables', () => { + it('strings with double quotes', () => { + expect(parse('"foo"')).toEqual('foo'); + expect(parse('"f b"')).toEqual('f b'); + expect(parse('"foo bar"')).toEqual('foo bar'); + expect(parse('"foo bar fizz buzz"')).toEqual('foo bar fizz buzz'); + expect(parse('"foo bar baby"')).toEqual('foo bar baby'); + }); + + it('strings with single quotes', () => { + /* eslint-disable prettier/prettier */ + expect(parse("'foo'")).toEqual('foo'); + expect(parse("'f b'")).toEqual('f b'); + expect(parse("'foo bar'")).toEqual('foo bar'); + expect(parse("'foo bar fizz buzz'")).toEqual('foo bar fizz buzz'); + expect(parse("'foo bar baby'")).toEqual('foo bar baby'); + /* eslint-enable prettier/prettier */ + }); + + it('allowed characters', () => { + expect(parse('"_foo bar"')).toEqual('_foo bar'); + expect(parse('"@foo bar"')).toEqual('@foo bar'); + expect(parse('".foo bar"')).toEqual('.foo bar'); + expect(parse('"-foo bar"')).toEqual('-foo bar'); + expect(parse('"_foo0 bar1"')).toEqual('_foo0 bar1'); + expect(parse('"@foo0 bar1"')).toEqual('@foo0 bar1'); + expect(parse('".foo0 bar1"')).toEqual('.foo0 bar1'); + expect(parse('"-foo0 bar1"')).toEqual('-foo0 bar1'); + }); + + it('invalid characters in double quotes', () => { + const check = (str) => () => parse(str); + expect(check('" foo bar"')).toThrow('but "\\"" found'); + expect(check('"foo bar "')).toThrow('but "\\"" found'); + expect(check('"0foo"')).toThrow('but "\\"" found'); + expect(check('" foo bar"')).toThrow('but "\\"" found'); + expect(check('"foo bar "')).toThrow('but "\\"" found'); + expect(check('"0foo"')).toThrow('but "\\"" found'); + }); + + it('invalid characters in single quotes', () => { + const check = (str) => () => parse(str); + /* eslint-disable prettier/prettier */ + expect(check("' foo bar'")).toThrow('but "\'" found'); + expect(check("'foo bar '")).toThrow('but "\'" found'); + expect(check("'0foo'")).toThrow('but "\'" found'); + expect(check("' foo bar'")).toThrow('but "\'" found'); + expect(check("'foo bar '")).toThrow('but "\'" found'); + expect(check("'0foo'")).toThrow('but "\'" found'); + /* eslint-enable prettier/prettier */ + }); + }); + + describe('Functions', () => { + it('no arguments', () => { + expect(parse('foo()')).toEqual({ name: 'foo', args: [] }); + }); + + it('arguments', () => { + expect(parse('foo(5,10)')).toEqual({ name: 'foo', args: [5, 10] }); + }); + + it('arguments with strings', () => { + expect(parse('foo("string with spaces")')).toEqual({ + name: 'foo', + args: ['string with spaces'], + }); + + /* eslint-disable prettier/prettier */ + expect(parse("foo('string with spaces')")).toEqual({ + name: 'foo', + args: ['string with spaces'], + }); + /* eslint-enable prettier/prettier */ + }); + }); + + it('Missing expression', () => { + expect(() => parse(undefined)).toThrow('Missing expression'); + expect(() => parse(null)).toThrow('Missing expression'); + }); + + it('Failed parse', () => { + expect(() => parse('')).toThrow('Failed to parse expression'); + }); + + it('Not a string', () => { + expect(() => parse(3)).toThrow('Expression must be a string'); + }); +}); + +describe('Evaluate', () => { + it('numbers', () => { + expect(evaluate('10')).toEqual(10); + }); + + it('variables', () => { + expect(evaluate('foo', { foo: 10 })).toEqual(10); + expect(evaluate('bar', { bar: [1, 2] })).toEqual([1, 2]); + }); + + it('variables with spaces', () => { + expect(evaluate('"foo bar"', { 'foo bar': 10 })).toEqual(10); + expect(evaluate('"key with many spaces in it"', { 'key with many spaces in it': 10 })).toEqual( + 10 + ); + }); + + it('valiables with dots', () => { + expect(evaluate('foo.bar', { 'foo.bar': 20 })).toEqual(20); + expect(evaluate('"is.null"', { 'is.null': null })).toEqual(null); + expect(evaluate('"is.false"', { 'is.null': null, 'is.false': false })).toEqual(false); + expect(evaluate('"with space.val"', { 'with space.val': 42 })).toEqual(42); + }); + + it('variables with dot notation', () => { + expect(evaluate('foo.bar', { foo: { bar: 20 } })).toEqual(20); + expect(evaluate('foo.bar[0].baz', { foo: { bar: [{ baz: 30 }, { beer: 40 }] } })).toEqual(30); + expect(evaluate('"is.false"', { is: { null: null, false: false } })).toEqual(false); + }); + + it('equations', () => { + expect(evaluate('3 + 4')).toEqual(7); + expect(evaluate('10 - 2')).toEqual(8); + expect(evaluate('8 + 6 / 3')).toEqual(10); + expect(evaluate('10 * (1 + 2)')).toEqual(30); + expect(evaluate('(3 - 4) * 10')).toEqual(-10); + expect(evaluate('-1 - -12')).toEqual(11); + expect(evaluate('5/20')).toEqual(0.25); + expect(evaluate('1 + 1 + 2 + 3 + 12')).toEqual(19); + expect(evaluate('100 / 10 / 10')).toEqual(1); + }); + + it('equations with functions', () => { + expect(evaluate('3 + multiply(10, 4)')).toEqual(43); + expect(evaluate('3 + multiply(10, 4, 5)')).toEqual(203); + }); + + it('equations with trigonometry', () => { + expect(evaluate('pi()')).toEqual(Math.PI); + expect(evaluate('sin(degtorad(0))')).toEqual(0); + expect(evaluate('sin(degtorad(180))')).toEqual(1.2246467991473532e-16); + expect(evaluate('cos(degtorad(0))')).toEqual(1); + expect(evaluate('cos(degtorad(180))')).toEqual(-1); + expect(evaluate('tan(degtorad(0))')).toEqual(0); + expect(evaluate('tan(degtorad(180))')).toEqual(-1.2246467991473532e-16); + }); + + it('equations with variables', () => { + expect(evaluate('3 + foo', { foo: 5 })).toEqual(8); + expect(evaluate('3 + foo', { foo: [5, 10] })).toEqual([8, 13]); + expect(evaluate('3 + foo', { foo: 5 })).toEqual(8); + expect(evaluate('sum(foo)', { foo: [5, 10, 15] })).toEqual(30); + expect(evaluate('90 / sum(foo)', { foo: [5, 10, 15] })).toEqual(3); + expect(evaluate('multiply(foo, bar)', { foo: [1, 2, 3], bar: [4, 5, 6] })).toEqual([4, 10, 18]); + }); + + it('equations with quoted variables', () => { + expect(evaluate('"b" * 7', { b: 3 })).toEqual(21); + expect(evaluate('"space name" * 2', { 'space name': [1, 2, 21] })).toEqual([2, 4, 42]); + expect(evaluate('sum("space name")', { 'space name': [1, 2, 21] })).toEqual(24); + }); + + it('equations with injected functions', () => { + expect( + evaluate( + 'plustwo(foo)', + { foo: 5 }, + { + plustwo: function (a) { + return a + 2; + }, + } + ) + ).toEqual(7); + expect( + evaluate('negate(1)', null, { + negate: function (a) { + return -a; + }, + }) + ).toEqual(-1); + expect( + evaluate('stringify(2)', null, { + stringify: function (a) { + return '' + a; + }, + }) + ).toEqual('2'); + }); + + it('equations with arrays using special operator functions', () => { + expect(evaluate('foo + bar', { foo: [1, 2, 3], bar: [4, 5, 6] })).toEqual([5, 7, 9]); + expect(evaluate('foo - bar', { foo: [1, 2, 3], bar: [4, 5, 6] })).toEqual([-3, -3, -3]); + expect(evaluate('foo * bar', { foo: [1, 2, 3], bar: [4, 5, 6] })).toEqual([4, 10, 18]); + expect(evaluate('foo / bar', { foo: [1, 2, 3], bar: [4, 5, 6] })).toEqual([ + 1 / 4, + 2 / 5, + 3 / 6, + ]); + }); + + it('missing expression', () => { + expect(() => evaluate('')).toThrow('Failed to parse expression'); + }); + + it('missing referenced scope when used in injected function', () => { + expect(() => + evaluate('increment(foo)', null, { + increment: function (a) { + return a + 1; + }, + }) + ).toThrow('Unknown variable: foo'); + }); + + it('invalid context datatypes', () => { + expect(evaluate('mean(foo)', { foo: [true, true, false] })).toBeNaN(); + expect(evaluate('mean(foo + bar)', { foo: [true, true, false], bar: [1, 2, 3] })).toBeNaN(); + expect(evaluate('mean(foo)', { foo: ['dog', 'cat', 'mouse'] })).toBeNaN(); + expect(evaluate('mean(foo + 2)', { foo: ['dog', 'cat', 'mouse'] })).toBeNaN(); + expect(evaluate('foo + bar', { foo: NaN, bar: [4, 5, 6] })).toBeNaN(); + }); +}); diff --git a/src/core/README.md b/src/core/README.md index e195bf30c054c..c73c6aa56bfd0 100644 --- a/src/core/README.md +++ b/src/core/README.md @@ -28,8 +28,10 @@ Even though the new validation system provided by the `core` is also based on Jo rules tailored to our needs (e.g. `byteSize`, `duration` etc.). That means that config values that were previously accepted by the "legacy" Kibana may be rejected by the `core` now. -Even though `core` has its own logging system it doesn't output log records directly (e.g. to file or terminal), but instead -forward them to the "legacy" Kibana so that they look the same as the rest of the log records throughout Kibana. +### Logging +`core` has its own [logging system](./server/logging/README.md) and will output log records directly (e.g. to file or terminal) when configured. When no +specific configuration is provided, logs are forwarded to the "legacy" Kibana so that they look the same as the rest of the +log records throughout Kibana. ## Core API Review To provide a stable API for plugin developers, it is important that the Core Public and Server API's are stable and diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index b82254e5a1416..c732fc7823b62 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -46,6 +46,11 @@ export class DocLinksService { auditbeat: { base: `${ELASTIC_WEBSITE_URL}guide/en/beats/auditbeat/${DOC_LINK_VERSION}`, }, + enterpriseSearch: { + base: `${ELASTIC_WEBSITE_URL}guide/en/enterprise-search/${DOC_LINK_VERSION}`, + appSearchBase: `${ELASTIC_WEBSITE_URL}guide/en/app-search/${DOC_LINK_VERSION}`, + workplaceSearchBase: `${ELASTIC_WEBSITE_URL}guide/en/workplace-search/${DOC_LINK_VERSION}`, + }, metricbeat: { base: `${ELASTIC_WEBSITE_URL}guide/en/beats/metricbeat/${DOC_LINK_VERSION}`, }, @@ -260,6 +265,11 @@ export interface DocLinksStart { readonly metricbeat: { readonly base: string; }; + readonly enterpriseSearch: { + readonly base: string; + readonly appSearchBase: string; + readonly workplaceSearchBase: string; + }; readonly heartbeat: { readonly base: string; }; diff --git a/src/core/public/public.api.md b/src/core/public/public.api.md index 52fc8fbf33910..0097127924a5c 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -483,6 +483,11 @@ export interface DocLinksStart { readonly metricbeat: { readonly base: string; }; + readonly enterpriseSearch: { + readonly base: string; + readonly appSearchBase: string; + readonly workplaceSearchBase: string; + }; readonly heartbeat: { readonly base: string; }; @@ -1502,6 +1507,7 @@ export interface UiSettingsParams { requiresPageReload?: boolean; // (undocumented) schema: Type; + sensitive?: boolean; type?: UiSettingsType; // (undocumented) validation?: ImageValidation | StringValidation; diff --git a/src/core/server/config/deprecation/core_deprecations.test.ts b/src/core/server/config/deprecation/core_deprecations.test.ts index a7c6a63826523..a791362d9166f 100644 --- a/src/core/server/config/deprecation/core_deprecations.test.ts +++ b/src/core/server/config/deprecation/core_deprecations.test.ts @@ -236,4 +236,24 @@ describe('core deprecations', () => { ).toEqual([`worker-src blob:`]); }); }); + + describe('logging.events.ops', () => { + it('warns when ops events are used', () => { + const { messages } = applyCoreDeprecations({ + logging: { events: { ops: '*' } }, + }); + expect(messages).toMatchInlineSnapshot(` + Array [ + "\\"logging.events.ops\\" has been deprecated and will be removed in 8.0. To access ops data moving forward, please enable debug logs for the \\"metrics.ops\\" context in your logging configuration.", + ] + `); + }); + + it('does not warn when other events are configured', () => { + const { messages } = applyCoreDeprecations({ + logging: { events: { log: '*' } }, + }); + expect(messages).toEqual([]); + }); + }); }); diff --git a/src/core/server/config/deprecation/core_deprecations.ts b/src/core/server/config/deprecation/core_deprecations.ts index 6f6e6c3e0522e..23a3518cd8eb6 100644 --- a/src/core/server/config/deprecation/core_deprecations.ts +++ b/src/core/server/config/deprecation/core_deprecations.ts @@ -103,6 +103,17 @@ const mapManifestServiceUrlDeprecation: ConfigDeprecation = (settings, fromPath, return settings; }; +const opsLoggingEventDeprecation: ConfigDeprecation = (settings, fromPath, log) => { + if (has(settings, 'logging.events.ops')) { + log( + '"logging.events.ops" has been deprecated and will be removed ' + + 'in 8.0. To access ops data moving forward, please enable debug logs for the ' + + '"metrics.ops" context in your logging configuration.' + ); + } + return settings; +}; + export const coreDeprecationProvider: ConfigDeprecationProvider = ({ rename, unusedFromRoot }) => [ unusedFromRoot('savedObjects.indexCheckTimeout'), unusedFromRoot('server.xsrf.token'), @@ -137,4 +148,5 @@ export const coreDeprecationProvider: ConfigDeprecationProvider = ({ rename, unu rewriteBasePathDeprecation, cspRulesDeprecation, mapManifestServiceUrlDeprecation, + opsLoggingEventDeprecation, ]; diff --git a/src/core/server/logging/README.md b/src/core/server/logging/README.md index 8cb704f09ce8c..cc2b6230d2d33 100644 --- a/src/core/server/logging/README.md +++ b/src/core/server/logging/README.md @@ -312,6 +312,9 @@ logging: - context: telemetry level: all appenders: [json-file-appender] + - context: metrics.ops + level: debug + appenders: [console] ``` Here is what we get with the config above: @@ -324,6 +327,7 @@ Here is what we get with the config above: | server | console, file | fatal | | optimize | console | error | | telemetry | json-file-appender | all | +| metrics.ops | console | debug | The `root` logger has a dedicated configuration node since this context is special and should always exist. By @@ -341,7 +345,25 @@ Or disable logging entirely with `off`: ```yaml logging.root.level: off ``` +### Dedicated loggers + +The `metrics.ops` logger is configured with `debug` level and will automatically output sample system and process information at a regular interval. +The metrics that are logged are a subset of the data collected and are formatted in the log message as follows: + +| Ops formatted log property | Location in metrics service | Log units +| :------------------------- | :-------------------------- | :-------------------------- | +| memory | process.memory.heap.used_in_bytes | [depends on the value](http://numeraljs.com/#format), typically MB or GB | +| uptime | process.uptime_in_millis | HH:mm:ss | +| load | os.load | [ "load for the last 1 min" "load for the last 5 min" "load for the last 15 min"] | +| delay | process.event_loop_delay | ms | + +The log interval is the same as the interval at which system and process information is refreshed and is configurable under `ops.interval`: + +```yaml +ops.interval: 5000 +``` +The minimum interval is 100ms and defaults to 5000ms. ## Usage Usage is very straightforward, one should just get a logger for a specific context and use it to log messages with @@ -478,4 +500,4 @@ TBD | meta | separate property `"meta": {"to": "v8"}` | merged in log record `{... "to": "v8"}` | | pid | `pid: 12345` | `pid: 12345` | | type | N/A | `type: log` | -| error | `{ message, name, stack }` | `{ message, name, stack, code, signal }` | \ No newline at end of file +| error | `{ message, name, stack }` | `{ message, name, stack, code, signal }` | diff --git a/src/core/server/logging/ecs.ts b/src/core/server/logging/ecs.ts new file mode 100644 index 0000000000000..0dbc403fca0b2 --- /dev/null +++ b/src/core/server/logging/ecs.ts @@ -0,0 +1,90 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +/** + * Typings for some ECS fields which core uses internally. + * These are not a complete set of ECS typings and should not + * be used externally; the only types included here are ones + * currently used in core. + * + * @internal + */ + +export interface EcsOpsMetricsEvent { + /** + * These typings were written as of ECS 1.7.0. + * Don't change this value without checking the rest + * of the types to conform to that ECS version. + * + * https://www.elastic.co/guide/en/ecs/1.7/index.html + */ + ecs: { version: '1.7.0' }; + + // base fields + ['@timestamp']?: string; + labels?: Record; + message?: string; + tags?: string[]; + // other fields + process?: EcsProcessField; + event?: EcsEventField; +} + +interface EcsProcessField { + uptime?: number; +} + +export interface EcsEventField { + kind?: EcsEventKind; + category?: EcsEventCategory[]; + type?: EcsEventType; +} + +export enum EcsEventKind { + ALERT = 'alert', + EVENT = 'event', + METRIC = 'metric', + STATE = 'state', + PIPELINE_ERROR = 'pipeline_error', + SIGNAL = 'signal', +} + +export enum EcsEventCategory { + AUTHENTICATION = 'authentication', + CONFIGURATION = 'configuration', + DATABASE = 'database', + DRIVER = 'driver', + FILE = 'file', + HOST = 'host', + IAM = 'iam', + INTRUSION_DETECTION = 'intrusion_detection', + MALWARE = 'malware', + NETWORK = 'network', + PACKAGE = 'package', + PROCESS = 'process', + WEB = 'web', +} + +export enum EcsEventType { + ACCESS = 'access', + ADMIN = 'admin', + ALLOWED = 'allowed', + CHANGE = 'change', + CONNECTION = 'connection', + CREATION = 'creation', + DELETION = 'deletion', + DENIED = 'denied', + END = 'end', + ERROR = 'error', + GROUP = 'group', + INFO = 'info', + INSTALLATION = 'installation', + PROTOCOL = 'protocol', + START = 'start', + USER = 'user', +} diff --git a/src/core/server/logging/index.ts b/src/core/server/logging/index.ts index f024bea1bf358..18a903af0a9fd 100644 --- a/src/core/server/logging/index.ts +++ b/src/core/server/logging/index.ts @@ -17,6 +17,13 @@ export { LogLevelId, LogLevel, } from '@kbn/logging'; +export { + EcsOpsMetricsEvent, + EcsEventField, + EcsEventKind, + EcsEventCategory, + EcsEventType, +} from './ecs'; export { config, LoggingConfigType, diff --git a/src/core/server/metrics/logging/get_ops_metrics_log.test.ts b/src/core/server/metrics/logging/get_ops_metrics_log.test.ts new file mode 100644 index 0000000000000..820959910e764 --- /dev/null +++ b/src/core/server/metrics/logging/get_ops_metrics_log.test.ts @@ -0,0 +1,132 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { OpsMetrics } from '..'; +import { getEcsOpsMetricsLog } from './get_ops_metrics_log'; + +function createBaseOpsMetrics(): OpsMetrics { + return { + collected_at: new Date('2020-01-01 01:00:00'), + process: { + memory: { + heap: { total_in_bytes: 1, used_in_bytes: 1, size_limit: 1 }, + resident_set_size_in_bytes: 1, + }, + event_loop_delay: 1, + pid: 1, + uptime_in_millis: 1, + }, + os: { + platform: 'darwin' as const, + platformRelease: 'test', + load: { '1m': 1, '5m': 1, '15m': 1 }, + memory: { total_in_bytes: 1, free_in_bytes: 1, used_in_bytes: 1 }, + uptime_in_millis: 1, + }, + response_times: { avg_in_millis: 1, max_in_millis: 1 }, + requests: { disconnects: 1, total: 1, statusCodes: { '200': 1 } }, + concurrent_connections: 1, + }; +} + +function createMockOpsMetrics(testMetrics: Partial): OpsMetrics { + const base = createBaseOpsMetrics(); + return { + ...base, + ...testMetrics, + }; +} +const testMetrics = ({ + process: { + memory: { heap: { used_in_bytes: 100 } }, + uptime_in_millis: 1500, + event_loop_delay: 50, + }, + os: { + load: { + '1m': 10, + '5m': 20, + '15m': 30, + }, + }, +} as unknown) as Partial; + +describe('getEcsOpsMetricsLog', () => { + it('provides correctly formatted message', () => { + const result = getEcsOpsMetricsLog(createMockOpsMetrics(testMetrics)); + expect(result.message).toMatchInlineSnapshot( + `"memory: 100.0B uptime: 0:00:01 load: [10.00,20.00,30.00] delay: 50.000"` + ); + }); + + it('correctly formats process uptime', () => { + const logMeta = getEcsOpsMetricsLog(createMockOpsMetrics(testMetrics)); + expect(logMeta.process!.uptime).toEqual(1); + }); + + it('excludes values from the message if unavailable', () => { + const baseMetrics = createBaseOpsMetrics(); + const missingMetrics = ({ + ...baseMetrics, + process: {}, + os: {}, + } as unknown) as OpsMetrics; + const logMeta = getEcsOpsMetricsLog(missingMetrics); + expect(logMeta.message).toMatchInlineSnapshot(`""`); + }); + + it('specifies correct ECS version', () => { + const logMeta = getEcsOpsMetricsLog(createBaseOpsMetrics()); + expect(logMeta.ecs.version).toBe('1.7.0'); + }); + + it('provides an ECS-compatible response', () => { + const logMeta = getEcsOpsMetricsLog(createBaseOpsMetrics()); + expect(logMeta).toMatchInlineSnapshot(` + Object { + "ecs": Object { + "version": "1.7.0", + }, + "event": Object { + "category": Array [ + "process", + "host", + ], + "kind": "metric", + "type": "info", + }, + "host": Object { + "os": Object { + "load": Object { + "15m": 1, + "1m": 1, + "5m": 1, + }, + }, + }, + "message": "memory: 1.0B load: [1.00,1.00,1.00] delay: 1.000", + "process": Object { + "eventLoopDelay": 1, + "memory": Object { + "heap": Object { + "usedInBytes": 1, + }, + }, + "uptime": 0, + }, + } + `); + }); + + it('logs ECS fields in the log meta', () => { + const logMeta = getEcsOpsMetricsLog(createBaseOpsMetrics()); + expect(logMeta.event!.kind).toBe('metric'); + expect(logMeta.event!.category).toEqual(expect.arrayContaining(['process', 'host'])); + expect(logMeta.event!.type).toBe('info'); + }); +}); diff --git a/src/core/server/metrics/logging/get_ops_metrics_log.ts b/src/core/server/metrics/logging/get_ops_metrics_log.ts new file mode 100644 index 0000000000000..361cac0bc310c --- /dev/null +++ b/src/core/server/metrics/logging/get_ops_metrics_log.ts @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import numeral from '@elastic/numeral'; +import { EcsOpsMetricsEvent, EcsEventKind, EcsEventCategory, EcsEventType } from '../../logging'; +import { OpsMetrics } from '..'; + +const ECS_VERSION = '1.7.0'; +/** + * Converts ops metrics into ECS-compliant `LogMeta` for logging + * + * @internal + */ +export function getEcsOpsMetricsLog(metrics: OpsMetrics): EcsOpsMetricsEvent { + const { process, os } = metrics; + const processMemoryUsedInBytes = process?.memory?.heap?.used_in_bytes; + const processMemoryUsedInBytesMsg = processMemoryUsedInBytes + ? `memory: ${numeral(processMemoryUsedInBytes).format('0.0b')} ` + : ''; + + // ECS process.uptime is in seconds: + const uptimeVal = process?.uptime_in_millis + ? Math.floor(process.uptime_in_millis / 1000) + : undefined; + + // HH:mm:ss message format for backward compatibility + const uptimeValMsg = uptimeVal ? `uptime: ${numeral(uptimeVal).format('00:00:00')} ` : ''; + + // Event loop delay is in ms + const eventLoopDelayVal = process?.event_loop_delay; + const eventLoopDelayValMsg = eventLoopDelayVal + ? `delay: ${numeral(process?.event_loop_delay).format('0.000')}` + : ''; + + const loadEntries = { + '1m': os?.load ? os?.load['1m'] : undefined, + '5m': os?.load ? os?.load['5m'] : undefined, + '15m': os?.load ? os?.load['15m'] : undefined, + }; + + const loadVals = [...Object.values(os?.load ?? [])]; + const loadValsMsg = + loadVals.length > 0 + ? `load: [${loadVals.map((val: number) => { + return numeral(val).format('0.00'); + })}] ` + : ''; + + return { + ecs: { version: ECS_VERSION }, + message: `${processMemoryUsedInBytesMsg}${uptimeValMsg}${loadValsMsg}${eventLoopDelayValMsg}`, + event: { + kind: EcsEventKind.METRIC, + category: [EcsEventCategory.PROCESS, EcsEventCategory.HOST], + type: EcsEventType.INFO, + }, + process: { + uptime: uptimeVal, + // @ts-expect-error custom fields not yet part of ECS + memory: { + heap: { + usedInBytes: processMemoryUsedInBytes, + }, + }, + eventLoopDelay: eventLoopDelayVal, + }, + host: { + os: { + load: loadEntries, + }, + }, + }; +} diff --git a/src/plugins/lens_oss/common/index.ts b/src/core/server/metrics/logging/index.ts similarity index 85% rename from src/plugins/lens_oss/common/index.ts rename to src/core/server/metrics/logging/index.ts index 7f60b8508dc27..5b3f9aed56be0 100644 --- a/src/plugins/lens_oss/common/index.ts +++ b/src/core/server/metrics/logging/index.ts @@ -6,4 +6,4 @@ * Public License, v 1. */ -export * from './constants'; +export { getEcsOpsMetricsLog } from './get_ops_metrics_log'; diff --git a/src/core/server/metrics/metrics_service.test.ts b/src/core/server/metrics/metrics_service.test.ts index 5ba29606d8a80..e21bad1ef4be7 100644 --- a/src/core/server/metrics/metrics_service.test.ts +++ b/src/core/server/metrics/metrics_service.test.ts @@ -13,12 +13,15 @@ import { mockOpsCollector } from './metrics_service.test.mocks'; import { MetricsService } from './metrics_service'; import { mockCoreContext } from '../core_context.mock'; import { httpServiceMock } from '../http/http_service.mock'; +import { loggingSystemMock } from '../logging/logging_system.mock'; import { take } from 'rxjs/operators'; const testInterval = 100; const dummyMetrics = { metricA: 'value', metricB: 'otherValue' }; +const logger = loggingSystemMock.create(); + describe('MetricsService', () => { const httpMock = httpServiceMock.createInternalSetupContract(); let metricsService: MetricsService; @@ -29,7 +32,7 @@ describe('MetricsService', () => { const configService = configServiceMock.create({ atPath: { interval: moment.duration(testInterval) }, }); - const coreContext = mockCoreContext.create({ configService }); + const coreContext = mockCoreContext.create({ logger, configService }); metricsService = new MetricsService(coreContext); }); @@ -118,6 +121,100 @@ describe('MetricsService', () => { expect(await nextEmission()).toEqual({ metric: 'first' }); expect(await nextEmission()).toEqual({ metric: 'second' }); }); + + it('logs the metrics at every interval', async () => { + const firstMetrics = { + process: { + memory: { heap: { used_in_bytes: 100 } }, + uptime_in_millis: 1500, + event_loop_delay: 50, + }, + os: { + load: { + '1m': 10, + '5m': 20, + '15m': 30, + }, + }, + }; + const secondMetrics = { + process: { + memory: { heap: { used_in_bytes: 200 } }, + uptime_in_millis: 3000, + event_loop_delay: 100, + }, + os: { + load: { + '1m': 20, + '5m': 30, + '15m': 40, + }, + }, + }; + + const opsLogger = logger.get('metrics', 'ops'); + + mockOpsCollector.collect + .mockResolvedValueOnce(firstMetrics) + .mockResolvedValueOnce(secondMetrics); + await metricsService.setup({ http: httpMock }); + const { getOpsMetrics$ } = await metricsService.start(); + + const nextEmission = async () => { + jest.advanceTimersByTime(testInterval); + const emission = await getOpsMetrics$().pipe(take(1)).toPromise(); + await new Promise((resolve) => process.nextTick(resolve)); + return emission; + }; + + await nextEmission(); + const opsLogs = loggingSystemMock.collect(opsLogger).debug; + expect(opsLogs.length).toEqual(2); + expect(opsLogs[0][1]).not.toEqual(opsLogs[1][1]); + }); + + it('omits metrics from log message if they are missing or malformed', async () => { + const opsLogger = logger.get('metrics', 'ops'); + mockOpsCollector.collect.mockResolvedValueOnce({ secondMetrics: 'metrics' }); + await metricsService.setup({ http: httpMock }); + await metricsService.start(); + expect(loggingSystemMock.collect(opsLogger).debug[0]).toMatchInlineSnapshot(` + Array [ + "", + Object { + "ecs": Object { + "version": "1.7.0", + }, + "event": Object { + "category": Array [ + "process", + "host", + ], + "kind": "metric", + "type": "info", + }, + "host": Object { + "os": Object { + "load": Object { + "15m": undefined, + "1m": undefined, + "5m": undefined, + }, + }, + }, + "process": Object { + "eventLoopDelay": undefined, + "memory": Object { + "heap": Object { + "usedInBytes": undefined, + }, + }, + "uptime": undefined, + }, + }, + ] + `); + }); }); describe('#stop', () => { diff --git a/src/core/server/metrics/metrics_service.ts b/src/core/server/metrics/metrics_service.ts index 24b2b1b67a07a..460035ad2e298 100644 --- a/src/core/server/metrics/metrics_service.ts +++ b/src/core/server/metrics/metrics_service.ts @@ -15,6 +15,7 @@ import { InternalHttpServiceSetup } from '../http'; import { InternalMetricsServiceSetup, InternalMetricsServiceStart, OpsMetrics } from './types'; import { OpsMetricsCollector } from './ops_metrics_collector'; import { opsConfig, OpsConfigType } from './ops_config'; +import { getEcsOpsMetricsLog } from './logging'; interface MetricsServiceSetupDeps { http: InternalHttpServiceSetup; @@ -24,6 +25,7 @@ interface MetricsServiceSetupDeps { export class MetricsService implements CoreService { private readonly logger: Logger; + private readonly opsMetricsLogger: Logger; private metricsCollector?: OpsMetricsCollector; private collectInterval?: NodeJS.Timeout; private metrics$ = new ReplaySubject(1); @@ -31,6 +33,7 @@ export class MetricsService constructor(private readonly coreContext: CoreContext) { this.logger = coreContext.logger.get('metrics'); + this.opsMetricsLogger = coreContext.logger.get('metrics', 'ops'); } public async setup({ http }: MetricsServiceSetupDeps): Promise { @@ -69,8 +72,9 @@ export class MetricsService } private async refreshMetrics() { - this.logger.debug('Refreshing metrics'); const metrics = await this.metricsCollector!.collect(); + const { message, ...meta } = getEcsOpsMetricsLog(metrics); + this.opsMetricsLogger.debug(message!, meta); this.metricsCollector!.reset(); this.metrics$.next(metrics); } diff --git a/src/core/server/mocks.ts b/src/core/server/mocks.ts index 2d053300273fb..b86e2e4c6dedb 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -69,9 +69,12 @@ export function pluginInitializerContextConfigMock(config: T) { }; const mock: jest.Mocked['config']> = { - legacy: { globalConfig$: of(globalConfig) }, + legacy: { + globalConfig$: of(globalConfig), + get: () => globalConfig, + }, create: jest.fn().mockReturnValue(of(config)), - createIfExists: jest.fn().mockReturnValue(of(config)), + get: jest.fn().mockReturnValue(config), }; return mock; diff --git a/src/core/server/plugins/legacy_config.test.ts b/src/core/server/plugins/legacy_config.test.ts new file mode 100644 index 0000000000000..fd8234d72bd17 --- /dev/null +++ b/src/core/server/plugins/legacy_config.test.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { take } from 'rxjs/operators'; +import { ConfigService, Env } from '@kbn/config'; +import { getEnvOptions, rawConfigServiceMock } from '../config/mocks'; +import { getGlobalConfig, getGlobalConfig$ } from './legacy_config'; +import { REPO_ROOT } from '@kbn/utils'; +import { loggingSystemMock } from '../logging/logging_system.mock'; +import { duration } from 'moment'; +import { fromRoot } from '../utils'; +import { ByteSizeValue } from '@kbn/config-schema'; +import { Server } from '../server'; + +describe('Legacy config', () => { + let env: Env; + let logger: ReturnType; + + beforeEach(() => { + env = Env.createDefault(REPO_ROOT, getEnvOptions()); + logger = loggingSystemMock.create(); + }); + + const createConfigService = (rawConfig: Record = {}): ConfigService => { + const rawConfigService = rawConfigServiceMock.create({ rawConfig }); + const server = new Server(rawConfigService, env, logger); + server.setupCoreConfig(); + return server.configService; + }; + + describe('getGlobalConfig', () => { + it('should return the global config', async () => { + const configService = createConfigService(); + await configService.validate(); + + const legacyConfig = getGlobalConfig(configService); + + expect(legacyConfig).toStrictEqual({ + kibana: { + index: '.kibana', + autocompleteTerminateAfter: duration(100000), + autocompleteTimeout: duration(1000), + }, + elasticsearch: { + shardTimeout: duration(30, 's'), + requestTimeout: duration(30, 's'), + pingTimeout: duration(30, 's'), + }, + path: { data: fromRoot('data') }, + savedObjects: { maxImportPayloadBytes: new ByteSizeValue(26214400) }, + }); + }); + }); + + describe('getGlobalConfig$', () => { + it('should return an observable for the global config', async () => { + const configService = createConfigService(); + + const legacyConfig = await getGlobalConfig$(configService).pipe(take(1)).toPromise(); + + expect(legacyConfig).toStrictEqual({ + kibana: { + index: '.kibana', + autocompleteTerminateAfter: duration(100000), + autocompleteTimeout: duration(1000), + }, + elasticsearch: { + shardTimeout: duration(30, 's'), + requestTimeout: duration(30, 's'), + pingTimeout: duration(30, 's'), + }, + path: { data: fromRoot('data') }, + savedObjects: { maxImportPayloadBytes: new ByteSizeValue(26214400) }, + }); + }); + }); +}); diff --git a/src/core/server/plugins/legacy_config.ts b/src/core/server/plugins/legacy_config.ts new file mode 100644 index 0000000000000..748a1e3190640 --- /dev/null +++ b/src/core/server/plugins/legacy_config.ts @@ -0,0 +1,69 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { map, shareReplay } from 'rxjs/operators'; +import { combineLatest, Observable } from 'rxjs'; +import { PathConfigType, config as pathConfig } from '@kbn/utils'; +import { pick, deepFreeze } from '@kbn/std'; +import { IConfigService } from '@kbn/config'; + +import { SharedGlobalConfig, SharedGlobalConfigKeys } from './types'; +import { KibanaConfigType, config as kibanaConfig } from '../kibana_config'; +import { + ElasticsearchConfigType, + config as elasticsearchConfig, +} from '../elasticsearch/elasticsearch_config'; +import { SavedObjectsConfigType, savedObjectsConfig } from '../saved_objects/saved_objects_config'; + +const createGlobalConfig = ({ + kibana, + elasticsearch, + path, + savedObjects, +}: { + kibana: KibanaConfigType; + elasticsearch: ElasticsearchConfigType; + path: PathConfigType; + savedObjects: SavedObjectsConfigType; +}): SharedGlobalConfig => { + return deepFreeze({ + kibana: pick(kibana, SharedGlobalConfigKeys.kibana), + elasticsearch: pick(elasticsearch, SharedGlobalConfigKeys.elasticsearch), + path: pick(path, SharedGlobalConfigKeys.path), + savedObjects: pick(savedObjects, SharedGlobalConfigKeys.savedObjects), + }); +}; + +export const getGlobalConfig = (configService: IConfigService): SharedGlobalConfig => { + return createGlobalConfig({ + kibana: configService.atPathSync(kibanaConfig.path), + elasticsearch: configService.atPathSync(elasticsearchConfig.path), + path: configService.atPathSync(pathConfig.path), + savedObjects: configService.atPathSync(savedObjectsConfig.path), + }); +}; + +export const getGlobalConfig$ = (configService: IConfigService): Observable => { + return combineLatest([ + configService.atPath(kibanaConfig.path), + configService.atPath(elasticsearchConfig.path), + configService.atPath(pathConfig.path), + configService.atPath(savedObjectsConfig.path), + ]).pipe( + map( + ([kibana, elasticsearch, path, savedObjects]) => + createGlobalConfig({ + kibana, + elasticsearch, + path, + savedObjects, + }), + shareReplay(1) + ) + ); +}; diff --git a/src/core/server/plugins/plugin_context.test.ts b/src/core/server/plugins/plugin_context.test.ts index 3d212bc555828..c71102df9929b 100644 --- a/src/core/server/plugins/plugin_context.test.ts +++ b/src/core/server/plugins/plugin_context.test.ts @@ -17,15 +17,8 @@ import { rawConfigServiceMock, getEnvOptions } from '../config/mocks'; import { PluginManifest } from './types'; import { Server } from '../server'; import { fromRoot } from '../utils'; -import { ByteSizeValue } from '@kbn/config-schema'; - -const logger = loggingSystemMock.create(); - -let coreId: symbol; -let env: Env; -let coreContext: CoreContext; -let server: Server; -let instanceInfo: InstanceInfo; +import { schema, ByteSizeValue } from '@kbn/config-schema'; +import { ConfigService } from '@kbn/config'; function createPluginManifest(manifestProps: Partial = {}): PluginManifest { return { @@ -43,61 +36,112 @@ function createPluginManifest(manifestProps: Partial = {}): Plug } describe('createPluginInitializerContext', () => { + let logger: ReturnType; + let coreId: symbol; + let opaqueId: symbol; + let env: Env; + let coreContext: CoreContext; + let server: Server; + let instanceInfo: InstanceInfo; + beforeEach(async () => { + logger = loggingSystemMock.create(); coreId = Symbol('core'); + opaqueId = Symbol(); instanceInfo = { uuid: 'instance-uuid', }; env = Env.createDefault(REPO_ROOT, getEnvOptions()); const config$ = rawConfigServiceMock.create({ rawConfig: {} }); server = new Server(config$, env, logger); - await server.setupCoreConfig(); + server.setupCoreConfig(); coreContext = { coreId, env, logger, configService: server.configService }; }); - it('should return a globalConfig handler in the context', async () => { - const manifest = createPluginManifest(); - const opaqueId = Symbol(); - const pluginInitializerContext = createPluginInitializerContext( - coreContext, - opaqueId, - manifest, - instanceInfo - ); + describe('context.config', () => { + it('config.get() should return the plugin config synchronously', async () => { + const config$ = rawConfigServiceMock.create({ + rawConfig: { + plugin: { + foo: 'bar', + answer: 42, + }, + }, + }); + + const configService = new ConfigService(config$, env, logger); + configService.setSchema( + 'plugin', + schema.object({ + foo: schema.string(), + answer: schema.number(), + }) + ); + await configService.validate(); - expect(pluginInitializerContext.config.legacy.globalConfig$).toBeDefined(); + coreContext = { coreId, env, logger, configService }; - const configObject = await pluginInitializerContext.config.legacy.globalConfig$ - .pipe(first()) - .toPromise(); - expect(configObject).toStrictEqual({ - kibana: { - index: '.kibana', - autocompleteTerminateAfter: duration(100000), - autocompleteTimeout: duration(1000), - }, - elasticsearch: { - shardTimeout: duration(30, 's'), - requestTimeout: duration(30, 's'), - pingTimeout: duration(30, 's'), - }, - path: { data: fromRoot('data') }, - savedObjects: { maxImportPayloadBytes: new ByteSizeValue(26214400) }, + const manifest = createPluginManifest({ + configPath: 'plugin', + }); + + const pluginInitializerContext = createPluginInitializerContext( + coreContext, + opaqueId, + manifest, + instanceInfo + ); + + expect(pluginInitializerContext.config.get()).toEqual({ + foo: 'bar', + answer: 42, + }); + }); + + it('config.globalConfig$ should be an observable for the global config', async () => { + const manifest = createPluginManifest(); + const pluginInitializerContext = createPluginInitializerContext( + coreContext, + opaqueId, + manifest, + instanceInfo + ); + + expect(pluginInitializerContext.config.legacy.globalConfig$).toBeDefined(); + + const configObject = await pluginInitializerContext.config.legacy.globalConfig$ + .pipe(first()) + .toPromise(); + expect(configObject).toStrictEqual({ + kibana: { + index: '.kibana', + autocompleteTerminateAfter: duration(100000), + autocompleteTimeout: duration(1000), + }, + elasticsearch: { + shardTimeout: duration(30, 's'), + requestTimeout: duration(30, 's'), + pingTimeout: duration(30, 's'), + }, + path: { data: fromRoot('data') }, + savedObjects: { maxImportPayloadBytes: new ByteSizeValue(26214400) }, + }); }); }); - it('allow to access the provided instance uuid', () => { - const manifest = createPluginManifest(); - const opaqueId = Symbol(); - instanceInfo = { - uuid: 'kibana-uuid', - }; - const pluginInitializerContext = createPluginInitializerContext( - coreContext, - opaqueId, - manifest, - instanceInfo - ); - expect(pluginInitializerContext.env.instanceUuid).toBe('kibana-uuid'); + describe('context.env', () => { + it('should expose the correct instance uuid', () => { + const manifest = createPluginManifest(); + instanceInfo = { + uuid: 'kibana-uuid', + }; + const pluginInitializerContext = createPluginInitializerContext( + coreContext, + opaqueId, + manifest, + instanceInfo + ); + expect(pluginInitializerContext.env.instanceUuid).toBe('kibana-uuid'); + }); }); }); diff --git a/src/core/server/plugins/plugin_context.ts b/src/core/server/plugins/plugin_context.ts index 5b0e2ee21a887..3b7dc70b9c054 100644 --- a/src/core/server/plugins/plugin_context.ts +++ b/src/core/server/plugins/plugin_context.ts @@ -6,27 +6,14 @@ * Public License, v 1. */ -import { map, shareReplay } from 'rxjs/operators'; -import { combineLatest } from 'rxjs'; -import { PathConfigType, config as pathConfig } from '@kbn/utils'; -import { pick, deepFreeze } from '@kbn/std'; +import { shareReplay } from 'rxjs/operators'; import type { RequestHandlerContext } from 'src/core/server'; import { CoreContext } from '../core_context'; import { PluginWrapper } from './plugin'; import { PluginsServiceSetupDeps, PluginsServiceStartDeps } from './plugins_service'; -import { - PluginInitializerContext, - PluginManifest, - PluginOpaqueId, - SharedGlobalConfigKeys, -} from './types'; -import { KibanaConfigType, config as kibanaConfig } from '../kibana_config'; -import { - ElasticsearchConfigType, - config as elasticsearchConfig, -} from '../elasticsearch/elasticsearch_config'; +import { PluginInitializerContext, PluginManifest, PluginOpaqueId } from './types'; import { IRouter, RequestHandlerContextProvider } from '../http'; -import { SavedObjectsConfigType, savedObjectsConfig } from '../saved_objects/saved_objects_config'; +import { getGlobalConfig, getGlobalConfig$ } from './legacy_config'; import { CoreSetup, CoreStart } from '..'; export interface InstanceInfo { @@ -78,40 +65,19 @@ export function createPluginInitializerContext( */ config: { legacy: { - /** - * Global configuration - * Note: naming not final here, it will be renamed in a near future (https://github.com/elastic/kibana/issues/46240) - * @deprecated - */ - globalConfig$: combineLatest([ - coreContext.configService.atPath(kibanaConfig.path), - coreContext.configService.atPath(elasticsearchConfig.path), - coreContext.configService.atPath(pathConfig.path), - coreContext.configService.atPath(savedObjectsConfig.path), - ]).pipe( - map(([kibana, elasticsearch, path, savedObjects]) => - deepFreeze({ - kibana: pick(kibana, SharedGlobalConfigKeys.kibana), - elasticsearch: pick(elasticsearch, SharedGlobalConfigKeys.elasticsearch), - path: pick(path, SharedGlobalConfigKeys.path), - savedObjects: pick(savedObjects, SharedGlobalConfigKeys.savedObjects), - }) - ) - ), + globalConfig$: getGlobalConfig$(coreContext.configService), + get: () => getGlobalConfig(coreContext.configService), }, /** * Reads the subset of the config at the `configPath` defined in the plugin - * manifest and validates it against the schema in the static `schema` on - * the given `ConfigClass`. - * @param ConfigClass A class (not an instance of a class) that contains a - * static `schema` that we validate the config at the given `path` against. + * manifest. */ create() { return coreContext.configService.atPath(pluginManifest.configPath).pipe(shareReplay(1)); }, - createIfExists() { - return coreContext.configService.optionalAtPath(pluginManifest.configPath); + get() { + return coreContext.configService.atPathSync(pluginManifest.configPath); }, }, }; diff --git a/src/core/server/plugins/plugins_service.ts b/src/core/server/plugins/plugins_service.ts index 9a1403bda3bca..dd2831f77f537 100644 --- a/src/core/server/plugins/plugins_service.ts +++ b/src/core/server/plugins/plugins_service.ts @@ -219,10 +219,7 @@ export class PluginsService implements CoreService { packageInfo: Readonly; instanceUuid: string; }; + /** + * {@link LoggerFactory | logger factory} instance already bound to the plugin's logging context + * + * @example + * ```typescript + * // plugins/my-plugin/server/plugin.ts + * // "id: myPlugin" in `plugins/my-plugin/kibana.yaml` + * + * export class MyPlugin implements Plugin { + * constructor(private readonly initContext: PluginInitializerContext) { + * this.logger = initContext.logger.get(); + * // `logger` context: `plugins.myPlugin` + * this.mySubLogger = initContext.logger.get('sub'); // or this.logger.get('sub'); + * // `mySubLogger` context: `plugins.myPlugin.sub` + * } + * } + * ``` + */ logger: LoggerFactory; + /** + * Accessors for the plugin's configuration + */ config: { - legacy: { globalConfig$: Observable }; + /** + * Provide access to Kibana legacy configuration values. + * + * @remarks Naming not final here, it may be renamed in a near future + * @deprecated Accessing configuration values outside of the plugin's config scope is highly discouraged + */ + legacy: { + globalConfig$: Observable; + get: () => SharedGlobalConfig; + }; + /** + * Return an observable of the plugin's configuration + * + * @example + * ```typescript + * // plugins/my-plugin/server/plugin.ts + * + * export class MyPlugin implements Plugin { + * constructor(private readonly initContext: PluginInitializerContext) {} + * setup(core) { + * this.configSub = this.initContext.config.create().subscribe((config) => { + * this.myService.reconfigure(config); + * }); + * } + * stop() { + * this.configSub.unsubscribe(); + * } + * ``` + * + * @example + * ```typescript + * // plugins/my-plugin/server/plugin.ts + * + * export class MyPlugin implements Plugin { + * constructor(private readonly initContext: PluginInitializerContext) {} + * async setup(core) { + * this.config = await this.initContext.config.create().pipe(take(1)).toPromise(); + * } + * stop() { + * this.configSub.unsubscribe(); + * } + * ``` + * + * @remarks The underlying observable has a replay effect, meaning that awaiting for the first emission + * will be resolved at next tick, without risks to delay any asynchronous code's workflow. + */ create: () => Observable; - createIfExists: () => Observable; + /** + * Return the current value of the plugin's configuration synchronously. + * + * @example + * ```typescript + * // plugins/my-plugin/server/plugin.ts + * + * export class MyPlugin implements Plugin { + * constructor(private readonly initContext: PluginInitializerContext) {} + * setup(core) { + * const config = this.initContext.config.get(); + * // do something with the config + * } + * } + * ``` + * + * @remarks This should only be used when synchronous access is an absolute necessity, such + * as during the plugin's setup or start lifecycle. For all other usages, + * {@link create} should be used instead. + */ + get: () => T; }; } diff --git a/src/core/server/root/index.ts b/src/core/server/root/index.ts index 17cb209897c25..a918580392caa 100644 --- a/src/core/server/root/index.ts +++ b/src/core/server/root/index.ts @@ -36,7 +36,7 @@ export class Root { public async setup() { try { - await this.server.setupCoreConfig(); + this.server.setupCoreConfig(); await this.setupLogging(); this.log.debug('setting up root'); return await this.server.setup(); diff --git a/src/core/server/saved_objects/migrations/core/document_migrator.test.ts b/src/core/server/saved_objects/migrations/core/document_migrator.test.ts index 6ba652abda3d5..741f715ba6ebe 100644 --- a/src/core/server/saved_objects/migrations/core/document_migrator.test.ts +++ b/src/core/server/saved_objects/migrations/core/document_migrator.test.ts @@ -206,20 +206,6 @@ describe('DocumentMigrator', () => { ); }); - it('coerces the current Kibana version if it has a hyphen', () => { - const validDefinition = { - kibanaVersion: '3.2.0-SNAPSHOT', - typeRegistry: createRegistry({ - name: 'foo', - convertToMultiNamespaceTypeVersion: '3.2.0', - namespaceType: 'multiple', - }), - minimumConvertVersion: '0.0.0', - log: mockLogger, - }; - expect(() => new DocumentMigrator(validDefinition)).not.toThrowError(); - }); - it('validates convertToMultiNamespaceTypeVersion is not used on a patch version', () => { const invalidDefinition = { kibanaVersion: '3.2.3', diff --git a/src/core/server/saved_objects/migrations/core/document_migrator.ts b/src/core/server/saved_objects/migrations/core/document_migrator.ts index e93586ec7ce4c..e4b89a949d3cf 100644 --- a/src/core/server/saved_objects/migrations/core/document_migrator.ts +++ b/src/core/server/saved_objects/migrations/core/document_migrator.ts @@ -159,11 +159,10 @@ export class DocumentMigrator implements VersionedTransformer { */ constructor({ typeRegistry, - kibanaVersion: rawKibanaVersion, + kibanaVersion, minimumConvertVersion = DEFAULT_MINIMUM_CONVERT_VERSION, log, }: DocumentMigratorOptions) { - const kibanaVersion = rawKibanaVersion.split('-')[0]; // coerce a semver-like string (x.y.z-SNAPSHOT) or prerelease version (x.y.z-alpha) to a regular semver (x.y.z) validateMigrationDefinition(typeRegistry, kibanaVersion, minimumConvertVersion); this.documentMigratorOptions = { typeRegistry, kibanaVersion, log }; diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts index 281d2ce8d03cf..dac93ff29b68f 100644 --- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts +++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.test.ts @@ -31,6 +31,14 @@ const createRegistry = (types: Array>) => { }; describe('KibanaMigrator', () => { + describe('constructor', () => { + it('coerces the current Kibana version if it has a hyphen', () => { + const options = mockOptions(); + options.kibanaVersion = '3.2.1-SNAPSHOT'; + const migrator = new KibanaMigrator(options); + expect(migrator.kibanaVersion).toEqual('3.2.1'); + }); + }); describe('getActiveMappings', () => { it('returns full index mappings w/ core properties', () => { const options = mockOptions(); diff --git a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts index c8bc4b2e14123..ecef84a6e297c 100644 --- a/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts +++ b/src/core/server/saved_objects/migrations/kibana/kibana_migrator.ts @@ -90,13 +90,12 @@ export class KibanaMigrator { }: KibanaMigratorOptions) { this.client = client; this.kibanaConfig = kibanaConfig; - this.kibanaVersion = kibanaVersion; this.savedObjectsConfig = savedObjectsConfig; this.typeRegistry = typeRegistry; this.serializer = new SavedObjectsSerializer(this.typeRegistry); this.mappingProperties = mergeTypes(this.typeRegistry.getAllTypes()); this.log = logger; - this.kibanaVersion = kibanaVersion; + this.kibanaVersion = kibanaVersion.split('-')[0]; // coerce a semver-like string (x.y.z-SNAPSHOT) or prerelease version (x.y.z-alpha) to a regular semver (x.y.z); this.documentMigrator = new DocumentMigrator({ kibanaVersion, typeRegistry, diff --git a/src/core/server/saved_objects/saved_objects_config.ts b/src/core/server/saved_objects/saved_objects_config.ts index 82db64daf2bd3..0885a52078f5c 100644 --- a/src/core/server/saved_objects/saved_objects_config.ts +++ b/src/core/server/saved_objects/saved_objects_config.ts @@ -18,7 +18,7 @@ export const savedObjectsMigrationConfig = { pollInterval: schema.number({ defaultValue: 1500 }), skip: schema.boolean({ defaultValue: false }), // TODO migrationsV2: remove/deprecate once we release migrations v2 - enableV2: schema.boolean({ defaultValue: false }), + enableV2: schema.boolean({ defaultValue: true }), }), }; diff --git a/src/core/server/server.api.md b/src/core/server/server.api.md index ceab69a6cdb18..aadd16bde0ee6 100644 --- a/src/core/server/server.api.md +++ b/src/core/server/server.api.md @@ -1195,6 +1195,7 @@ export interface IUiSettingsClient { getRegistered: () => Readonly>; getUserProvided: () => Promise>>; isOverridden: (key: string) => boolean; + isSensitive: (key: string) => boolean; remove: (key: string) => Promise; removeMany: (keys: string[]) => Promise; set: (key: string, value: any) => Promise; @@ -1256,7 +1257,7 @@ export type KibanaResponseFactory = typeof kibanaResponseFactory; // @public export const kibanaResponseFactory: { - custom: | { + custom: | Buffer | Error | Stream | { message: string | Error; attributes?: Record | undefined; } | undefined>(options: CustomHttpResponseOptions) => KibanaResponse; @@ -1267,9 +1268,9 @@ export const kibanaResponseFactory: { conflict: (options?: ErrorHttpResponseOptions) => KibanaResponse; internalError: (options?: ErrorHttpResponseOptions) => KibanaResponse; customError: (options: CustomHttpResponseOptions) => KibanaResponse; - redirected: (options: RedirectResponseOptions) => KibanaResponse>; - ok: (options?: HttpResponseOptions) => KibanaResponse>; - accepted: (options?: HttpResponseOptions) => KibanaResponse>; + redirected: (options: RedirectResponseOptions) => KibanaResponse | Buffer | Stream>; + ok: (options?: HttpResponseOptions) => KibanaResponse | Buffer | Stream>; + accepted: (options?: HttpResponseOptions) => KibanaResponse | Buffer | Stream>; noContent: (options?: HttpResponseOptions) => KibanaResponse; }; @@ -1840,13 +1841,13 @@ export type PluginInitializer { - // (undocumented) config: { legacy: { globalConfig$: Observable; + get: () => SharedGlobalConfig; }; create: () => Observable; - createIfExists: () => Observable; + get: () => T; }; // (undocumented) env: { @@ -1854,7 +1855,7 @@ export interface PluginInitializerContext { packageInfo: Readonly; instanceUuid: string; }; - // (undocumented) + // Warning: (ae-unresolved-link) The @link reference could not be resolved: Reexported declarations are not supported logger: LoggerFactory; // (undocumented) opaqueId: PluginOpaqueId; @@ -3100,6 +3101,7 @@ export interface UiSettingsParams { requiresPageReload?: boolean; // (undocumented) schema: Type; + sensitive?: boolean; type?: UiSettingsType; // (undocumented) validation?: ImageValidation | StringValidation; @@ -3137,5 +3139,6 @@ export const validBodyOutput: readonly ["data", "stream"]; // src/core/server/plugins/types.ts:263:3 - (ae-forgotten-export) The symbol "KibanaConfigType" needs to be exported by the entry point index.d.ts // src/core/server/plugins/types.ts:263:3 - (ae-forgotten-export) The symbol "SharedGlobalConfigKeys" needs to be exported by the entry point index.d.ts // src/core/server/plugins/types.ts:266:3 - (ae-forgotten-export) The symbol "SavedObjectsConfigType" needs to be exported by the entry point index.d.ts +// src/core/server/plugins/types.ts:371:5 - (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "create" ``` diff --git a/src/core/server/server.ts b/src/core/server/server.ts index 60f3f90428d40..cc1087a422e39 100644 --- a/src/core/server/server.ts +++ b/src/core/server/server.ts @@ -300,7 +300,7 @@ export class Server { ); } - public async setupCoreConfig() { + public setupCoreConfig() { const configDescriptors: Array> = [ pathConfig, cspConfig, @@ -325,7 +325,7 @@ export class Server { if (descriptor.deprecations) { this.configService.addDeprecationProvider(descriptor.path, descriptor.deprecations); } - await this.configService.setSchema(descriptor.path, descriptor.schema); + this.configService.setSchema(descriptor.path, descriptor.schema); } } } diff --git a/src/core/server/ui_settings/integration_tests/lib/servers.ts b/src/core/server/ui_settings/integration_tests/lib/servers.ts index f181272030ae1..b5198b19007d0 100644 --- a/src/core/server/ui_settings/integration_tests/lib/servers.ts +++ b/src/core/server/ui_settings/integration_tests/lib/servers.ts @@ -37,6 +37,9 @@ export async function startServers() { adjustTimeout: (t) => jest.setTimeout(t), settings: { kbn: { + migrations: { + enableV2: false, + }, uiSettings: { overrides: { foo: 'bar', diff --git a/src/core/server/ui_settings/settings/notifications.ts b/src/core/server/ui_settings/settings/notifications.ts index a60732ecb807a..cedd52289a68c 100644 --- a/src/core/server/ui_settings/settings/notifications.ts +++ b/src/core/server/ui_settings/settings/notifications.ts @@ -35,6 +35,7 @@ export const getNotificationsSettings = (): Record => }, }), category: ['notifications'], + sensitive: true, schema: schema.string(), }, 'notifications:lifetime:banner': { diff --git a/src/core/server/ui_settings/types.ts b/src/core/server/ui_settings/types.ts index eb2d8b00dc488..73f46e4db3b2c 100644 --- a/src/core/server/ui_settings/types.ts +++ b/src/core/server/ui_settings/types.ts @@ -65,6 +65,10 @@ export interface IUiSettingsClient { * Shows whether the uiSettings value set by the user. */ isOverridden: (key: string) => boolean; + /** + * Shows whether the uiSetting is a sensitive value. Used by telemetry to not send sensitive values. + */ + isSensitive: (key: string) => boolean; } /** @internal */ diff --git a/src/core/server/ui_settings/ui_settings_client.test.ts b/src/core/server/ui_settings/ui_settings_client.test.ts index 7fa5a85e5154e..26c4d9e8e6dd9 100644 --- a/src/core/server/ui_settings/ui_settings_client.test.ts +++ b/src/core/server/ui_settings/ui_settings_client.test.ts @@ -644,6 +644,38 @@ describe('ui settings', () => { }); }); + describe('#isSensitive()', () => { + it('returns false if sensitive config is not set', () => { + const defaults = { + foo: { + schema: schema.string(), + value: '1', + }, + }; + + const { uiSettings } = setup({ defaults }); + expect(uiSettings.isSensitive('foo')).toBe(false); + }); + + it('returns false if key is not in the settings', () => { + const { uiSettings } = setup(); + expect(uiSettings.isSensitive('baz')).toBe(false); + }); + + it('returns true if overrides defined and key is overridden', () => { + const defaults = { + foo: { + schema: schema.string(), + sensitive: true, + value: '1', + }, + }; + + const { uiSettings } = setup({ defaults }); + expect(uiSettings.isSensitive('foo')).toBe(true); + }); + }); + describe('#isOverridden()', () => { it('returns false if no overrides defined', () => { const { uiSettings } = setup(); diff --git a/src/core/server/ui_settings/ui_settings_client.ts b/src/core/server/ui_settings/ui_settings_client.ts index 2ba8caaba5170..b8a46a2f994aa 100644 --- a/src/core/server/ui_settings/ui_settings_client.ts +++ b/src/core/server/ui_settings/ui_settings_client.ts @@ -52,7 +52,6 @@ export class UiSettingsClient implements IUiSettingsClient { constructor(options: UiSettingsServiceOptions) { const { type, id, buildNum, savedObjectsClient, log, defaults = {}, overrides = {} } = options; - this.type = type; this.id = id; this.buildNum = buildNum; @@ -132,6 +131,11 @@ export class UiSettingsClient implements IUiSettingsClient { return this.overrides.hasOwnProperty(key); } + isSensitive(key: string): boolean { + const definition = this.defaults[key]; + return !!definition?.sensitive; + } + private assertUpdateAllowed(key: string) { if (this.isOverridden(key)) { throw new CannotOverrideError(`Unable to update "${key}" because it is overridden`); diff --git a/src/core/server/ui_settings/ui_settings_service.mock.ts b/src/core/server/ui_settings/ui_settings_service.mock.ts index a03412e37f551..771b9d243656a 100644 --- a/src/core/server/ui_settings/ui_settings_service.mock.ts +++ b/src/core/server/ui_settings/ui_settings_service.mock.ts @@ -25,6 +25,7 @@ const createClientMock = () => { remove: jest.fn(), removeMany: jest.fn(), isOverridden: jest.fn(), + isSensitive: jest.fn(), }; mocked.get.mockResolvedValue(false); mocked.getAll.mockResolvedValue({}); diff --git a/src/core/types/ui_settings.ts b/src/core/types/ui_settings.ts index 24dfbbeea6726..92e8f6ef2f41e 100644 --- a/src/core/types/ui_settings.ts +++ b/src/core/types/ui_settings.ts @@ -56,6 +56,11 @@ export interface UiSettingsParams { requiresPageReload?: boolean; /** a flag indicating that value cannot be changed */ readonly?: boolean; + /** + * a flag indicating that value might contain user sensitive data. + * used by telemetry to mask the value of the setting when sent. + */ + sensitive?: boolean; /** defines a type of UI element {@link UiSettingsType} */ type?: UiSettingsType; /** optional deprecation information. Used to generate a deprecation warning. */ diff --git a/src/dev/build/args.test.ts b/src/dev/build/args.test.ts index 584f0dfe54b74..745b9d0b910c8 100644 --- a/src/dev/build/args.test.ts +++ b/src/dev/build/args.test.ts @@ -30,8 +30,9 @@ it('build default and oss dist for current platform, without packages, by defaul "buildOssDist": true, "createArchives": true, "createDebPackage": false, - "createDockerPackage": false, - "createDockerUbiPackage": false, + "createDockerCentOS": false, + "createDockerContexts": false, + "createDockerUBI": false, "createRpmPackage": false, "downloadFreshNode": true, "isRelease": false, @@ -53,8 +54,9 @@ it('builds packages if --all-platforms is passed', () => { "buildOssDist": true, "createArchives": true, "createDebPackage": true, - "createDockerPackage": true, - "createDockerUbiPackage": true, + "createDockerCentOS": true, + "createDockerContexts": true, + "createDockerUBI": true, "createRpmPackage": true, "downloadFreshNode": true, "isRelease": false, @@ -76,8 +78,9 @@ it('limits packages if --rpm passed with --all-platforms', () => { "buildOssDist": true, "createArchives": true, "createDebPackage": false, - "createDockerPackage": false, - "createDockerUbiPackage": false, + "createDockerCentOS": false, + "createDockerContexts": false, + "createDockerUBI": false, "createRpmPackage": true, "downloadFreshNode": true, "isRelease": false, @@ -99,8 +102,9 @@ it('limits packages if --deb passed with --all-platforms', () => { "buildOssDist": true, "createArchives": true, "createDebPackage": true, - "createDockerPackage": false, - "createDockerUbiPackage": false, + "createDockerCentOS": false, + "createDockerContexts": false, + "createDockerUBI": false, "createRpmPackage": false, "downloadFreshNode": true, "isRelease": false, @@ -115,7 +119,7 @@ it('limits packages if --deb passed with --all-platforms', () => { }); it('limits packages if --docker passed with --all-platforms', () => { - expect(readCliArgs(['node', 'scripts/build', '--all-platforms', '--docker'])) + expect(readCliArgs(['node', 'scripts/build', '--all-platforms', '--docker-images'])) .toMatchInlineSnapshot(` Object { "buildOptions": Object { @@ -123,8 +127,9 @@ it('limits packages if --docker passed with --all-platforms', () => { "buildOssDist": true, "createArchives": true, "createDebPackage": false, - "createDockerPackage": true, - "createDockerUbiPackage": true, + "createDockerCentOS": true, + "createDockerContexts": false, + "createDockerUBI": true, "createRpmPackage": false, "downloadFreshNode": true, "isRelease": false, @@ -139,16 +144,24 @@ it('limits packages if --docker passed with --all-platforms', () => { }); it('limits packages if --docker passed with --skip-docker-ubi and --all-platforms', () => { - expect(readCliArgs(['node', 'scripts/build', '--all-platforms', '--docker', '--skip-docker-ubi'])) - .toMatchInlineSnapshot(` + expect( + readCliArgs([ + 'node', + 'scripts/build', + '--all-platforms', + '--docker-images', + '--skip-docker-ubi', + ]) + ).toMatchInlineSnapshot(` Object { "buildOptions": Object { "buildDefaultDist": true, "buildOssDist": true, "createArchives": true, "createDebPackage": false, - "createDockerPackage": true, - "createDockerUbiPackage": false, + "createDockerCentOS": true, + "createDockerContexts": false, + "createDockerUBI": false, "createRpmPackage": false, "downloadFreshNode": true, "isRelease": false, @@ -161,3 +174,28 @@ it('limits packages if --docker passed with --skip-docker-ubi and --all-platform } `); }); + +it('limits packages if --all-platforms passed with --skip-docker-centos', () => { + expect(readCliArgs(['node', 'scripts/build', '--all-platforms', '--skip-docker-centos'])) + .toMatchInlineSnapshot(` + Object { + "buildOptions": Object { + "buildDefaultDist": true, + "buildOssDist": true, + "createArchives": true, + "createDebPackage": true, + "createDockerCentOS": false, + "createDockerContexts": true, + "createDockerUBI": true, + "createRpmPackage": true, + "downloadFreshNode": true, + "isRelease": false, + "targetAllPlatforms": true, + "versionQualifier": "", + }, + "log": , + "showHelp": false, + "unknownFlags": Array [], + } + `); +}); diff --git a/src/dev/build/args.ts b/src/dev/build/args.ts index 5070e325f40a4..2d26d7db3a5e3 100644 --- a/src/dev/build/args.ts +++ b/src/dev/build/args.ts @@ -21,8 +21,10 @@ export function readCliArgs(argv: string[]) { 'skip-os-packages', 'rpm', 'deb', - 'docker', + 'docker-images', + 'docker-contexts', 'skip-docker-ubi', + 'skip-docker-centos', 'release', 'skip-node-download', 'verbose', @@ -42,7 +44,8 @@ export function readCliArgs(argv: string[]) { debug: true, rpm: null, deb: null, - docker: null, + 'docker-images': null, + 'docker-contexts': null, oss: null, 'version-qualifier': '', }, @@ -69,7 +72,7 @@ export function readCliArgs(argv: string[]) { // In order to build a docker image we always need // to generate all the platforms - if (flags.docker) { + if (flags['docker-images'] || flags['docker-contexts']) { flags['all-platforms'] = true; } @@ -79,7 +82,12 @@ export function readCliArgs(argv: string[]) { } // build all if no flags specified - if (flags.rpm === null && flags.deb === null && flags.docker === null) { + if ( + flags.rpm === null && + flags.deb === null && + flags['docker-images'] === null && + flags['docker-contexts'] === null + ) { return true; } @@ -95,8 +103,10 @@ export function readCliArgs(argv: string[]) { createArchives: !Boolean(flags['skip-archives']), createRpmPackage: isOsPackageDesired('rpm'), createDebPackage: isOsPackageDesired('deb'), - createDockerPackage: isOsPackageDesired('docker'), - createDockerUbiPackage: isOsPackageDesired('docker') && !Boolean(flags['skip-docker-ubi']), + createDockerCentOS: + isOsPackageDesired('docker-images') && !Boolean(flags['skip-docker-centos']), + createDockerUBI: isOsPackageDesired('docker-images') && !Boolean(flags['skip-docker-ubi']), + createDockerContexts: isOsPackageDesired('docker-contexts'), targetAllPlatforms: Boolean(flags['all-platforms']), }; diff --git a/src/dev/build/build_distributables.ts b/src/dev/build/build_distributables.ts index 6620673829711..df4ba45517cc1 100644 --- a/src/dev/build/build_distributables.ts +++ b/src/dev/build/build_distributables.ts @@ -19,8 +19,9 @@ export interface BuildOptions { createArchives: boolean; createRpmPackage: boolean; createDebPackage: boolean; - createDockerPackage: boolean; - createDockerUbiPackage: boolean; + createDockerUBI: boolean; + createDockerCentOS: boolean; + createDockerContexts: boolean; versionQualifier: string | undefined; targetAllPlatforms: boolean; } @@ -95,12 +96,19 @@ export async function buildDistributables(log: ToolingLog, options: BuildOptions // control w/ --rpm or --skip-os-packages await run(Tasks.CreateRpmPackage); } - if (options.createDockerPackage) { - // control w/ --docker or --skip-docker-ubi or --skip-os-packages - await run(Tasks.CreateDockerPackage); - if (options.createDockerUbiPackage) { - await run(Tasks.CreateDockerUbiPackage); - } + if (options.createDockerUBI) { + // control w/ --docker-images or --skip-docker-ubi or --skip-os-packages + await run(Tasks.CreateDockerUBI); + } + + if (options.createDockerCentOS) { + // control w/ --docker-images or --skip-docker-centos or --skip-os-packages + await run(Tasks.CreateDockerCentOS); + } + + if (options.createDockerContexts) { + // control w/ --docker-contexts or --skip-os-packages + await run(Tasks.CreateDockerContexts); } /** diff --git a/src/dev/build/cli.ts b/src/dev/build/cli.ts index ca9debfdc1ae1..3e3a0a493f2d1 100644 --- a/src/dev/build/cli.ts +++ b/src/dev/build/cli.ts @@ -38,10 +38,12 @@ if (showHelp) { --skip-archives {dim Don't produce tar/zip archives} --skip-os-packages {dim Don't produce rpm/deb/docker packages} --all-platforms {dim Produce archives for all platforms, not just this one} - --rpm {dim Only build the rpm package} - --deb {dim Only build the deb package} - --docker {dim Only build the docker image} + --rpm {dim Only build the rpm packages} + --deb {dim Only build the deb packages} + --docker-images {dim Only build the Docker images} + --docker-contexts {dim Only build the Docker build contexts} --skip-docker-ubi {dim Don't build the docker ubi image} + --skip-docker-centos {dim Don't build the docker centos image} --release {dim Produce a release-ready distributable} --version-qualifier {dim Suffix version with a qualifier} --skip-node-download {dim Reuse existing downloads of node.js} diff --git a/src/dev/build/tasks/os_packages/create_os_package_tasks.ts b/src/dev/build/tasks/os_packages/create_os_package_tasks.ts index ba57a5f3dbfc9..fd0224d3de13b 100644 --- a/src/dev/build/tasks/os_packages/create_os_package_tasks.ts +++ b/src/dev/build/tasks/os_packages/create_os_package_tasks.ts @@ -8,7 +8,7 @@ import { Task } from '../../lib'; import { runFpm } from './run_fpm'; -import { runDockerGenerator, runDockerGeneratorForUBI } from './docker_generator'; +import { runDockerGenerator } from './docker_generator'; export const CreateDebPackage: Task = { description: 'Creating deb package', @@ -49,20 +49,56 @@ export const CreateRpmPackage: Task = { }, }; -export const CreateDockerPackage: Task = { - description: 'Creating docker package', +export const CreateDockerCentOS: Task = { + description: 'Creating Docker CentOS image', async run(config, log, build) { - // Builds Docker targets for default and oss - await runDockerGenerator(config, log, build); + await runDockerGenerator(config, log, build, { + ubi: false, + context: false, + architecture: 'x64', + image: true, + }); + await runDockerGenerator(config, log, build, { + ubi: false, + context: false, + architecture: 'aarch64', + image: true, + }); }, }; -export const CreateDockerUbiPackage: Task = { - description: 'Creating docker ubi package', +export const CreateDockerUBI: Task = { + description: 'Creating Docker UBI image', async run(config, log, build) { - // Builds Docker target default with ubi7 base image - await runDockerGeneratorForUBI(config, log, build); + if (!build.isOss()) { + await runDockerGenerator(config, log, build, { + ubi: true, + context: false, + architecture: 'x64', + image: true, + }); + } + }, +}; + +export const CreateDockerContexts: Task = { + description: 'Creating Docker build contexts', + + async run(config, log, build) { + await runDockerGenerator(config, log, build, { + ubi: false, + context: true, + image: false, + }); + + if (!build.isOss()) { + await runDockerGenerator(config, log, build, { + ubi: true, + context: true, + image: false, + }); + } }, }; diff --git a/src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.ts b/src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.ts index 07a86927d5a35..4780457fe8054 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/bundle_dockerfiles.ts @@ -18,7 +18,6 @@ export async function bundleDockerFiles(config: Config, log: ToolingLog, scope: log.info( `Generating kibana${scope.imageFlavor}${scope.ubiImageFlavor} docker build context bundle` ); - const dockerFilesDirName = `kibana${scope.imageFlavor}${scope.ubiImageFlavor}-${scope.version}-docker-build-context`; const dockerFilesBuildDir = resolve(scope.dockerBuildDir, dockerFilesDirName); const dockerFilesOutputDir = config.resolveFromTarget(`${dockerFilesDirName}.tar.gz`); diff --git a/src/dev/build/tasks/os_packages/docker_generator/index.ts b/src/dev/build/tasks/os_packages/docker_generator/index.ts index 1e6e6156c3ed9..229bd5242228c 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/index.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/index.ts @@ -6,4 +6,4 @@ * Public License, v 1. */ -export { runDockerGenerator, runDockerGeneratorForUBI } from './run'; +export { runDockerGenerator } from './run'; diff --git a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker index 30e3b60dcee83..1598f00354bf8 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker +++ b/src/dev/build/tasks/os_packages/docker_generator/resources/bin/kibana-docker @@ -15,11 +15,17 @@ # --elasticsearch.logQueries=true kibana_vars=( + apm_oss.apmAgentConfigurationIndex + apm_oss.errorIndices + apm_oss.indexPattern + apm_oss.metricsIndices + apm_oss.onboardingIndices + apm_oss.sourcemapIndices + apm_oss.spanIndices + apm_oss.transactionIndices console.enabled console.proxyConfig console.proxyFilter - ops.cGroupOverrides.cpuPath - ops.cGroupOverrides.cpuAcctPath cpu.cgroup.path.override cpuacct.cgroup.path.override csp.rules @@ -41,10 +47,10 @@ kibana_vars=( elasticsearch.ssl.certificateAuthorities elasticsearch.ssl.key elasticsearch.ssl.keyPassphrase - elasticsearch.ssl.keystore.path elasticsearch.ssl.keystore.password - elasticsearch.ssl.truststore.path + elasticsearch.ssl.keystore.path elasticsearch.ssl.truststore.password + elasticsearch.ssl.truststore.path elasticsearch.ssl.verificationMode elasticsearch.username enterpriseSearch.accessCheckTimeout @@ -76,34 +82,42 @@ kibana_vars=( map.tilemap.options.minZoom map.tilemap.options.subdomains map.tilemap.url + migrations.batchSize + migrations.enableV2 + migrations.pollInterval + migrations.scrollDuration + migrations.skip monitoring.cluster_alerts.email_notifications.email_address monitoring.enabled monitoring.kibana.collection.enabled monitoring.kibana.collection.interval monitoring.ui.container.elasticsearch.enabled monitoring.ui.container.logstash.enabled - monitoring.ui.elasticsearch.password - monitoring.ui.elasticsearch.pingTimeout monitoring.ui.elasticsearch.hosts - monitoring.ui.elasticsearch.username monitoring.ui.elasticsearch.logFetchCount + monitoring.ui.elasticsearch.password + monitoring.ui.elasticsearch.pingTimeout monitoring.ui.elasticsearch.ssl.certificateAuthorities monitoring.ui.elasticsearch.ssl.verificationMode + monitoring.ui.elasticsearch.username monitoring.ui.enabled monitoring.ui.max_bucket_size monitoring.ui.min_interval_seconds newsfeed.enabled + ops.cGroupOverrides.cpuAcctPath + ops.cGroupOverrides.cpuPath ops.interval path.data pid.file regionmap security.showInsecureClusterWarning server.basePath - server.customResponseHeaders server.compression.enabled server.compression.referrerWhitelist server.cors server.cors.origin + server.customResponseHeaders + server.customResponseHeaders server.defaultRoute server.host server.keepAliveTimeout @@ -117,20 +131,24 @@ kibana_vars=( server.ssl.certificateAuthorities server.ssl.cipherSuites server.ssl.clientAuthentication - server.customResponseHeaders server.ssl.enabled server.ssl.key server.ssl.keyPassphrase - server.ssl.keystore.path server.ssl.keystore.password - server.ssl.truststore.path - server.ssl.truststore.password + server.ssl.keystore.path server.ssl.redirectHttpFromPort server.ssl.supportedProtocols + server.ssl.truststore.password + server.ssl.truststore.path server.xsrf.disableProtection server.xsrf.whitelist status.allowAnonymous status.v6ApiFormat + telemetry.allowChangingOptInStatus + telemetry.enabled + telemetry.optIn + telemetry.optInStatusUrl + telemetry.sendUsageFrom tilemap.options.attribution tilemap.options.maxZoom tilemap.options.minZoom @@ -142,9 +160,9 @@ kibana_vars=( xpack.actions.enabled xpack.actions.enabledActionTypes xpack.actions.preconfigured - xpack.actions.proxyUrl xpack.actions.proxyHeaders xpack.actions.proxyRejectUnauthorizedCertificates + xpack.actions.proxyUrl xpack.actions.rejectUnauthorized xpack.alerts.healthCheck.interval xpack.alerts.invalidateApiKeysTask.interval @@ -154,37 +172,29 @@ kibana_vars=( xpack.apm.ui.enabled xpack.apm.ui.maxTraceItems xpack.apm.ui.transactionGroupBucketSize - apm_oss.apmAgentConfigurationIndex - apm_oss.indexPattern - apm_oss.errorIndices - apm_oss.onboardingIndices - apm_oss.spanIndices - apm_oss.sourcemapIndices - apm_oss.transactionIndices - apm_oss.metricsIndices xpack.canvas.enabled - xpack.code.ui.enabled xpack.code.disk.thresholdEnabled xpack.code.disk.watermarkLow - xpack.code.maxWorkspace xpack.code.indexRepoFrequencyMs - xpack.code.updateRepoFrequencyMs xpack.code.lsp.verbose - xpack.code.verbose + xpack.code.maxWorkspace xpack.code.security.enableGitCertCheck xpack.code.security.gitHostWhitelist xpack.code.security.gitProtocolWhitelist + xpack.code.ui.enabled + xpack.code.updateRepoFrequencyMs + xpack.code.verbose xpack.encryptedSavedObjects.encryptionKey xpack.encryptedSavedObjects.keyRotation.decryptionOnlyKeys xpack.event_log.enabled - xpack.event_log.logEntries xpack.event_log.indexEntries + xpack.event_log.logEntries xpack.fleet.agents.elasticsearch.host xpack.fleet.agents.kibana.host xpack.fleet.agents.tlsCheckDisabled xpack.fleet.registryUrl - xpack.graph.enabled xpack.graph.canEditDrillDownUrls + xpack.graph.enabled xpack.graph.savePolicy xpack.grokdebugger.enabled xpack.infra.enabled @@ -208,28 +218,28 @@ kibana_vars=( xpack.reporting.capture.browser.chromium.disableSandbox xpack.reporting.capture.browser.chromium.inspect xpack.reporting.capture.browser.chromium.maxScreenshotDimension + xpack.reporting.capture.browser.chromium.proxy.bypass xpack.reporting.capture.browser.chromium.proxy.enabled xpack.reporting.capture.browser.chromium.proxy.server - xpack.reporting.capture.browser.chromium.proxy.bypass xpack.reporting.capture.browser.type xpack.reporting.capture.concurrency xpack.reporting.capture.loadDelay + xpack.reporting.capture.maxAttempts xpack.reporting.capture.settleTime xpack.reporting.capture.timeout + xpack.reporting.capture.timeouts.openUrl + xpack.reporting.capture.timeouts.renderComplete + xpack.reporting.capture.timeouts.waitForElements xpack.reporting.capture.viewport.height xpack.reporting.capture.viewport.width xpack.reporting.capture.zoom xpack.reporting.csv.checkForFormulas - xpack.reporting.csv.escapeFormulaValues xpack.reporting.csv.enablePanelActionDownload - xpack.reporting.csv.useByteOrderMarkEncoding + xpack.reporting.csv.escapeFormulaValues xpack.reporting.csv.maxSizeBytes xpack.reporting.csv.scroll.duration xpack.reporting.csv.scroll.size - xpack.reporting.capture.maxAttempts - xpack.reporting.capture.timeouts.openUrl - xpack.reporting.capture.timeouts.waitForElements - xpack.reporting.capture.timeouts.renderComplete + xpack.reporting.csv.useByteOrderMarkEncoding xpack.reporting.enabled xpack.reporting.encryptionKey xpack.reporting.index @@ -248,43 +258,38 @@ kibana_vars=( xpack.reporting.queue.timeout xpack.reporting.roles.allow xpack.rollup.enabled - xpack.security.audit.enabled xpack.searchprofiler.enabled - xpack.security.authc.providers + xpack.security.audit.enabled xpack.security.authc.oidc.realm - xpack.security.authc.saml.realm + xpack.security.authc.providers xpack.security.authc.saml.maxRedirectURLSize + xpack.security.authc.saml.realm xpack.security.authc.selector.enabled xpack.security.cookieName xpack.security.enabled xpack.security.encryptionKey + xpack.security.loginAssistanceMessage + xpack.security.loginHelp xpack.security.sameSiteCookies xpack.security.secureCookies - xpack.security.sessionTimeout + xpack.security.session.cleanupInterval xpack.security.session.idleTimeout xpack.security.session.lifespan - xpack.security.session.cleanupInterval - xpack.security.loginAssistanceMessage - xpack.security.loginHelp + xpack.security.sessionTimeout xpack.spaces.enabled xpack.spaces.maxSpaces xpack.task_manager.enabled + xpack.task_manager.index xpack.task_manager.max_attempts - xpack.task_manager.poll_interval xpack.task_manager.max_poll_inactivity_cycles - xpack.task_manager.request_capacity - xpack.task_manager.index xpack.task_manager.max_workers - xpack.task_manager.monitored_stats_required_freshness xpack.task_manager.monitored_aggregated_stats_refresh_rate + xpack.task_manager.monitored_stats_required_freshness xpack.task_manager.monitored_stats_running_average_window xpack.task_manager.monitored_task_execution_thresholds + xpack.task_manager.poll_interval + xpack.task_manager.request_capacity xpack.task_manager.version_conflict_threshold - telemetry.allowChangingOptInStatus - telemetry.enabled - telemetry.optIn - telemetry.optInStatusUrl - telemetry.sendUsageFrom ) longopts='' diff --git a/src/dev/build/tasks/os_packages/docker_generator/run.ts b/src/dev/build/tasks/os_packages/docker_generator/run.ts index 26a6a9d6e4a03..c92de567cb446 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/run.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/run.ts @@ -26,19 +26,26 @@ export async function runDockerGenerator( config: Config, log: ToolingLog, build: Build, - ubi: boolean = false + flags: { + architecture?: string; + context: boolean; + image: boolean; + ubi: boolean; + } ) { // UBI var config - const baseOSImage = ubi ? 'docker.elastic.co/ubi8/ubi-minimal:latest' : 'centos:8'; + const baseOSImage = flags.ubi ? 'docker.elastic.co/ubi8/ubi-minimal:latest' : 'centos:8'; const ubiVersionTag = 'ubi8'; - const ubiImageFlavor = ubi ? `-${ubiVersionTag}` : ''; + const ubiImageFlavor = flags.ubi ? `-${ubiVersionTag}` : ''; // General docker var config const license = build.isOss() ? 'ASL 2.0' : 'Elastic License'; const imageFlavor = build.isOss() ? '-oss' : ''; const imageTag = 'docker.elastic.co/kibana/kibana'; const version = config.getBuildVersion(); - const artifactTarball = `kibana${imageFlavor}-${version}-linux-x86_64.tar.gz`; + const artifactArchitecture = flags.architecture === 'aarch64' ? 'aarch64' : 'x86_64'; + const artifactPrefix = `kibana${imageFlavor}-${version}-linux`; + const artifactTarball = `${artifactPrefix}-${artifactArchitecture}.tar.gz`; const artifactsDir = config.resolveFromTarget('.'); const dockerBuildDate = new Date().toISOString(); // That would produce oss, default and default-ubi7 @@ -47,10 +54,12 @@ export async function runDockerGenerator( 'kibana-docker', build.isOss() ? `oss` : `default${ubiImageFlavor}` ); + const imageArchitecture = flags.architecture === 'aarch64' ? '-aarch64' : ''; const dockerTargetFilename = config.resolveFromTarget( - `kibana${imageFlavor}${ubiImageFlavor}-${version}-docker-image.tar.gz` + `kibana${imageFlavor}${ubiImageFlavor}-${version}-docker-image${imageArchitecture}.tar.gz` ); const scope: TemplateContext = { + artifactPrefix, artifactTarball, imageFlavor, version, @@ -62,7 +71,8 @@ export async function runDockerGenerator( baseOSImage, ubiImageFlavor, dockerBuildDate, - ubi, + ubi: flags.ubi, + architecture: flags.architecture, revision: config.getBuildSha(), }; @@ -106,20 +116,23 @@ export async function runDockerGenerator( // created from the templates/build_docker_sh.template.js // and we just run that bash script await chmodAsync(`${resolve(dockerBuildDir, 'build_docker.sh')}`, '755'); - await exec(log, `./build_docker.sh`, [], { - cwd: dockerBuildDir, - level: 'info', - }); - - // Pack Dockerfiles and create a target for them - await bundleDockerFiles(config, log, scope); -} -export async function runDockerGeneratorForUBI(config: Config, log: ToolingLog, build: Build) { - // Only run ubi docker image build for default distribution - if (build.isOss()) { - return; + // Only build images on native targets + type HostArchitectureToDocker = Record; + const hostTarget: HostArchitectureToDocker = { + x64: 'x64', + arm64: 'aarch64', + }; + const buildImage = hostTarget[process.arch] === flags.architecture && flags.image; + if (buildImage) { + await exec(log, `./build_docker.sh`, [], { + cwd: dockerBuildDir, + level: 'info', + }); } - await runDockerGenerator(config, log, build, true); + // Pack Dockerfiles and create a target for them + if (flags.context) { + await bundleDockerFiles(config, log, scope); + } } diff --git a/src/dev/build/tasks/os_packages/docker_generator/template_context.ts b/src/dev/build/tasks/os_packages/docker_generator/template_context.ts index 4805ba0ba9bc0..8de2b5e9361e5 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/template_context.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/template_context.ts @@ -7,6 +7,7 @@ */ export interface TemplateContext { + artifactPrefix: string; artifactTarball: string; imageFlavor: string; version: string; @@ -21,4 +22,5 @@ export interface TemplateContext { usePublicArtifact?: boolean; ubi: boolean; revision: string; + architecture?: string; } diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile b/src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile index 1a0e325a2486a..eb4708b6ac555 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/Dockerfile @@ -17,17 +17,19 @@ RUN {{packageManager}} install -y findutils tar gzip {{#usePublicArtifact}} RUN cd /opt && \ - curl --retry 8 -s -L -O https://artifacts.elastic.co/downloads/kibana/{{artifactTarball}} && \ + curl --retry 8 -s -L \ + --output kibana.tar.gz \ + https://artifacts.elastic.co/downloads/kibana/{{artifactPrefix}}-$(arch).tar.gz && \ cd - {{/usePublicArtifact}} {{^usePublicArtifact}} -COPY {{artifactTarball}} /opt +COPY {{artifactTarball}} /opt/kibana.tar.gz {{/usePublicArtifact}} RUN mkdir /usr/share/kibana WORKDIR /usr/share/kibana -RUN tar --strip-components=1 -zxf /opt/{{artifactTarball}} +RUN tar --strip-components=1 -zxf /opt/kibana.tar.gz # Ensure that group permissions are the same as user permissions. # This will help when relying on GID-0 to run Kibana, rather than UID-1000. # OpenShift does this, for example. @@ -51,7 +53,7 @@ EXPOSE 5601 RUN for iter in {1..10}; do \ {{packageManager}} update --setopt=tsflags=nodocs -y && \ {{packageManager}} install --setopt=tsflags=nodocs -y \ - fontconfig freetype shadow-utils libnss3.so {{#ubi}}findutils{{/ubi}} && \ + fontconfig freetype shadow-utils nss {{#ubi}}findutils{{/ubi}} && \ {{packageManager}} clean all && exit_code=0 && break || exit_code=$? && echo "{{packageManager}} error: retry $iter in 10s" && \ sleep 10; \ done; \ @@ -59,8 +61,17 @@ RUN for iter in {1..10}; do \ # Add an init process, check the checksum to make sure it's a match RUN set -e ; \ + TINI_BIN="" ; \ + case "$(arch)" in \ + aarch64) \ + TINI_BIN='tini-arm64' ; \ + ;; \ + x86_64) \ + TINI_BIN='tini-amd64' ; \ + ;; \ + *) echo >&2 "Unsupported architecture $(arch)" ; exit 1 ;; \ + esac ; \ TINI_VERSION='v0.19.0' ; \ - TINI_BIN='tini-amd64' ; \ curl --retry 8 -S -L -O "https://github.com/krallin/tini/releases/download/${TINI_VERSION}/${TINI_BIN}" ; \ curl --retry 8 -S -L -O "https://github.com/krallin/tini/releases/download/${TINI_VERSION}/${TINI_BIN}.sha256sum" ; \ sha256sum -c "${TINI_BIN}.sha256sum" ; \ diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.ts b/src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.ts index 5e2a0b72769fe..93c5f82aa1e42 100644 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/build_docker_sh.template.ts @@ -17,7 +17,9 @@ function generator({ dockerTargetFilename, baseOSImage, ubiImageFlavor, + architecture, }: TemplateContext) { + const fileArchitecture = architecture === 'aarch64' ? 'arm64' : 'amd64'; return dedent(` #!/usr/bin/env bash # @@ -54,9 +56,9 @@ function generator({ retry_docker_pull ${baseOSImage} echo "Building: kibana${imageFlavor}${ubiImageFlavor}-docker"; \\ - docker build -t ${imageTag}${imageFlavor}${ubiImageFlavor}:${version} -f Dockerfile . || exit 1; + docker build -t ${imageTag}${imageFlavor}${ubiImageFlavor}:${version}-${fileArchitecture} -f Dockerfile . || exit 1; - docker save ${imageTag}${imageFlavor}${ubiImageFlavor}:${version} | gzip -c > ${dockerTargetFilename} + docker save ${imageTag}${imageFlavor}${ubiImageFlavor}:${version}-${fileArchitecture} | gzip -c > ${dockerTargetFilename} exit 0 `); diff --git a/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts b/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts index a5cc2d9527cbf..f4e9d11ca9c21 100755 --- a/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts +++ b/src/dev/build/tasks/os_packages/docker_generator/templates/dockerfile.template.ts @@ -16,6 +16,7 @@ function generator(options: TemplateContext) { const template = readFileSync(resolve(__dirname, './Dockerfile')); return Mustache.render(template.toString(), { packageManager: options.ubiImageFlavor ? 'microdnf' : 'yum', + tiniBin: options.architecture === 'aarch64' ? 'tini-arm64' : 'tini-amd64', ...options, }); } diff --git a/src/dev/ci_setup/.bazelrc-ci b/src/dev/ci_setup/.bazelrc-ci new file mode 100644 index 0000000000000..5b345d3c9e207 --- /dev/null +++ b/src/dev/ci_setup/.bazelrc-ci @@ -0,0 +1,10 @@ +# Inspired on https://github.com/angular/angular/blob/master/.circleci/bazel.linux.rc +# These options are only enabled when running on CI +# That is done by copying this file into "$HOME/.bazelrc" which loads after the .bazelrc into the workspace + +# Import and load bazelrc common settings for ci env +try-import %workspace%/src/dev/ci_setup/.bazelrc-ci.common + +# Remote cache settings for ci env +# build --google_default_credentials +# build --remote_upload_local_results=true diff --git a/src/dev/ci_setup/.bazelrc-ci.common b/src/dev/ci_setup/.bazelrc-ci.common new file mode 100644 index 0000000000000..3f58e4e03a178 --- /dev/null +++ b/src/dev/ci_setup/.bazelrc-ci.common @@ -0,0 +1,11 @@ +# Inspired on https://github.com/angular/angular/blob/master/.circleci/bazel.common.rc +# Settings in this file should be OS agnostic + +# Don't be spammy in the logs +build --noshow_progress + +# Print all the options that apply to the build. +build --announce_rc + +# More details on failures +build --verbose_failures=true diff --git a/src/dev/ci_setup/setup.sh b/src/dev/ci_setup/setup.sh index 61f578ba33971..e5e21e312b0dd 100755 --- a/src/dev/ci_setup/setup.sh +++ b/src/dev/ci_setup/setup.sh @@ -65,3 +65,8 @@ if [ "$GIT_CHANGES" ]; then echo -e "$GIT_CHANGES\n" exit 1 fi + +### +### copy .bazelrc-ci into $HOME/.bazelrc +### +cp "src/dev/ci_setup/.bazelrc-ci" "$HOME/.bazelrc"; diff --git a/src/dev/precommit_hook/casing_check_config.js b/src/dev/precommit_hook/casing_check_config.js index 1a870e3c878f7..d18e2e49d6b83 100644 --- a/src/dev/precommit_hook/casing_check_config.js +++ b/src/dev/precommit_hook/casing_check_config.js @@ -65,6 +65,10 @@ export const IGNORE_FILE_GLOBS = [ 'x-pack/test/fleet_api_integration/apis/fixtures/test_packages/**/*', '.teamcity/**/*', + + // Bazel default files + '**/WORKSPACE.bazel', + '**/BUILD.bazel', ]; /** diff --git a/src/dev/typescript/run_type_check_cli.ts b/src/dev/typescript/run_type_check_cli.ts index c1a25a8e4bdee..6e7ea6fcf2405 100644 --- a/src/dev/typescript/run_type_check_cli.ts +++ b/src/dev/typescript/run_type_check_cli.ts @@ -61,7 +61,7 @@ export async function runTypeCheckCli() { Options: --project [path] {dim Path to a tsconfig.json file determines the project to check} - --skip-lib-check {dim Skip type checking of all declaration files (*.d.ts)} + --skip-lib-check {dim Skip type checking of all declaration files (*.d.ts). Default is false} --help {dim Show this message} `) ); @@ -77,7 +77,9 @@ export async function runTypeCheckCli() { ...['--emitDeclarationOnly', 'false'], '--noEmit', '--pretty', - ...(opts['skip-lib-check'] ? ['--skipLibCheck'] : []), + ...(opts['skip-lib-check'] + ? ['--skipLibCheck', opts['skip-lib-check']] + : ['--skipLibCheck', 'false']), ]; const projects = filterProjectsByFlag(opts.project).filter((p) => !p.disableTypeCheck); diff --git a/src/plugins/apm_oss/server/tutorial/index_pattern.json b/src/plugins/apm_oss/server/tutorial/index_pattern.json index 6eb040f2758af..93a2393b70fa4 100644 --- a/src/plugins/apm_oss/server/tutorial/index_pattern.json +++ b/src/plugins/apm_oss/server/tutorial/index_pattern.json @@ -1,7 +1,7 @@ { "attributes": { "fieldFormatMap": "{\"client.bytes\":{\"id\":\"bytes\"},\"client.nat.port\":{\"id\":\"string\"},\"client.port\":{\"id\":\"string\"},\"destination.bytes\":{\"id\":\"bytes\"},\"destination.nat.port\":{\"id\":\"string\"},\"destination.port\":{\"id\":\"string\"},\"event.duration\":{\"id\":\"duration\",\"params\":{\"inputFormat\":\"nanoseconds\",\"outputFormat\":\"asMilliseconds\",\"outputPrecision\":1}},\"event.sequence\":{\"id\":\"string\"},\"event.severity\":{\"id\":\"string\"},\"http.request.body.bytes\":{\"id\":\"bytes\"},\"http.request.bytes\":{\"id\":\"bytes\"},\"http.response.body.bytes\":{\"id\":\"bytes\"},\"http.response.bytes\":{\"id\":\"bytes\"},\"http.response.status_code\":{\"id\":\"string\"},\"log.syslog.facility.code\":{\"id\":\"string\"},\"log.syslog.priority\":{\"id\":\"string\"},\"network.bytes\":{\"id\":\"bytes\"},\"package.size\":{\"id\":\"string\"},\"process.parent.pgid\":{\"id\":\"string\"},\"process.parent.pid\":{\"id\":\"string\"},\"process.parent.ppid\":{\"id\":\"string\"},\"process.parent.thread.id\":{\"id\":\"string\"},\"process.pgid\":{\"id\":\"string\"},\"process.pid\":{\"id\":\"string\"},\"process.ppid\":{\"id\":\"string\"},\"process.thread.id\":{\"id\":\"string\"},\"server.bytes\":{\"id\":\"bytes\"},\"server.nat.port\":{\"id\":\"string\"},\"server.port\":{\"id\":\"string\"},\"source.bytes\":{\"id\":\"bytes\"},\"source.nat.port\":{\"id\":\"string\"},\"source.port\":{\"id\":\"string\"},\"system.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.memory.actual.free\":{\"id\":\"bytes\"},\"system.memory.total\":{\"id\":\"bytes\"},\"system.process.cgroup.memory.mem.limit.bytes\":{\"id\":\"bytes\"},\"system.process.cgroup.memory.mem.usage.bytes\":{\"id\":\"bytes\"},\"system.process.cpu.total.norm.pct\":{\"id\":\"percent\"},\"system.process.memory.rss.bytes\":{\"id\":\"bytes\"},\"system.process.memory.size\":{\"id\":\"bytes\"},\"url.port\":{\"id\":\"string\"},\"view spans\":{\"id\":\"url\",\"params\":{\"labelTemplate\":\"View Spans\"}}}", - "fields": "[{\"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\":\"labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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\":\"agent.build.original\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.organization.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.organization.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.domain\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.registered_domain\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.email\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.full_name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.account.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.account.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.availability_zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.machine.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.project.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.project.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.region\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.tag\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.runtime\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.organization.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.domain\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.registered_domain\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.email\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.full_name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.original_file_name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.data\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.ttl\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.header_flags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.op_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.resolved_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.response_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"ecs.version\",\"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\":\"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\":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.stack_trace\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.stack_trace.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"error.type\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.dataset\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.end\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.ingested\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.kind\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"event.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.outcome\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.reason\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score_norm\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.sequence\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.severity\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.timezone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.url\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.accessed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.attributes\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.ctime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.device\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.directory\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.drive_letter\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.gid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.group\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.inode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mime_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mtime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.owner\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.path\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.path.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.original_file_name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.target_path\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.target_path.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.alternative_names\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.distinguished_name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.public_key_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.public_key_curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"file.x509.public_key_exponent\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.public_key_size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.signature_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.distinguished_name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.version_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha512\",\"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.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.hostname\",\"scripted\":false,\"searchable\":true},{\"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.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"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\":\"host.name\",\"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.os.full\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.full.text\",\"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.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.name.text\",\"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.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.email\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.full_name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.content\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.content.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.method\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.mime_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.referrer\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.content\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.content.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.mime_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.status_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"interface.alias\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"interface.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"interface.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.file.path\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.logger\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"log.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.priority\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.application\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.community_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.direction\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.forwarded_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.iana_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.inner\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.inner.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.inner.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.transport\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.interface.alias\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.interface.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.interface.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.interface.alias\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.interface.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.interface.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.full\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.vendor\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.full\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.build_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.checksum\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.install_scope\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.installed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.license\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.original_file_name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.args\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.args_count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.command_line\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.command_line.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.entity_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.executable\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.executable.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.exit_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.args\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.args_count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.command_line\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.command_line.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.entity_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.executable\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.executable.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.exit_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.original_file_name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pgid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.ppid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.thread.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.thread.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.title\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.title.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.working_directory\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.working_directory.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.original_file_name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pgid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.ppid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.title\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.title.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.working_directory\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.working_directory.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.data.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.data.strings\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.data.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.hive\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.key\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.path\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.value\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.hosts\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.user\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.author\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.license\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.ruleset\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.uuid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.organization.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.domain\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.registered_domain\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.email\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.full_name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.state\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.organization.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.domain\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.registered_domain\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.email\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.full_name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.framework\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.subtechnique.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.subtechnique.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.subtechnique.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.subtechnique.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.cipher\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.certificate\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.certificate_chain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.issuer\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.ja3\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.server_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.subject\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.supported_ciphers\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.alternative_names\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.distinguished_name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.public_key_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.public_key_curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"tls.client.x509.public_key_exponent\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.public_key_size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.signature_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.distinguished_name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.version_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.established\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.next_protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.resumed\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.certificate\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.certificate_chain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.issuer\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.ja3s\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.subject\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.alternative_names\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.distinguished_name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.public_key_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.public_key_curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"tls.server.x509.public_key_exponent\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.public_key_size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.signature_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.distinguished_name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.version_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.version_protocol\",\"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\":\"string\"},{\"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\":\"transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.domain\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.fragment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.full\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.original\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.original.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.password\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.path\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.query\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.registered_domain\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.scheme\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.username\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.changes.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.changes.email\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.changes.full_name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.changes.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.changes.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.changes.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.changes.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.changes.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.changes.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.changes.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.changes.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.changes.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.effective.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.effective.email\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.effective.full_name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.effective.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.effective.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.effective.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.effective.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.effective.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.effective.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.effective.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.effective.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.effective.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.email\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.full_name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.target.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.target.email\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.target.full_name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.target.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.target.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.target.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.target.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.target.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.target.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.target.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.target.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.target.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.device.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.full\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.classification\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.description.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.enumeration\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.report_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.scanner.vendor\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.base\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.environmental\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.temporal\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.severity\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.alternative_names\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.distinguished_name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.public_key_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.public_key_curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"x509.public_key_exponent\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.public_key_size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.signature_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.distinguished_name\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.version_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"fields\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"timeseries.instance\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.image.id\",\"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.containerized\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.build\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.codename\",\"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.node.hostname\",\"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.replicaset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.deployment.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.statefulset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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\":\"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\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"timestamp.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.request.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.finished\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.response.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.environment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.sampled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"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.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.breakdown.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"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\":\"observer.listening\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version_major\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"experimental\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"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,\"type\":\"boolean\"},{\"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\":\"transaction.root\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"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.subtype\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"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\":\"system.process.cgroup.memory.mem.limit.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.cgroup.memory.mem.usage.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.cpu.ns\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.samples.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.alloc_objects.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.alloc_space.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.inuse_objects.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.inuse_space.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.filename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.filename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.line\",\"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\":\"child.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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.action\",\"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.sync\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.db.link\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.db.rows_affected\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.resource\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.message.queue.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.message.age.ms\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"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.*.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.cls\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.fid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.tbt\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.longtask.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.longtask.sum\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.longtask.max\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.span_count.dropped\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.message.queue.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.message.age.ms\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.histogram\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"metricset.period\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.response_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.response_time.sum.us\",\"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\":\"@timestamp\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"message\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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\":\"agent.build.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"client.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.account.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.account.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.availability_zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.instance.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.machine.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.project.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.project.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.region\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.image.tag\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.labels\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"container.runtime\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"destination.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dll.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.data\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.ttl\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.answers.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.header_flags\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.op_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.class\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.question.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.resolved_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.response_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"dns.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"ecs.version\",\"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\":\"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\":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\":false,\"name\":\"error.stack_trace\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"error.stack_trace.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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\":\"event.action\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.dataset\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.end\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.ingested\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.kind\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.module\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"event.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.outcome\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.provider\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.reason\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.risk_score_norm\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.sequence\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.severity\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.timezone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"event.url\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.accessed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.attributes\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.created\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.ctime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.device\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.drive_letter\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.gid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.group\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.inode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mime_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mode\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.mtime\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.owner\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.path.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.target_path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.target_path.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.uid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.alternative_names\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.issuer.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.public_key_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.public_key_curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"file.x509.public_key_exponent\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.public_key_size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.signature_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.subject.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"file.x509.version_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"hash.sha512\",\"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.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.hostname\",\"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.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"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\":\"host.name\",\"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.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.full.text\",\"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.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.name.text\",\"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.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.body.content.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.method\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.mime_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.request.referrer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.content\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.body.content.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.mime_type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.status_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"interface.alias\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"interface.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"interface.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.file.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.level\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.logger\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.file.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.origin.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"log.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.facility.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.priority\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"log.syslog.severity.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.application\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.community_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.direction\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.forwarded_ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.iana_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.inner\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.inner.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.inner.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.transport\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"network.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.interface.alias\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.interface.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.interface.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.egress.zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.interface.alias\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.interface.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.interface.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ingress.zone\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.vendor\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.build_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.checksum\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.install_scope\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.installed\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.license\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"package.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.args\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.args_count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.command_line\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.command_line.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.entity_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.executable\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.executable.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.exit_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.args\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.args_count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.exists\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.status\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.subject_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.trusted\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.code_signature.valid\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.command_line\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.command_line.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.entity_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.executable\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.executable.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.exit_code\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.hash.sha512\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pgid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.pid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.ppid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.thread.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.thread.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.title\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.title.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.working_directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.parent.working_directory.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.architecture\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.company\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.file_version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.imphash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.original_file_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pe.product\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pgid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.pid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.ppid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.start\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.id\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.thread.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.title\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.title.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.uptime\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.working_directory\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"process.working_directory.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.data.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.data.strings\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.data.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.hive\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.key\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"registry.value\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.hosts\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"related.user\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.author\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.license\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.ruleset\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.uuid\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"rule.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"server.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.ephemeral_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.node.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.state\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.address\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.number\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.organization.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.as.organization.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.city_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.continent_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.country_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.location\",\"scripted\":false,\"searchable\":true,\"type\":\"geo_point\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_iso_code\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.geo.region_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.mac\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.ip\",\"scripted\":false,\"searchable\":true,\"type\":\"ip\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.nat.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.packets\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"source.user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.framework\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.tactic.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.subtechnique.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.subtechnique.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.subtechnique.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"threat.technique.subtechnique.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.cipher\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.certificate\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.certificate_chain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.issuer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.ja3\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.server_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.subject\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.supported_ciphers\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.alternative_names\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.issuer.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.public_key_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.public_key_curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"tls.client.x509.public_key_exponent\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.public_key_size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.signature_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.subject.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.client.x509.version_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.established\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.next_protocol\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.resumed\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.certificate\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.certificate_chain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.hash.md5\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.hash.sha1\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.hash.sha256\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.issuer\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.ja3s\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.subject\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.alternative_names\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.issuer.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.public_key_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.public_key_curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"tls.server.x509.public_key_exponent\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.public_key_size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.signature_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.subject.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.server.x509.version_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"tls.version_protocol\",\"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\":\"string\"},{\"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\":\"transaction.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.extension\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.fragment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.original.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.password\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.path\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.port\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.query\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.registered_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.scheme\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.subdomain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.top_level_domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"url.username\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.email\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.full_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.full_name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.domain\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.group.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.hash\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user.roles\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.device.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.original.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.family\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.full\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.full.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.kernel\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.platform\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.os.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"user_agent.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vlan.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vlan.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.category\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.classification\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.description\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.description.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.enumeration\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.reference\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.report_id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.scanner.vendor\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.base\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.environmental\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.temporal\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.score.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"vulnerability.severity\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.alternative_names\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.issuer.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.not_after\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.not_before\",\"scripted\":false,\"searchable\":true,\"type\":\"date\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.public_key_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.public_key_curve\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":false,\"name\":\"x509.public_key_exponent\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.public_key_size\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.serial_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.signature_algorithm\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.common_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.country\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.distinguished_name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.locality\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.organization\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.organizational_unit\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.subject.state_or_province\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"x509.version_number\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"agent.hostname\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"fields\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"timeseries.instance\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"cloud.image.id\",\"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.containerized\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.build\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"host.os.codename\",\"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.node.hostname\",\"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.replicaset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.deployment.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"kubernetes.statefulset.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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\":\"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\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"timestamp.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.request.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"http.response.finished\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":false,\"enabled\":false,\"indexed\":false,\"name\":\"http.response.headers\",\"scripted\":false,\"searchable\":false},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.environment\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.language.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.runtime.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"service.framework.version\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.sampled\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"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.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":false,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.name.text\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.breakdown.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"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\":\"observer.listening\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"observer.version_major\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"experimental\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"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,\"type\":\"boolean\"},{\"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\":\"transaction.root\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"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.subtype\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.self_time.sum.us\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"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\":\"system.process.cgroup.memory.mem.limit.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"system.process.cgroup.memory.mem.usage.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.duration\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.cpu.ns\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.samples.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.alloc_objects.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.alloc_space.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.inuse_objects.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.inuse_space.bytes\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.filename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":1,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.top.line\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.function\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.filename\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"profile.stack.line\",\"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\":\"child.id\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"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.action\",\"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.sync\",\"scripted\":false,\"searchable\":true,\"type\":\"boolean\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.db.link\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.db.rows_affected\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.type\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.resource\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.message.queue.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.message.age.ms\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"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.*.*\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.cls\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.fid\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.tbt\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.longtask.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.longtask.sum\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.experience.longtask.max\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.span_count.dropped\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.message.queue.name\",\"scripted\":false,\"searchable\":true,\"type\":\"string\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.message.age.ms\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"transaction.duration.histogram\",\"scripted\":false,\"searchable\":true},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"metricset.period\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.response_time.count\",\"scripted\":false,\"searchable\":true,\"type\":\"number\"},{\"aggregatable\":true,\"analyzed\":false,\"count\":0,\"doc_values\":true,\"indexed\":true,\"name\":\"span.destination.service.response_time.sum.us\",\"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/plugins/dashboard/public/application/dashboard_app.tsx b/src/plugins/dashboard/public/application/dashboard_app.tsx index e1e2a49439de3..7ea181715717b 100644 --- a/src/plugins/dashboard/public/application/dashboard_app.tsx +++ b/src/plugins/dashboard/public/application/dashboard_app.tsx @@ -6,22 +6,22 @@ * Public License, v 1. */ -import _ from 'lodash'; import { History } from 'history'; -import { merge, Subscription } from 'rxjs'; -import React, { useEffect, useCallback, useState } from 'react'; +import { merge, Subject, Subscription } from 'rxjs'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; +import { debounceTime, tap } from 'rxjs/operators'; import { useKibana } from '../../../kibana_react/public'; import { DashboardConstants } from '../dashboard_constants'; import { DashboardTopNav } from './top_nav/dashboard_top_nav'; import { DashboardAppServices, DashboardEmbedSettings, DashboardRedirect } from './types'; import { + getChangesFromAppStateForContainerState, + getDashboardContainerInput, + getFiltersSubscription, getInputSubscription, getOutputSubscription, - getFiltersSubscription, getSearchSessionIdFromURL, - getDashboardContainerInput, - getChangesFromAppStateForContainerState, } from './dashboard_app_functions'; import { useDashboardBreadcrumbs, @@ -30,11 +30,11 @@ import { useSavedDashboard, } from './hooks'; -import { removeQueryParam } from '../services/kibana_utils'; import { IndexPattern } from '../services/data'; import { EmbeddableRenderer } from '../services/embeddable'; import { DashboardContainerInput } from '.'; import { leaveConfirmStrings } from '../dashboard_strings'; +import { createQueryParamObservable, replaceUrlHashQuery } from '../../../kibana_utils/public'; export interface DashboardAppProps { history: History; @@ -59,7 +59,7 @@ export function DashboardApp({ indexPatterns: indexPatternService, } = useKibana().services; - const [lastReloadTime, setLastReloadTime] = useState(0); + const triggerRefresh$ = useMemo(() => new Subject<{ force?: boolean }>(), []); const [indexPatterns, setIndexPatterns] = useState([]); const savedDashboard = useSavedDashboard(savedDashboardId, history); @@ -68,9 +68,13 @@ export function DashboardApp({ history ); const dashboardContainer = useDashboardContainer(dashboardStateManager, history, false); + const searchSessionIdQuery$ = useMemo( + () => createQueryParamObservable(history, DashboardConstants.SEARCH_SESSION_ID), + [history] + ); const refreshDashboardContainer = useCallback( - (lastReloadRequestTime?: number) => { + (force?: boolean) => { if (!dashboardContainer || !dashboardStateManager) { return; } @@ -80,7 +84,7 @@ export function DashboardApp({ appStateDashboardInput: getDashboardContainerInput({ isEmbeddedExternally: Boolean(embedSettings), dashboardStateManager, - lastReloadRequestTime, + lastReloadRequestTime: force ? Date.now() : undefined, dashboardCapabilities, query: data.query, }), @@ -100,10 +104,35 @@ export function DashboardApp({ const shouldRefetch = Object.keys(changes).some( (changeKey) => !noRefetchKeys.includes(changeKey as keyof DashboardContainerInput) ); - if (getSearchSessionIdFromURL(history)) { - // going away from a background search results - removeQueryParam(history, DashboardConstants.SEARCH_SESSION_ID, true); - } + + const newSearchSessionId: string | undefined = (() => { + // do not update session id if this is irrelevant state change to prevent excessive searches + if (!shouldRefetch) return; + + let searchSessionIdFromURL = getSearchSessionIdFromURL(history); + if (searchSessionIdFromURL) { + if ( + data.search.session.isRestore() && + data.search.session.isCurrentSession(searchSessionIdFromURL) + ) { + // navigating away from a restored session + dashboardStateManager.kbnUrlStateStorage.kbnUrlControls.updateAsync((nextUrl) => { + if (nextUrl.includes(DashboardConstants.SEARCH_SESSION_ID)) { + return replaceUrlHashQuery(nextUrl, (query) => { + delete query[DashboardConstants.SEARCH_SESSION_ID]; + return query; + }); + } + return nextUrl; + }); + searchSessionIdFromURL = undefined; + } else { + data.search.session.restore(searchSessionIdFromURL); + } + } + + return searchSessionIdFromURL ?? data.search.session.start(); + })(); if (changes.viewMode) { setViewMode(changes.viewMode); @@ -111,8 +140,7 @@ export function DashboardApp({ dashboardContainer.updateInput({ ...changes, - // do not start a new session if this is irrelevant state change to prevent excessive searches - ...(shouldRefetch && { searchSessionId: data.search.session.start() }), + ...(newSearchSessionId && { searchSessionId: newSearchSessionId }), }); } }, @@ -159,23 +187,42 @@ export function DashboardApp({ subscriptions.add( merge( ...[timeFilter.getRefreshIntervalUpdate$(), timeFilter.getTimeUpdate$()] - ).subscribe(() => refreshDashboardContainer()) + ).subscribe(() => triggerRefresh$.next()) ); + subscriptions.add( merge( data.search.session.onRefresh$, - data.query.timefilter.timefilter.getAutoRefreshFetch$() + data.query.timefilter.timefilter.getAutoRefreshFetch$(), + searchSessionIdQuery$ ).subscribe(() => { - setLastReloadTime(() => new Date().getTime()); + triggerRefresh$.next({ force: true }); }) ); dashboardStateManager.registerChangeListener(() => { // we aren't checking dirty state because there are changes the container needs to know about // that won't make the dashboard "dirty" - like a view mode change. - refreshDashboardContainer(); + triggerRefresh$.next(); }); + // debounce `refreshDashboardContainer()` + // use `forceRefresh=true` in case at least one debounced trigger asked for it + let forceRefresh: boolean = false; + subscriptions.add( + triggerRefresh$ + .pipe( + tap((trigger) => { + forceRefresh = forceRefresh || (trigger?.force ?? false); + }), + debounceTime(50) + ) + .subscribe(() => { + refreshDashboardContainer(forceRefresh); + forceRefresh = false; + }) + ); + return () => { subscriptions.unsubscribe(); }; @@ -187,6 +234,8 @@ export function DashboardApp({ data.search.session, indexPatternService, dashboardStateManager, + searchSessionIdQuery$, + triggerRefresh$, refreshDashboardContainer, ]); @@ -216,11 +265,6 @@ export function DashboardApp({ }; }, [dashboardStateManager, dashboardContainer, onAppLeave, embeddable]); - // Refresh the dashboard container when lastReloadTime changes - useEffect(() => { - refreshDashboardContainer(lastReloadTime); - }, [lastReloadTime, refreshDashboardContainer]); - return (
{savedDashboard && dashboardStateManager && dashboardContainer && viewMode && ( @@ -242,7 +286,7 @@ export function DashboardApp({ // The user can still request a reload in the query bar, even if the // query is the same, and in that case, we have to explicitly ask for // a reload, since no state changes will cause it. - setLastReloadTime(() => new Date().getTime()); + triggerRefresh$.next({ force: true }); } }} /> diff --git a/src/plugins/dashboard/public/application/dashboard_router.tsx b/src/plugins/dashboard/public/application/dashboard_router.tsx index 9141f2e592fd7..5206c76f50be2 100644 --- a/src/plugins/dashboard/public/application/dashboard_router.tsx +++ b/src/plugins/dashboard/public/application/dashboard_router.tsx @@ -104,6 +104,7 @@ export async function mountApp({ mapsCapabilities: { save: Boolean(coreStart.application.capabilities.maps?.save) }, createShortUrl: Boolean(coreStart.application.capabilities.dashboard.createShortUrl), visualizeCapabilities: { save: Boolean(coreStart.application.capabilities.visualize?.save) }, + storeSearchSession: Boolean(coreStart.application.capabilities.dashboard.storeSearchSession), }, }; diff --git a/src/plugins/dashboard/public/application/dashboard_state_manager.ts b/src/plugins/dashboard/public/application/dashboard_state_manager.ts index 90706a11b8ce2..c52bd1b4d47b8 100644 --- a/src/plugins/dashboard/public/application/dashboard_state_manager.ts +++ b/src/plugins/dashboard/public/application/dashboard_state_manager.ts @@ -72,7 +72,7 @@ export class DashboardStateManager { >; private readonly stateContainerChangeSub: Subscription; private readonly STATE_STORAGE_KEY = '_a'; - private readonly kbnUrlStateStorage: IKbnUrlStateStorage; + public readonly kbnUrlStateStorage: IKbnUrlStateStorage; private readonly stateSyncRef: ISyncStateRef; private readonly history: History; private readonly usageCollection: UsageCollectionSetup | undefined; @@ -596,7 +596,7 @@ export class DashboardStateManager { this.toUrlState(this.stateContainer.get()) ); // immediately forces scheduled updates and changes location - return this.kbnUrlStateStorage.flush({ replace }); + return !!this.kbnUrlStateStorage.kbnUrlControls.flush(replace); } // TODO: find nicer solution for this diff --git a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx index fa520ec22497b..780eb1bad8c2b 100644 --- a/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx +++ b/src/plugins/dashboard/public/application/embeddable/dashboard_container.tsx @@ -89,7 +89,7 @@ export interface InheritedChildInput extends IndexSignature { export type DashboardReactContextValue = KibanaReactContextValue; export type DashboardReactContext = KibanaReactContext; -const defaultCapabilities = { +const defaultCapabilities: DashboardCapabilities = { show: false, createNew: false, saveQuery: false, @@ -97,6 +97,7 @@ const defaultCapabilities = { hideWriteControls: true, mapsCapabilities: { save: false }, visualizeCapabilities: { save: false }, + storeSearchSession: true, }; export class DashboardContainer extends Container { diff --git a/src/plugins/dashboard/public/application/hooks/use_dashboard_state_manager.ts b/src/plugins/dashboard/public/application/hooks/use_dashboard_state_manager.ts index a4044e8668e59..93fbb50950850 100644 --- a/src/plugins/dashboard/public/application/hooks/use_dashboard_state_manager.ts +++ b/src/plugins/dashboard/public/application/hooks/use_dashboard_state_manager.ts @@ -16,6 +16,7 @@ import { useKibana } from '../../services/kibana_react'; import { connectToQueryState, esFilters, + noSearchSessionStorageCapabilityMessage, QueryState, syncQueryStateWithUrl, } from '../../services/data'; @@ -159,13 +160,22 @@ export const useDashboardStateManager = ( stateManager.isNew() ); - searchSession.setSearchSessionInfoProvider( + searchSession.enableStorage( createSessionRestorationDataProvider({ data: dataPlugin, getDashboardTitle: () => dashboardTitle, getDashboardId: () => savedDashboard?.id || '', getAppState: () => stateManager.getAppState(), - }) + }), + { + isDisabled: () => + dashboardCapabilities.storeSearchSession + ? { disabled: false } + : { + disabled: true, + reasonText: noSearchSessionStorageCapabilityMessage, + }, + } ); setDashboardStateManager(stateManager); @@ -192,6 +202,7 @@ export const useDashboardStateManager = ( toasts, uiSettings, usageCollection, + dashboardCapabilities.storeSearchSession, ]); return { dashboardStateManager, viewMode, setViewMode }; diff --git a/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.tsx.snap b/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.tsx.snap index bce8a661634f6..faec6b4f6f24b 100644 --- a/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.tsx.snap +++ b/src/plugins/dashboard/public/application/listing/__snapshots__/dashboard_listing.test.tsx.snap @@ -4,10 +4,16 @@ exports[`after fetch When given a title that matches multiple dashboards, filter - getTableColumns((id) => redirectTo({ destination: 'dashboard', id }), savedObjectsTagging), - [savedObjectsTagging, redirectTo] + getTableColumns( + core.application, + kbnUrlStateStorage, + core.uiSettings.get('state:storeInSessionStorage'), + savedObjectsTagging + ), + [core.application, core.uiSettings, kbnUrlStateStorage, savedObjectsTagging] ); const noItemsFragment = useMemo( @@ -99,7 +103,6 @@ export const DashboardListing = ({ (filter: string) => { let searchTerm = filter; let references: SavedObjectsFindOptionsReference[] | undefined; - if (savedObjectsTagging) { const parsed = savedObjectsTagging.ui.parseSearchQuery(filter, { useName: true, @@ -164,7 +167,9 @@ export const DashboardListing = ({ }; const getTableColumns = ( - redirectTo: (id?: string) => void, + application: ApplicationStart, + kbnUrlStateStorage: IKbnUrlStateStorage, + useHash: boolean, savedObjectsTagging?: SavedObjectsTaggingApi ) => { return [ @@ -172,9 +177,15 @@ const getTableColumns = ( field: 'title', name: dashboardListingTable.getTitleColumnName(), sortable: true, - render: (field: string, record: { id: string; title: string }) => ( + render: (field: string, record: { id: string; title: string; timeRestore: boolean }) => ( redirectTo(record.id)} + href={getDashboardListItemLink( + application, + kbnUrlStateStorage, + useHash, + record.id, + record.timeRestore + )} data-test-subj={`dashboardListingTitleLink-${record.title.split(' ').join('-')}`} > {field} diff --git a/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.test.ts b/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.test.ts new file mode 100644 index 0000000000000..6dbc76803af90 --- /dev/null +++ b/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.test.ts @@ -0,0 +1,142 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { getDashboardListItemLink } from './get_dashboard_list_item_link'; +import { ApplicationStart } from 'kibana/public'; +import { esFilters } from '../../../../data/public'; +import { createHashHistory } from 'history'; +import { createKbnUrlStateStorage } from '../../../../kibana_utils/public'; +import { GLOBAL_STATE_STORAGE_KEY } from '../../url_generator'; + +const DASHBOARD_ID = '13823000-99b9-11ea-9eb6-d9e8adceb647'; + +const application = ({ + getUrlForApp: jest.fn((appId: string, options?: { path?: string; absolute?: boolean }) => { + return `/app/${appId}${options?.path}`; + }), +} as unknown) as ApplicationStart; + +const history = createHashHistory(); +const kbnUrlStateStorage = createKbnUrlStateStorage({ + history, + useHash: false, +}); +kbnUrlStateStorage.set(GLOBAL_STATE_STORAGE_KEY, { time: { from: 'now-7d', to: 'now' } }); + +describe('listing dashboard link', () => { + test('creates a link to a dashboard without the timerange query if time is saved on the dashboard', async () => { + const url = getDashboardListItemLink( + application, + kbnUrlStateStorage, + false, + DASHBOARD_ID, + true + ); + expect(url).toMatchInlineSnapshot(`"/app/dashboards#/view/${DASHBOARD_ID}?_g=()"`); + }); + + test('creates a link to a dashboard with the timerange query if time is not saved on the dashboard', async () => { + const url = getDashboardListItemLink( + application, + kbnUrlStateStorage, + false, + DASHBOARD_ID, + false + ); + expect(url).toMatchInlineSnapshot( + `"/app/dashboards#/view/${DASHBOARD_ID}?_g=(time:(from:now-7d,to:now))"` + ); + }); +}); + +describe('when global time changes', () => { + beforeEach(() => { + kbnUrlStateStorage.set(GLOBAL_STATE_STORAGE_KEY, { + time: { + from: '2021-01-05T11:45:53.375Z', + to: '2021-01-21T11:46:00.990Z', + }, + }); + }); + + test('propagates the correct time on the query', async () => { + const url = getDashboardListItemLink( + application, + kbnUrlStateStorage, + false, + DASHBOARD_ID, + false + ); + expect(url).toMatchInlineSnapshot( + `"/app/dashboards#/view/${DASHBOARD_ID}?_g=(time:(from:'2021-01-05T11:45:53.375Z',to:'2021-01-21T11:46:00.990Z'))"` + ); + }); +}); + +describe('when global refreshInterval changes', () => { + beforeEach(() => { + kbnUrlStateStorage.set(GLOBAL_STATE_STORAGE_KEY, { + refreshInterval: { pause: false, value: 300 }, + }); + }); + + test('propagates the refreshInterval on the query', async () => { + const url = getDashboardListItemLink( + application, + kbnUrlStateStorage, + false, + DASHBOARD_ID, + false + ); + expect(url).toMatchInlineSnapshot( + `"/app/dashboards#/view/${DASHBOARD_ID}?_g=(refreshInterval:(pause:!f,value:300))"` + ); + }); +}); + +describe('when global filters change', () => { + beforeEach(() => { + const filters = [ + { + meta: { + alias: null, + disabled: false, + negate: false, + }, + query: { query: 'q1' }, + }, + { + meta: { + alias: null, + disabled: false, + negate: false, + }, + query: { query: 'q1' }, + $state: { + store: esFilters.FilterStateStore.GLOBAL_STATE, + }, + }, + ]; + kbnUrlStateStorage.set(GLOBAL_STATE_STORAGE_KEY, { + filters, + }); + }); + + test('propagates the filters on the query', async () => { + const url = getDashboardListItemLink( + application, + kbnUrlStateStorage, + false, + DASHBOARD_ID, + false + ); + expect(url).toMatchInlineSnapshot( + `"/app/dashboards#/view/${DASHBOARD_ID}?_g=(filters:!((meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1)),('$state':(store:globalState),meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1))))"` + ); + }); +}); diff --git a/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.ts b/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.ts new file mode 100644 index 0000000000000..d14638b9e231f --- /dev/null +++ b/src/plugins/dashboard/public/application/listing/get_dashboard_list_item_link.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ +import { ApplicationStart } from 'kibana/public'; +import { QueryState } from '../../../../data/public'; +import { setStateToKbnUrl } from '../../../../kibana_utils/public'; +import { createDashboardEditUrl, DashboardConstants } from '../../dashboard_constants'; +import { GLOBAL_STATE_STORAGE_KEY } from '../../url_generator'; +import { IKbnUrlStateStorage } from '../../services/kibana_utils'; + +export const getDashboardListItemLink = ( + application: ApplicationStart, + kbnUrlStateStorage: IKbnUrlStateStorage, + useHash: boolean, + id: string, + timeRestore: boolean +) => { + let url = application.getUrlForApp(DashboardConstants.DASHBOARDS_ID, { + path: `#${createDashboardEditUrl(id)}`, + }); + const globalStateInUrl = kbnUrlStateStorage.get(GLOBAL_STATE_STORAGE_KEY) || {}; + + if (timeRestore) { + delete globalStateInUrl.time; + delete globalStateInUrl.refreshInterval; + } + url = setStateToKbnUrl(GLOBAL_STATE_STORAGE_KEY, globalStateInUrl, { useHash }, url); + return url; +}; diff --git a/src/plugins/dashboard/public/application/top_nav/show_share_modal.test.tsx b/src/plugins/dashboard/public/application/top_nav/show_share_modal.test.tsx new file mode 100644 index 0000000000000..d4703d14627a4 --- /dev/null +++ b/src/plugins/dashboard/public/application/top_nav/show_share_modal.test.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { Capabilities } from 'src/core/public'; +import { showPublicUrlSwitch } from './show_share_modal'; + +describe('showPublicUrlSwitch', () => { + test('returns false if "dashboard" app is not available', () => { + const anonymousUserCapabilities: Capabilities = { + catalogue: {}, + management: {}, + navLinks: {}, + }; + const result = showPublicUrlSwitch(anonymousUserCapabilities); + + expect(result).toBe(false); + }); + + test('returns false if "dashboard" app is not accessible', () => { + const anonymousUserCapabilities: Capabilities = { + catalogue: {}, + management: {}, + navLinks: {}, + dashboard: { + show: false, + }, + }; + const result = showPublicUrlSwitch(anonymousUserCapabilities); + + expect(result).toBe(false); + }); + + test('returns true if "dashboard" app is not available an accessible', () => { + const anonymousUserCapabilities: Capabilities = { + catalogue: {}, + management: {}, + navLinks: {}, + dashboard: { + show: true, + }, + }; + const result = showPublicUrlSwitch(anonymousUserCapabilities); + + expect(result).toBe(true); + }); +}); diff --git a/src/plugins/dashboard/public/application/top_nav/show_share_modal.tsx b/src/plugins/dashboard/public/application/top_nav/show_share_modal.tsx index 660e7635eb99d..fe4f8ea411289 100644 --- a/src/plugins/dashboard/public/application/top_nav/show_share_modal.tsx +++ b/src/plugins/dashboard/public/application/top_nav/show_share_modal.tsx @@ -6,6 +6,7 @@ * Public License, v 1. */ +import { Capabilities } from 'src/core/public'; import { EuiCheckboxGroup } from '@elastic/eui'; import React from 'react'; import { ReactElement, useState } from 'react'; @@ -27,6 +28,14 @@ interface ShowShareModalProps { dashboardStateManager: DashboardStateManager; } +export const showPublicUrlSwitch = (anonymousUserCapabilities: Capabilities) => { + if (!anonymousUserCapabilities.dashboard) return false; + + const dashboard = (anonymousUserCapabilities.dashboard as unknown) as DashboardCapabilities; + + return !!dashboard.show; +}; + export function ShowShareModal({ share, anchorElement, @@ -94,7 +103,7 @@ export function ShowShareModal({ share.toggleShareContextMenu({ anchorElement, allowEmbed: true, - allowShortUrl: !dashboardCapabilities.hideWriteControls || dashboardCapabilities.createShortUrl, + allowShortUrl: dashboardCapabilities.createShortUrl, shareableUrl: setStateToKbnUrl( '_a', dashboardStateManager.getAppState(), @@ -113,5 +122,6 @@ export function ShowShareModal({ component: EmbedUrlParamExtension, }, ], + showPublicUrlSwitch, }); } diff --git a/src/plugins/dashboard/public/application/types.ts b/src/plugins/dashboard/public/application/types.ts index 61e16beed61f4..e4f9388a919d1 100644 --- a/src/plugins/dashboard/public/application/types.ts +++ b/src/plugins/dashboard/public/application/types.ts @@ -55,6 +55,7 @@ export interface DashboardCapabilities { saveQuery: boolean; createNew: boolean; show: boolean; + storeSearchSession: boolean; } export interface DashboardAppServices { diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index ff3e2ebc89a41..fc8c44e8d1870 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -384,6 +384,7 @@ export { SearchTimeoutError, TimeoutErrorMode, PainlessError, + noSearchSessionStorageCapabilityMessage, } from './search'; export type { diff --git a/src/plugins/data/public/public.api.md b/src/plugins/data/public/public.api.md index 28997de4517e7..9e493f46b0781 100644 --- a/src/plugins/data/public/public.api.md +++ b/src/plugins/data/public/public.api.md @@ -218,11 +218,11 @@ export class AggConfigs { id?: string | undefined; params?: {} | import("./agg_config").SerializableState | undefined; schema?: string | undefined; - }, "enabled" | "schema" | "id" | "params"> & Pick<{ + }, "schema" | "enabled" | "id" | "params"> & Pick<{ type: string | IAggType; }, "type"> & Pick<{ type: string | IAggType; - }, never>, "enabled" | "type" | "schema" | "id" | "params">[] | undefined, opts: AggConfigsOptions); + }, never>, "schema" | "type" | "enabled" | "id" | "params">[] | undefined, opts: AggConfigsOptions); // (undocumented) aggs: IAggConfig[]; // (undocumented) @@ -1834,6 +1834,11 @@ export enum METRIC_TYPES { TOP_HITS = "top_hits" } +// Warning: (ae-missing-release-tag) "noSearchSessionStorageCapabilityMessage" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export const noSearchSessionStorageCapabilityMessage: string; + // Warning: (ae-missing-release-tag) "OptionedParamType" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) @@ -2235,8 +2240,8 @@ export const search: { // Warning: (ae-missing-release-tag) "SearchBar" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) // // @public (undocumented) -export const SearchBar: React.ComponentClass, "query" | "isLoading" | "filters" | "indexPatterns" | "dataTestSubj" | "refreshInterval" | "screenTitle" | "onRefresh" | "onRefreshChange" | "showQueryInput" | "showDatePicker" | "showAutoRefreshOnly" | "dateRangeFrom" | "dateRangeTo" | "isRefreshPaused" | "customSubmitButton" | "timeHistory" | "indicateNoData" | "onFiltersUpdated" | "trackUiMetric" | "savedQuery" | "showSaveQuery" | "onClearSavedQuery" | "showQueryBar" | "showFilterBar" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated">, any> & { - WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; +export const SearchBar: React.ComponentClass, "query" | "isLoading" | "indexPatterns" | "filters" | "dataTestSubj" | "refreshInterval" | "screenTitle" | "onRefresh" | "onRefreshChange" | "showQueryInput" | "showDatePicker" | "showAutoRefreshOnly" | "dateRangeFrom" | "dateRangeTo" | "isRefreshPaused" | "customSubmitButton" | "timeHistory" | "indicateNoData" | "onFiltersUpdated" | "trackUiMetric" | "savedQuery" | "showSaveQuery" | "onClearSavedQuery" | "showQueryBar" | "showFilterBar" | "onQueryChange" | "onQuerySubmit" | "onSaved" | "onSavedQueryUpdated">, any> & { + WrappedComponent: React.ComponentType & ReactIntl.InjectedIntlProps>; }; // Warning: (ae-forgotten-export) The symbol "SearchBarOwnProps" needs to be exported by the entry point index.d.ts @@ -2629,21 +2634,21 @@ export const UI_SETTINGS: { // src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "validateIndexPattern" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "flattenHitWrapper" needs to be exported by the entry point index.d.ts // src/plugins/data/public/index.ts:234:27 - (ae-forgotten-export) The symbol "formatHitProvider" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:399:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:399:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:399:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:399:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:401:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:402:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:411:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:413:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:414:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:418:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:419:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:422:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:423:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts -// src/plugins/data/public/index.ts:426:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:400:20 - (ae-forgotten-export) The symbol "getRequestInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:400:20 - (ae-forgotten-export) The symbol "getResponseInspectorStats" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:400:20 - (ae-forgotten-export) The symbol "tabifyAggResponse" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:400:20 - (ae-forgotten-export) The symbol "tabifyGetColumns" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:402:1 - (ae-forgotten-export) The symbol "CidrMask" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:403:1 - (ae-forgotten-export) The symbol "dateHistogramInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:412:1 - (ae-forgotten-export) The symbol "InvalidEsCalendarIntervalError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:413:1 - (ae-forgotten-export) The symbol "InvalidEsIntervalFormatError" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:414:1 - (ae-forgotten-export) The symbol "Ipv4Address" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:415:1 - (ae-forgotten-export) The symbol "isDateHistogramBucketAggConfig" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:419:1 - (ae-forgotten-export) The symbol "isValidEsInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:420:1 - (ae-forgotten-export) The symbol "isValidInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:423:1 - (ae-forgotten-export) The symbol "parseInterval" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:424:1 - (ae-forgotten-export) The symbol "propFilter" needs to be exported by the entry point index.d.ts +// src/plugins/data/public/index.ts:427:1 - (ae-forgotten-export) The symbol "toAbsoluteDates" needs to be exported by the entry point index.d.ts // src/plugins/data/public/query/state_sync/connect_to_query_state.ts:34:5 - (ae-forgotten-export) The symbol "FilterStateStore" needs to be exported by the entry point index.d.ts // src/plugins/data/public/search/session/session_service.ts:41:5 - (ae-forgotten-export) The symbol "UrlGeneratorStateMapping" needs to be exported by the entry point index.d.ts diff --git a/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts b/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts index 19035eb8a9328..991f3063cac0a 100644 --- a/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts +++ b/src/plugins/data/public/query/state_sync/sync_state_with_url.test.ts @@ -90,7 +90,7 @@ describe('sync_query_state_with_url', () => { test('url is actually changed when data in services changes', () => { const { stop } = syncQueryStateWithUrl(queryServiceStart, kbnUrlStateStorage); filterManager.setFilters([gF, aF]); - kbnUrlStateStorage.flush(); // sync force location change + kbnUrlStateStorage.kbnUrlControls.flush(); // sync force location change expect(history.location.hash).toMatchInlineSnapshot( `"#?_g=(filters:!(('$state':(store:globalState),meta:(alias:!n,disabled:!t,index:'logstash-*',key:query,negate:!t,type:custom,value:'%7B%22match%22:%7B%22key1%22:%22value1%22%7D%7D'),query:(match:(key1:value1)))),refreshInterval:(pause:!t,value:0),time:(from:now-15m,to:now))"` ); @@ -126,7 +126,7 @@ describe('sync_query_state_with_url', () => { test('when url is changed, filters synced back to filterManager', () => { const { stop } = syncQueryStateWithUrl(queryServiceStart, kbnUrlStateStorage); - kbnUrlStateStorage.cancel(); // stop initial syncing pending update + kbnUrlStateStorage.kbnUrlControls.cancel(); // stop initial syncing pending update history.push(pathWithFilter); expect(filterManager.getGlobalFilters()).toHaveLength(1); stop(); diff --git a/src/plugins/data/public/search/index.ts b/src/plugins/data/public/search/index.ts index 1deffc9c2d55e..3d87411883a67 100644 --- a/src/plugins/data/public/search/index.ts +++ b/src/plugins/data/public/search/index.ts @@ -37,6 +37,7 @@ export { SearchSessionState, SessionsClient, ISessionsClient, + noSearchSessionStorageCapabilityMessage, } from './session'; export { getEsPreference } from './es_search'; diff --git a/src/plugins/data/public/search/session/i18n.ts b/src/plugins/data/public/search/session/i18n.ts new file mode 100644 index 0000000000000..2ee36b46dfd5a --- /dev/null +++ b/src/plugins/data/public/search/session/i18n.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { i18n } from '@kbn/i18n'; + +/** + * Message to display in case storing + * session session is disabled due to turned off capability + */ +export const noSearchSessionStorageCapabilityMessage = i18n.translate( + 'data.searchSessionIndicator.noCapability', + { + defaultMessage: "You don't have permissions to create search sessions.", + } +); diff --git a/src/plugins/data/public/search/session/index.ts b/src/plugins/data/public/search/session/index.ts index ab311d56fe096..f3f0c34c1be75 100644 --- a/src/plugins/data/public/search/session/index.ts +++ b/src/plugins/data/public/search/session/index.ts @@ -9,3 +9,4 @@ export { SessionService, ISessionService, SearchSessionInfoProvider } from './session_service'; export { SearchSessionState } from './search_session_state'; export { SessionsClient, ISessionsClient } from './sessions_client'; +export { noSearchSessionStorageCapabilityMessage } from './i18n'; diff --git a/src/plugins/data/public/search/session/mocks.ts b/src/plugins/data/public/search/session/mocks.ts index 6a7a207b90d46..679898e3e51dd 100644 --- a/src/plugins/data/public/search/session/mocks.ts +++ b/src/plugins/data/public/search/session/mocks.ts @@ -29,7 +29,6 @@ export function getSessionServiceMock(): jest.Mocked { getSessionId: jest.fn(), getSession$: jest.fn(() => new BehaviorSubject(undefined).asObservable()), state$: new BehaviorSubject(SearchSessionState.None).asObservable(), - setSearchSessionInfoProvider: jest.fn(), trackSearch: jest.fn((searchDescriptor) => () => {}), destroy: jest.fn(), onRefresh$: new Subject(), @@ -40,5 +39,8 @@ export function getSessionServiceMock(): jest.Mocked { save: jest.fn(), isCurrentSession: jest.fn(), getSearchOptions: jest.fn(), + enableStorage: jest.fn(), + isSessionStorageReady: jest.fn(() => true), + getSearchSessionIndicatorUiConfig: jest.fn(() => ({ isDisabled: () => ({ disabled: false }) })), }; } diff --git a/src/plugins/data/public/search/session/session_service.test.ts b/src/plugins/data/public/search/session/session_service.test.ts index 7797d92c4e07d..21c38c68e6a83 100644 --- a/src/plugins/data/public/search/session/session_service.test.ts +++ b/src/plugins/data/public/search/session/session_service.test.ts @@ -113,7 +113,7 @@ describe('Session service', () => { sessionId, }); - sessionService.setSearchSessionInfoProvider({ + sessionService.enableStorage({ getName: async () => 'Name', getUrlGeneratorData: async () => ({ urlGeneratorId: 'id', @@ -156,4 +156,62 @@ describe('Session service', () => { expect(sessionService.isCurrentSession('some-other')).toBeFalsy(); expect(sessionService.isCurrentSession(sessionId)).toBeTruthy(); }); + + test('enableStorage() enables storage capabilities', async () => { + sessionService.start(); + await expect(() => sessionService.save()).rejects.toThrowErrorMatchingInlineSnapshot( + `"No info provider for current session"` + ); + + expect(sessionService.isSessionStorageReady()).toBe(false); + + sessionService.enableStorage({ + getName: async () => 'Name', + getUrlGeneratorData: async () => ({ + urlGeneratorId: 'id', + initialState: {}, + restoreState: {}, + }), + }); + + expect(sessionService.isSessionStorageReady()).toBe(true); + + await expect(() => sessionService.save()).resolves; + + sessionService.clear(); + expect(sessionService.isSessionStorageReady()).toBe(false); + }); + + test('can provide config for search session indicator', () => { + expect(sessionService.getSearchSessionIndicatorUiConfig().isDisabled().disabled).toBe(false); + sessionService.enableStorage( + { + getName: async () => 'Name', + getUrlGeneratorData: async () => ({ + urlGeneratorId: 'id', + initialState: {}, + restoreState: {}, + }), + }, + { + isDisabled: () => ({ disabled: true, reasonText: 'text' }), + } + ); + + expect(sessionService.getSearchSessionIndicatorUiConfig().isDisabled().disabled).toBe(true); + + sessionService.clear(); + expect(sessionService.getSearchSessionIndicatorUiConfig().isDisabled().disabled).toBe(false); + }); + + test('save() throws in case getUrlGeneratorData returns throws', async () => { + sessionService.enableStorage({ + getName: async () => 'Name', + getUrlGeneratorData: async () => { + throw new Error('Haha'); + }, + }); + sessionService.start(); + await expect(() => sessionService.save()).rejects.toMatchInlineSnapshot(`[Error: Haha]`); + }); }); diff --git a/src/plugins/data/public/search/session/session_service.ts b/src/plugins/data/public/search/session/session_service.ts index 6269398036e76..23129a9afc460 100644 --- a/src/plugins/data/public/search/session/session_service.ts +++ b/src/plugins/data/public/search/session/session_service.ts @@ -43,6 +43,20 @@ export interface SearchSessionInfoProvider; } +/** + * Configure a "Search session indicator" UI + */ +export interface SearchSessionIndicatorUiConfig { + /** + * App controls if "Search session indicator" UI should be disabled. + * reasonText will appear in a tooltip. + * + * Could be used, for example, to disable "Search session indicator" UI + * in case user doesn't have permissions to store a search session + */ + isDisabled: () => { disabled: true; reasonText: string } | { disabled: false }; +} + /** * Responsible for tracking a current search session. Supports only a single session at a time. */ @@ -51,6 +65,7 @@ export class SessionService { private readonly state: SessionStateContainer; private searchSessionInfoProvider?: SearchSessionInfoProvider; + private searchSessionIndicatorUiConfig?: Partial; private subscription = new Subscription(); private curApp?: string; @@ -102,17 +117,6 @@ export class SessionService { }); } - /** - * Set a provider of info about current session - * This will be used for creating a search session saved object - * @param searchSessionInfoProvider - */ - public setSearchSessionInfoProvider( - searchSessionInfoProvider: SearchSessionInfoProvider | undefined - ) { - this.searchSessionInfoProvider = searchSessionInfoProvider; - } - /** * Used to track pending searches within current session * @@ -185,7 +189,8 @@ export class SessionService { */ public clear() { this.state.transitions.clear(); - this.setSearchSessionInfoProvider(undefined); + this.searchSessionInfoProvider = undefined; + this.searchSessionIndicatorUiConfig = undefined; } private refresh$ = new Subject(); @@ -269,4 +274,34 @@ export class SessionService { isStored: isCurrentSession ? this.isStored() : false, }; } + + /** + * Provide an info about current session which is needed for storing a search session. + * To opt-into "Search session indicator" UI app has to call {@link enableStorage}. + * + * @param searchSessionInfoProvider - info provider for saving a search session + * @param searchSessionIndicatorUiConfig - config for "Search session indicator" UI + */ + public enableStorage( + searchSessionInfoProvider: SearchSessionInfoProvider, + searchSessionIndicatorUiConfig?: SearchSessionIndicatorUiConfig + ) { + this.searchSessionInfoProvider = searchSessionInfoProvider; + this.searchSessionIndicatorUiConfig = searchSessionIndicatorUiConfig; + } + + /** + * If the current app explicitly called {@link enableStorage} and provided all configuration needed + * for storing its search sessions + */ + public isSessionStorageReady(): boolean { + return !!this.searchSessionInfoProvider; + } + + public getSearchSessionIndicatorUiConfig(): SearchSessionIndicatorUiConfig { + return { + isDisabled: () => ({ disabled: false }), + ...this.searchSessionIndicatorUiConfig, + }; + } } diff --git a/src/plugins/data/public/ui/query_string_input/query_string_input.test.tsx b/src/plugins/data/public/ui/query_string_input/query_string_input.test.tsx index 426302689c8f0..b7d9be485a303 100644 --- a/src/plugins/data/public/ui/query_string_input/query_string_input.test.tsx +++ b/src/plugins/data/public/ui/query_string_input/query_string_input.test.tsx @@ -84,12 +84,15 @@ function wrapQueryStringInputInContext(testProps: any, storage?: any) { ); } -describe('QueryStringInput', () => { +// FAILING: https://github.com/elastic/kibana/issues/85715 +// FAILING: https://github.com/elastic/kibana/issues/89603 +// FAILING: https://github.com/elastic/kibana/issues/89641 +describe.skip('QueryStringInput', () => { beforeEach(() => { jest.clearAllMocks(); }); - it('Should render the given query', async () => { + it.skip('Should render the given query', async () => { const { getByText } = render( wrapQueryStringInputInContext({ query: kqlQuery, diff --git a/src/plugins/data/server/server.api.md b/src/plugins/data/server/server.api.md index 6a96fd8209a8d..9789f3354e9ef 100644 --- a/src/plugins/data/server/server.api.md +++ b/src/plugins/data/server/server.api.md @@ -1138,7 +1138,7 @@ export class Plugin implements Plugin_2 Promise; }; indexPatterns: { - indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; + indexPatternsServiceFactory: (savedObjectsClient: Pick, elasticsearchClient: import("../../../core/server").ElasticsearchClient) => Promise; }; search: ISearchStart>; }; diff --git a/src/plugins/discover/public/application/angular/context_state.ts b/src/plugins/discover/public/application/angular/context_state.ts index 73523b218df7c..e8c2f1d397ba5 100644 --- a/src/plugins/discover/public/application/angular/context_state.ts +++ b/src/plugins/discover/public/application/angular/context_state.ts @@ -206,7 +206,7 @@ export function getState({ } }, // helper function just needed for testing - flushToUrl: (replace?: boolean) => stateStorage.flush({ replace }), + flushToUrl: (replace?: boolean) => stateStorage.kbnUrlControls.flush(replace), }; } diff --git a/src/plugins/discover/public/application/angular/discover.js b/src/plugins/discover/public/application/angular/discover.js index 946baa7f4ecb1..dcf86babaa5e1 100644 --- a/src/plugins/discover/public/application/angular/discover.js +++ b/src/plugins/discover/public/application/angular/discover.js @@ -18,12 +18,12 @@ import { connectToQueryState, esFilters, indexPatterns as indexPatternsUtils, + noSearchSessionStorageCapabilityMessage, syncQueryStateWithUrl, } from '../../../../data/public'; import { getSortArray } from './doc_table'; import * as columnActions from './doc_table/actions/columns'; import indexTemplateLegacy from './discover_legacy.html'; -import indexTemplateGrid from './discover_datagrid.html'; import { addHelpMenuToAppChrome } from '../components/help_menu/help_menu_util'; import { discoverResponseHandler } from './response_handler'; import { @@ -47,8 +47,6 @@ import { popularizeField } from '../helpers/popularize_field'; import { getSwitchIndexPatternAppState } from '../helpers/get_switch_index_pattern_app_state'; import { addFatalError } from '../../../../kibana_legacy/public'; import { METRIC_TYPE } from '@kbn/analytics'; -import { SEARCH_SESSION_ID_QUERY_PARAM } from '../../url_generator'; -import { getQueryParams, removeQueryParam } from '../../../../kibana_utils/public'; import { DEFAULT_COLUMNS_SETTING, MODIFY_COLUMNS_ON_SWITCH, @@ -62,6 +60,7 @@ import { getTopNavLinks } from '../components/top_nav/get_top_nav_links'; import { updateSearchSource } from '../helpers/update_search_source'; import { calcFieldCounts } from '../helpers/calc_field_counts'; import { getDefaultSort } from './doc_table/lib/get_default_sort'; +import { DiscoverSearchSessionManager } from './discover_search_session'; const services = getServices(); @@ -86,9 +85,6 @@ const fetchStatuses = { ERROR: 'error', }; -const getSearchSessionIdFromURL = (history) => - getQueryParams(history.location)[SEARCH_SESSION_ID_QUERY_PARAM]; - const app = getAngularModule(); app.config(($routeProvider) => { @@ -115,9 +111,7 @@ app.config(($routeProvider) => { }; const discoverRoute = { ...defaults, - template: getServices().uiSettings.get('doc_table:legacy', true) - ? indexTemplateLegacy - : indexTemplateGrid, + template: indexTemplateLegacy, reloadOnSearch: false, resolve: { savedObjects: function ($route, Promise) { @@ -179,7 +173,9 @@ function discoverController($route, $scope, Promise) { const { isDefault: isDefaultType } = indexPatternsUtils; const subscriptions = new Subscription(); const refetch$ = new Subject(); + let inspectorRequest; + let isChangingIndexPattern = false; const savedSearch = $route.current.locals.savedObjects.savedSearch; $scope.searchSource = savedSearch.searchSource; $scope.indexPattern = resolveIndexPattern( @@ -197,15 +193,10 @@ function discoverController($route, $scope, Promise) { }; const history = getHistory(); - // used for restoring a search session - let isInitialSearch = true; - - // search session requested a data refresh - subscriptions.add( - data.search.session.onRefresh$.subscribe(() => { - refetch$.next(); - }) - ); + const searchSessionManager = new DiscoverSearchSessionManager({ + history, + session: data.search.session, + }); const state = getState({ getStateDefaults, @@ -257,6 +248,7 @@ function discoverController($route, $scope, Promise) { $scope.$evalAsync(async () => { if (oldStatePartial.index !== newStatePartial.index) { //in case of index pattern switch the route has currently to be reloaded, legacy + isChangingIndexPattern = true; $route.reload(); return; } @@ -284,12 +276,21 @@ function discoverController($route, $scope, Promise) { } }); - data.search.session.setSearchSessionInfoProvider( + data.search.session.enableStorage( createSearchSessionRestorationDataProvider({ appStateContainer, data, getSavedSearch: () => savedSearch, - }) + }), + { + isDisabled: () => + capabilities.discover.storeSearchSession + ? { disabled: false } + : { + disabled: true, + reasonText: noSearchSessionStorageCapabilityMessage, + }, + } ); $scope.setIndexPattern = async (id) => { @@ -344,7 +345,12 @@ function discoverController($route, $scope, Promise) { if (abortController) abortController.abort(); savedSearch.destroy(); subscriptions.unsubscribe(); - data.search.session.clear(); + if (!isChangingIndexPattern) { + // HACK: + // do not clear session when changing index pattern due to how state management around it is setup + // it will be cleared by searchSessionManager on controller reload instead + data.search.session.clear(); + } appStateUnsubscribe(); stopStateSync(); stopSyncingGlobalStateWithUrl(); @@ -468,7 +474,8 @@ function discoverController($route, $scope, Promise) { return ( config.get(SEARCH_ON_PAGE_LOAD_SETTING) || savedSearch.id !== undefined || - timefilter.getRefreshInterval().pause === false + timefilter.getRefreshInterval().pause === false || + searchSessionManager.hasSearchSessionIdInURL() ); }; @@ -479,7 +486,8 @@ function discoverController($route, $scope, Promise) { filterManager.getFetches$(), timefilter.getFetch$(), timefilter.getAutoRefreshFetch$(), - data.query.queryString.getUpdates$() + data.query.queryString.getUpdates$(), + searchSessionManager.newSearchSessionIdFromURL$ ).pipe(debounceTime(100)); subscriptions.add( @@ -505,6 +513,13 @@ function discoverController($route, $scope, Promise) { ) ); + subscriptions.add( + data.search.session.onRefresh$.subscribe(() => { + searchSessionManager.removeSearchSessionIdFromURL({ replace: false }); + refetch$.next(); + }) + ); + $scope.changeInterval = (interval) => { if (interval) { setAppState({ interval }); @@ -584,20 +599,7 @@ function discoverController($route, $scope, Promise) { if (abortController) abortController.abort(); abortController = new AbortController(); - const searchSessionId = (() => { - const searchSessionIdFromURL = getSearchSessionIdFromURL(history); - if (searchSessionIdFromURL) { - if (isInitialSearch) { - data.search.session.restore(searchSessionIdFromURL); - isInitialSearch = false; - return searchSessionIdFromURL; - } else { - // navigating away from background search - removeQueryParam(history, SEARCH_SESSION_ID_QUERY_PARAM); - } - } - return data.search.session.start(); - })(); + const searchSessionId = searchSessionManager.getNextSearchSessionId(); $scope .updateDataSource() @@ -624,6 +626,7 @@ function discoverController($route, $scope, Promise) { $scope.handleRefresh = function (_payload, isUpdate) { if (isUpdate === false) { + searchSessionManager.removeSearchSessionIdFromURL({ replace: false }); refetch$.next(); } }; diff --git a/src/plugins/discover/public/application/angular/discover_legacy.html b/src/plugins/discover/public/application/angular/discover_legacy.html index 9383980fd9fd6..76e5c568ffde6 100644 --- a/src/plugins/discover/public/application/angular/discover_legacy.html +++ b/src/plugins/discover/public/application/angular/discover_legacy.html @@ -1,5 +1,5 @@ - - + diff --git a/src/plugins/discover/public/application/angular/discover_search_session.test.ts b/src/plugins/discover/public/application/angular/discover_search_session.test.ts new file mode 100644 index 0000000000000..abec6aedeaf5c --- /dev/null +++ b/src/plugins/discover/public/application/angular/discover_search_session.test.ts @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { DiscoverSearchSessionManager } from './discover_search_session'; +import { createMemoryHistory } from 'history'; +import { dataPluginMock } from '../../../../data/public/mocks'; +import { DataPublicPluginStart } from '../../../../data/public'; + +describe('DiscoverSearchSessionManager', () => { + const history = createMemoryHistory(); + const session = dataPluginMock.createStartContract().search.session as jest.Mocked< + DataPublicPluginStart['search']['session'] + >; + const searchSessionManager = new DiscoverSearchSessionManager({ + history, + session, + }); + + beforeEach(() => { + history.push('/'); + session.start.mockReset(); + session.restore.mockReset(); + session.getSessionId.mockReset(); + session.isCurrentSession.mockReset(); + session.isRestore.mockReset(); + }); + + describe('getNextSearchSessionId', () => { + test('starts a new session', () => { + const nextId = 'id'; + session.start.mockImplementationOnce(() => nextId); + + const id = searchSessionManager.getNextSearchSessionId(); + expect(id).toEqual(nextId); + expect(session.start).toBeCalled(); + }); + + test('restores a session using query param from the URL', () => { + const nextId = 'id_from_url'; + history.push(`/?searchSessionId=${nextId}`); + + const id = searchSessionManager.getNextSearchSessionId(); + expect(id).toEqual(nextId); + expect(session.restore).toBeCalled(); + }); + + test('removes query param from the URL when navigating away from a restored session', () => { + const idFromUrl = 'id_from_url'; + history.push(`/?searchSessionId=${idFromUrl}`); + + const nextId = 'id'; + session.start.mockImplementationOnce(() => nextId); + session.isCurrentSession.mockImplementationOnce(() => true); + session.isRestore.mockImplementationOnce(() => true); + + const id = searchSessionManager.getNextSearchSessionId(); + expect(id).toEqual(nextId); + expect(session.start).toBeCalled(); + expect(history.location.search).toMatchInlineSnapshot(`""`); + }); + }); + + describe('newSearchSessionIdFromURL$', () => { + test('notifies about searchSessionId changes in the URL', () => { + const emits: Array = []; + + const sub = searchSessionManager.newSearchSessionIdFromURL$.subscribe((newId) => { + emits.push(newId); + }); + + history.push(`/?searchSessionId=id1`); + history.push(`/?searchSessionId=id1`); + session.isCurrentSession.mockImplementationOnce(() => true); + history.replace(`/?searchSessionId=id2`); // should skip current this + history.replace(`/`); + history.push(`/?searchSessionId=id1`); + history.push(`/`); + + expect(emits).toMatchInlineSnapshot(` + Array [ + "id1", + null, + "id1", + null, + ] + `); + + sub.unsubscribe(); + }); + }); +}); diff --git a/src/plugins/discover/public/application/angular/discover_search_session.ts b/src/plugins/discover/public/application/angular/discover_search_session.ts new file mode 100644 index 0000000000000..a53d7d6d2c333 --- /dev/null +++ b/src/plugins/discover/public/application/angular/discover_search_session.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { History } from 'history'; +import { filter } from 'rxjs/operators'; +import { DataPublicPluginStart } from '../../../../data/public'; +import { + createQueryParamObservable, + getQueryParams, + removeQueryParam, +} from '../../../../kibana_utils/public'; +import { SEARCH_SESSION_ID_QUERY_PARAM } from '../../url_generator'; + +export interface DiscoverSearchSessionManagerDeps { + history: History; + session: DataPublicPluginStart['search']['session']; +} + +/** + * Helps with state management of search session and {@link SEARCH_SESSION_ID_QUERY_PARAM} in the URL + */ +export class DiscoverSearchSessionManager { + /** + * Notifies about `searchSessionId` changes in the URL, + * skips if `searchSessionId` matches current search session id + */ + readonly newSearchSessionIdFromURL$ = createQueryParamObservable( + this.deps.history, + SEARCH_SESSION_ID_QUERY_PARAM + ).pipe( + filter((searchSessionId) => { + if (!searchSessionId) return true; + return !this.deps.session.isCurrentSession(searchSessionId); + }) + ); + + constructor(private readonly deps: DiscoverSearchSessionManagerDeps) {} + + /** + * Get next session id by either starting or restoring a session. + * When navigating away from the restored session {@link SEARCH_SESSION_ID_QUERY_PARAM} is removed from the URL using history.replace + */ + getNextSearchSessionId() { + let searchSessionIdFromURL = this.getSearchSessionIdFromURL(); + if (searchSessionIdFromURL) { + if ( + this.deps.session.isRestore() && + this.deps.session.isCurrentSession(searchSessionIdFromURL) + ) { + // navigating away from a restored session + this.removeSearchSessionIdFromURL({ replace: true }); + searchSessionIdFromURL = undefined; + } else { + this.deps.session.restore(searchSessionIdFromURL); + } + } + + return searchSessionIdFromURL ?? this.deps.session.start(); + } + + /** + * Removes Discovers {@link SEARCH_SESSION_ID_QUERY_PARAM} from the URL + * @param replace - methods to change the URL + */ + removeSearchSessionIdFromURL({ replace = true }: { replace?: boolean } = { replace: true }) { + if (this.hasSearchSessionIdInURL()) { + removeQueryParam(this.deps.history, SEARCH_SESSION_ID_QUERY_PARAM, replace); + } + } + + /** + * If there is a {@link SEARCH_SESSION_ID_QUERY_PARAM} currently in the URL + */ + hasSearchSessionIdInURL(): boolean { + return !!this.getSearchSessionIdFromURL(); + } + + private getSearchSessionIdFromURL = () => + getQueryParams(this.deps.history.location)[SEARCH_SESSION_ID_QUERY_PARAM] as string | undefined; +} diff --git a/src/plugins/discover/public/application/angular/discover_state.ts b/src/plugins/discover/public/application/angular/discover_state.ts index c769e263655ab..65a8dded11092 100644 --- a/src/plugins/discover/public/application/angular/discover_state.ts +++ b/src/plugins/discover/public/application/angular/discover_state.ts @@ -200,7 +200,7 @@ export function getState({ setState(appStateContainerModified, defaultState); }, getPreviousAppState: () => previousAppState, - flushToUrl: () => stateStorage.flush(), + flushToUrl: () => stateStorage.kbnUrlControls.flush(), isAppStateDirty: () => !isEqualState(initialAppState, appStateContainer.getState()), }; } diff --git a/src/plugins/discover/public/application/angular/doc_table/create_doc_table_react.tsx b/src/plugins/discover/public/application/angular/doc_table/create_doc_table_react.tsx index 06b6e504832e4..cbd93feb835a0 100644 --- a/src/plugins/discover/public/application/angular/doc_table/create_doc_table_react.tsx +++ b/src/plugins/discover/public/application/angular/doc_table/create_doc_table_react.tsx @@ -9,8 +9,11 @@ import angular, { auto, ICompileService, IScope } from 'angular'; import { render } from 'react-dom'; import React, { useRef, useEffect } from 'react'; +import { EuiButtonEmpty } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; import { getServices, IIndexPattern } from '../../../kibana_services'; import { IndexPatternField } from '../../../../../data/common/index_patterns'; + export type AngularScope = IScope; export interface AngularDirective { @@ -83,9 +86,11 @@ export interface DocTableLegacyProps { indexPattern: IIndexPattern; minimumVisibleRows: number; onAddColumn?: (column: string) => void; + onBackToTop: () => void; onSort?: (sort: string[][]) => void; onMoveColumn?: (columns: string, newIdx: number) => void; onRemoveColumn?: (column: string) => void; + sampleSize: number; sort?: string[][]; useNewFieldsApi?: boolean; } @@ -120,5 +125,31 @@ export function DocTableLegacy(renderProps: DocTableLegacyProps) { return renderFn(ref.current, renderProps); } }, [renderFn, renderProps]); - return
; + return ( +
+
+ {renderProps.rows.length === renderProps.sampleSize ? ( +
+ + + + +
+ ) : ( + + ​ + + )} +
+ ); } diff --git a/src/plugins/discover/public/application/components/create_discover_directive.ts b/src/plugins/discover/public/application/components/create_discover_directive.ts index 42b99b635a791..10439488f4bc7 100644 --- a/src/plugins/discover/public/application/components/create_discover_directive.ts +++ b/src/plugins/discover/public/application/components/create_discover_directive.ts @@ -17,18 +17,21 @@ export function createDiscoverDirective(reactDirective: any) { ['histogramData', { watchDepth: 'reference' }], ['hits', { watchDepth: 'reference' }], ['indexPattern', { watchDepth: 'reference' }], + ['minimumVisibleRows', { watchDepth: 'reference' }], ['onAddColumn', { watchDepth: 'reference' }], ['onAddFilter', { watchDepth: 'reference' }], ['onChangeInterval', { watchDepth: 'reference' }], + ['onMoveColumn', { watchDepth: 'reference' }], ['onRemoveColumn', { watchDepth: 'reference' }], ['onSetColumns', { watchDepth: 'reference' }], + ['onSkipBottomButtonClick', { watchDepth: 'reference' }], ['onSort', { watchDepth: 'reference' }], ['opts', { watchDepth: 'reference' }], ['resetQuery', { watchDepth: 'reference' }], ['resultState', { watchDepth: 'reference' }], ['rows', { watchDepth: 'reference' }], + ['savedSearch', { watchDepth: 'reference' }], ['searchSource', { watchDepth: 'reference' }], - ['setColumns', { watchDepth: 'reference' }], ['setIndexPattern', { watchDepth: 'reference' }], ['showSaveQuery', { watchDepth: 'reference' }], ['state', { watchDepth: 'reference' }], diff --git a/src/plugins/discover/public/application/components/create_discover_legacy_directive.ts b/src/plugins/discover/public/application/components/create_discover_legacy_directive.ts deleted file mode 100644 index b2b9fd38f73b1..0000000000000 --- a/src/plugins/discover/public/application/components/create_discover_legacy_directive.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { DiscoverLegacy } from './discover_legacy'; - -export function createDiscoverLegacyDirective(reactDirective: any) { - return reactDirective(DiscoverLegacy, [ - ['fetch', { watchDepth: 'reference' }], - ['fetchCounter', { watchDepth: 'reference' }], - ['fetchError', { watchDepth: 'reference' }], - ['fieldCounts', { watchDepth: 'reference' }], - ['histogramData', { watchDepth: 'reference' }], - ['hits', { watchDepth: 'reference' }], - ['indexPattern', { watchDepth: 'reference' }], - ['minimumVisibleRows', { watchDepth: 'reference' }], - ['onAddColumn', { watchDepth: 'reference' }], - ['onAddFilter', { watchDepth: 'reference' }], - ['onChangeInterval', { watchDepth: 'reference' }], - ['onMoveColumn', { watchDepth: 'reference' }], - ['onRemoveColumn', { watchDepth: 'reference' }], - ['onSetColumns', { watchDepth: 'reference' }], - ['onSkipBottomButtonClick', { watchDepth: 'reference' }], - ['onSort', { watchDepth: 'reference' }], - ['opts', { watchDepth: 'reference' }], - ['resetQuery', { watchDepth: 'reference' }], - ['resultState', { watchDepth: 'reference' }], - ['rows', { watchDepth: 'reference' }], - ['savedSearch', { watchDepth: 'reference' }], - ['searchSource', { watchDepth: 'reference' }], - ['setIndexPattern', { watchDepth: 'reference' }], - ['showSaveQuery', { watchDepth: 'reference' }], - ['state', { watchDepth: 'reference' }], - ['timefilterUpdateHandler', { watchDepth: 'reference' }], - ['timeRange', { watchDepth: 'reference' }], - ['topNavMenu', { watchDepth: 'reference' }], - ['updateQuery', { watchDepth: 'reference' }], - ['updateSavedQueryId', { watchDepth: 'reference' }], - ['useNewFieldsApi', { watchDepth: 'reference' }], - ]); -} diff --git a/src/plugins/discover/public/application/components/discover_legacy.test.tsx b/src/plugins/discover/public/application/components/discover.test.tsx similarity index 89% rename from src/plugins/discover/public/application/components/discover_legacy.test.tsx rename to src/plugins/discover/public/application/components/discover.test.tsx index 04f294912d49e..3088ca45f7941 100644 --- a/src/plugins/discover/public/application/components/discover_legacy.test.tsx +++ b/src/plugins/discover/public/application/components/discover.test.tsx @@ -8,7 +8,7 @@ import React from 'react'; import { shallowWithIntl } from '@kbn/test/jest'; -import { DiscoverLegacy } from './discover_legacy'; +import { Discover } from './discover'; import { inspectorPluginMock } from '../../../../inspector/public/mocks'; import { esHits } from '../../__mocks__/es_hits'; import { indexPatternMock } from '../../__mocks__/index_pattern'; @@ -19,7 +19,7 @@ import { savedSearchMock } from '../../__mocks__/saved_search'; import { createSearchSourceMock } from '../../../../data/common/search/search_source/mocks'; import { dataPluginMock } from '../../../../data/public/mocks'; import { createFilterManagerMock } from '../../../../data/public/query/filter_manager/filter_manager.mock'; -import { uiSettingsMock } from '../../__mocks__/ui_settings'; +import { uiSettingsMock as mockUiSettings } from '../../__mocks__/ui_settings'; import { IndexPattern, IndexPatternAttributes } from '../../../../data/common/index_patterns'; import { SavedObject } from '../../../../../core/types'; import { navigationPluginMock } from '../../../../navigation/public/mocks'; @@ -40,6 +40,7 @@ jest.mock('../../kibana_services', () => { }, }, navigation: mockNavigation, + uiSettings: mockUiSettings, }), }; }); @@ -53,6 +54,7 @@ function getProps(indexPattern: IndexPattern) { save: true, }, }, + uiSettings: mockUiSettings, } as unknown) as DiscoverServices; return { @@ -72,7 +74,7 @@ function getProps(indexPattern: IndexPattern) { onSkipBottomButtonClick: jest.fn(), onSort: jest.fn(), opts: { - config: uiSettingsMock, + config: mockUiSettings, data: dataPluginMock.createStartContract(), fixedScroll: jest.fn(), filterManager: createFilterManagerMock(), @@ -105,15 +107,13 @@ function getProps(indexPattern: IndexPattern) { }; } -describe('Descover legacy component', () => { +describe('Discover component', () => { test('selected index pattern without time field displays no chart toggle', () => { - const component = shallowWithIntl(); + const component = shallowWithIntl(); expect(component.find('[data-test-subj="discoverChartToggle"]').length).toBe(0); }); test('selected index pattern with time field displays chart toggle', () => { - const component = shallowWithIntl( - - ); + const component = shallowWithIntl(); expect(component.find('[data-test-subj="discoverChartToggle"]').length).toBe(1); }); }); diff --git a/src/plugins/discover/public/application/components/discover.tsx b/src/plugins/discover/public/application/components/discover.tsx index 704e7a9c02e1b..5653ef4f57435 100644 --- a/src/plugins/discover/public/application/components/discover.tsx +++ b/src/plugins/discover/public/application/components/discover.tsx @@ -26,25 +26,30 @@ import classNames from 'classnames'; import { HitsCounter } from './hits_counter'; import { TimechartHeader } from './timechart_header'; import { getServices } from '../../kibana_services'; -import { DiscoverUninitialized, DiscoverHistogram } from '../angular/directives'; +import { DiscoverHistogram, DiscoverUninitialized } from '../angular/directives'; import { DiscoverNoResults } from './no_results'; import { LoadingSpinner } from './loading_spinner/loading_spinner'; +import { DocTableLegacy, DocTableLegacyProps } from '../angular/doc_table/create_doc_table_react'; +import { SkipBottomButton } from './skip_bottom_button'; import { search } from '../../../../data/public'; import { DiscoverSidebarResponsive, DiscoverSidebarResponsiveProps, } from './sidebar/discover_sidebar_responsive'; -import { DiscoverProps } from './discover_legacy'; +import { DiscoverProps } from './types'; +import { getDisplayedColumns } from '../helpers/columns'; import { SortPairArr } from '../angular/doc_table/lib/get_sort'; import { DiscoverGrid, DiscoverGridProps } from './discover_grid/discover_grid'; +import { SEARCH_FIELDS_FROM_SOURCE } from '../../../common'; -export const SidebarMemoized = React.memo((props: DiscoverSidebarResponsiveProps) => ( +const DocTableLegacyMemoized = React.memo((props: DocTableLegacyProps) => ( + +)); +const SidebarMemoized = React.memo((props: DiscoverSidebarResponsiveProps) => ( )); -export const DataGridMemoized = React.memo((props: DiscoverGridProps) => ( - -)); +const DataGridMemoized = React.memo((props: DiscoverGridProps) => ); export function Discover({ fetch, @@ -54,11 +59,14 @@ export function Discover({ histogramData, hits, indexPattern, + minimumVisibleRows, onAddColumn, onAddFilter, onChangeInterval, + onMoveColumn, onRemoveColumn, onSetColumns, + onSkipBottomButtonClick, onSort, opts, resetQuery, @@ -66,7 +74,6 @@ export function Discover({ rows, searchSource, setIndexPattern, - showSaveQuery, state, timefilterUpdateHandler, timeRange, @@ -76,6 +83,11 @@ export function Discover({ }: DiscoverProps) { const scrollableDesktop = useRef(null); const collapseIcon = useRef(null); + const isMobile = () => { + // collapse icon isn't displayed in mobile view, use it to detect which view is displayed + return collapseIcon && !collapseIcon.current; + }; + const [toggleOn, toggleChart] = useState(true); const [isSidebarClosed, setIsSidebarClosed] = useState(false); const services = getServices(); @@ -88,18 +100,8 @@ export function Discover({ ? bucketAggConfig.buckets?.getInterval() : undefined; const contentCentered = resultState === 'uninitialized'; - const showTimeCol = !config.get('doc_table:hideTimeColumn', false) && indexPattern.timeFieldName; - const columns = - state.columns && - state.columns.length > 0 && - // check if all columns where removed except the configured timeField (this can't be removed) - !(state.columns.length === 1 && state.columns[0] === indexPattern.timeFieldName) - ? state.columns - : ['_source']; - // if columns include _source this is considered as default view, so you can't remove columns - // until you add a column using Discover's sidebar - const defaultColumns = columns.includes('_source'); - + const isLegacy = services.uiSettings.get('doc_table:legacy'); + const useNewFieldsApi = !services.uiSettings.get(SEARCH_FIELDS_FROM_SOURCE); return ( @@ -114,7 +116,7 @@ export function Discover({ savedQueryId={state.savedQuery} screenTitle={savedSearch.title} showDatePicker={indexPattern.isTimeBased()} - showSaveQuery={showSaveQuery} + showSaveQuery={!!services.capabilities.discover.saveQuery} showSearchBar={true} useDefaultBehaviors={true} /> @@ -137,6 +139,7 @@ export function Discover({ setIndexPattern={setIndexPattern} isClosed={isSidebarClosed} trackUiMetric={trackUiMetric} + useNewFieldsApi={useNewFieldsApi} /> @@ -207,24 +210,28 @@ export function Discover({ /> )} - - { - toggleChart(!toggleOn); - }} - > - {toggleOn - ? i18n.translate('discover.hideChart', { - defaultMessage: 'Hide chart', - }) - : i18n.translate('discover.showChart', { - defaultMessage: 'Show chart', - })} - - + {opts.timefield && ( + + { + toggleChart(!toggleOn); + }} + data-test-subj="discoverChartToggle" + > + {toggleOn + ? i18n.translate('discover.hideChart', { + defaultMessage: 'Hide chart', + }) + : i18n.translate('discover.showChart', { + defaultMessage: 'Show chart', + })} + + + )} + {isLegacy && } {toggleOn && opts.timefield && ( @@ -238,7 +245,10 @@ export function Discover({ className="dscTimechart" > {opts.chartAggConfigs && histogramData && rows.length !== 0 && ( -
+
- {rows && rows.length && ( + {isLegacy && rows && rows.length && ( + { + if (scrollableDesktop && scrollableDesktop.current) { + scrollableDesktop.current.focus(); + } + // Only the desktop one needs to target a specific container + if (!isMobile() && scrollableDesktop.current) { + scrollableDesktop.current.scrollTo(0, 0); + } else if (window) { + window.scrollTo(0, 0); + } + }} + onFilter={onAddFilter} + onMoveColumn={onMoveColumn} + onRemoveColumn={onRemoveColumn} + onSort={onSort} + sampleSize={opts.sampleSize} + useNewFieldsApi={useNewFieldsApi} + /> + )} + {!isLegacy && rows && rows.length && (
{ export const DiscoverGrid = ({ ariaLabelledBy, columns, - defaultColumns, indexPattern, onAddColumn, onFilter, @@ -144,6 +137,7 @@ export const DiscoverGrid = ({ sort, }: DiscoverGridProps) => { const [expanded, setExpanded] = useState(undefined); + const defaultColumns = columns.includes('_source'); /** * Pagination diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.tsx index 481d308cf88a9..6a247ad951c9b 100644 --- a/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.tsx +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_columns.tsx @@ -48,7 +48,12 @@ export function buildEuiGridColumn( id: columnName, schema: getSchemaByKbnType(indexPatternField?.type), isSortable: indexPatternField?.sortable, - display: indexPatternField?.displayName, + display: + columnName === '_source' + ? i18n.translate('discover.grid.documentHeader', { + defaultMessage: 'Document', + }) + : indexPatternField?.displayName, actions: { showHide: defaultColumns || columnName === indexPattern.timeFieldName diff --git a/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.test.tsx b/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.test.tsx new file mode 100644 index 0000000000000..f9428e30569f7 --- /dev/null +++ b/src/plugins/discover/public/application/components/discover_grid/discover_grid_flyout.test.tsx @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import React from 'react'; +import { findTestSubject } from '@elastic/eui/lib/test'; +import { mountWithIntl } from '@kbn/test/jest'; +import { DiscoverGridFlyout } from './discover_grid_flyout'; +import { esHits } from '../../../__mocks__/es_hits'; +import { createFilterManagerMock } from '../../../../../data/public/query/filter_manager/filter_manager.mock'; +import { indexPatternMock } from '../../../__mocks__/index_pattern'; +import { DiscoverServices } from '../../../build_services'; +import { DocViewsRegistry } from '../../doc_views/doc_views_registry'; +import { setDocViewsRegistry } from '../../../kibana_services'; +import { indexPatternWithTimefieldMock } from '../../../__mocks__/index_pattern_with_timefield'; + +describe('Discover flyout', function () { + setDocViewsRegistry(new DocViewsRegistry()); + + it('should be rendered correctly using an index pattern without timefield', async () => { + const onClose = jest.fn(); + const component = mountWithIntl( + + ); + + const url = findTestSubject(component, 'docTableRowAction').prop('href'); + expect(url).toMatchInlineSnapshot(`"#/doc/the-index-pattern-id/i?id=1"`); + findTestSubject(component, 'euiFlyoutCloseButton').simulate('click'); + expect(onClose).toHaveBeenCalled(); + }); + + it('should be rendered correctly using an index pattern with timefield', async () => { + const onClose = jest.fn(); + const component = mountWithIntl( + + ); + + const actions = findTestSubject(component, 'docTableRowAction'); + expect(actions.length).toBe(2); + expect(actions.first().prop('href')).toMatchInlineSnapshot( + `"#/doc/index-pattern-with-timefield-id/i?id=1"` + ); + expect(actions.last().prop('href')).toMatchInlineSnapshot( + `"#/context/index-pattern-with-timefield-id/1?_g=(filters:!())&_a=(columns:!(date),filters:!())"` + ); + findTestSubject(component, 'euiFlyoutCloseButton').simulate('click'); + expect(onClose).toHaveBeenCalled(); + }); +}); diff --git a/src/plugins/discover/public/application/components/discover_legacy.tsx b/src/plugins/discover/public/application/components/discover_legacy.tsx deleted file mode 100644 index 1b90b845a8fff..0000000000000 --- a/src/plugins/discover/public/application/components/discover_legacy.tsx +++ /dev/null @@ -1,517 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import './discover.scss'; - -import React, { useState, useRef } from 'react'; -import { - EuiButtonEmpty, - EuiButtonIcon, - EuiFlexGroup, - EuiFlexItem, - EuiHideFor, - EuiPage, - EuiPageBody, - EuiPageContent, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; -import { IUiSettingsClient, MountPoint } from 'kibana/public'; -import classNames from 'classnames'; -import { HitsCounter } from './hits_counter'; -import { TimechartHeader } from './timechart_header'; -import { getServices, IndexPattern } from '../../kibana_services'; -import { DiscoverUninitialized, DiscoverHistogram } from '../angular/directives'; -import { DiscoverNoResults } from './no_results'; -import { LoadingSpinner } from './loading_spinner/loading_spinner'; -import { DocTableLegacy, DocTableLegacyProps } from '../angular/doc_table/create_doc_table_react'; -import { SkipBottomButton } from './skip_bottom_button'; -import { - search, - ISearchSource, - TimeRange, - Query, - IndexPatternAttributes, - DataPublicPluginStart, - AggConfigs, - FilterManager, -} from '../../../../data/public'; -import { Chart } from '../angular/helpers/point_series'; -import { AppState } from '../angular/discover_state'; -import { SavedSearch } from '../../saved_searches'; -import { SavedObject } from '../../../../../core/types'; -import { TopNavMenuData } from '../../../../navigation/public'; -import { - DiscoverSidebarResponsive, - DiscoverSidebarResponsiveProps, -} from './sidebar/discover_sidebar_responsive'; -import { DocViewFilterFn, ElasticSearchHit } from '../doc_views/doc_views_types'; - -export interface DiscoverProps { - /** - * Function to fetch documents from Elasticsearch - */ - fetch: () => void; - /** - * Counter how often data was fetched (used for testing) - */ - fetchCounter: number; - /** - * Error in case of a failing document fetch - */ - fetchError?: Error; - /** - * Statistics by fields calculated using the fetched documents - */ - fieldCounts: Record; - /** - * Histogram aggregation data - */ - histogramData?: Chart; - /** - * Number of documents found by recent fetch - */ - hits: number; - /** - * Current IndexPattern - */ - indexPattern: IndexPattern; - /** - * Value needed for legacy "infinite" loading functionality - * Determins how much records are rendered using the legacy table - * Increased when scrolling down - */ - minimumVisibleRows: number; - /** - * Function to add a column to state - */ - onAddColumn: (column: string) => void; - /** - * Function to add a filter to state - */ - onAddFilter: DocViewFilterFn; - /** - * Function to change the used time interval of the date histogram - */ - onChangeInterval: (interval: string) => void; - /** - * Function to move a given column to a given index, used in legacy table - */ - onMoveColumn: (columns: string, newIdx: number) => void; - /** - * Function to remove a given column from state - */ - onRemoveColumn: (column: string) => void; - /** - * Function to replace columns in state - */ - onSetColumns: (columns: string[]) => void; - /** - * Function to scroll down the legacy table to the bottom - */ - onSkipBottomButtonClick: () => void; - /** - * Function to change sorting of the table, triggers a fetch - */ - onSort: (sort: string[][]) => void; - opts: { - /** - * Date histogram aggregation config - */ - chartAggConfigs?: AggConfigs; - /** - * Client of uiSettings - */ - config: IUiSettingsClient; - /** - * Data plugin - */ - data: DataPublicPluginStart; - /** - * Data plugin filter manager - */ - filterManager: FilterManager; - /** - * List of available index patterns - */ - indexPatternList: Array>; - /** - * The number of documents that can be displayed in the table/grid - */ - sampleSize: number; - /** - * Current instance of SavedSearch - */ - savedSearch: SavedSearch; - /** - * Function to set the header menu - */ - setHeaderActionMenu: (menuMount: MountPoint | undefined) => void; - /** - * Timefield of the currently used index pattern - */ - timefield: string; - /** - * Function to set the current state - */ - setAppState: (state: Partial) => void; - }; - /** - * Function to reset the current query - */ - resetQuery: () => void; - /** - * Current state of the actual query, one of 'uninitialized', 'loading' ,'ready', 'none' - */ - resultState: string; - /** - * Array of document of the recent successful search request - */ - rows: ElasticSearchHit[]; - /** - * Instance of SearchSource, the high level search API - */ - searchSource: ISearchSource; - /** - * Function to change the current index pattern - */ - setIndexPattern: (id: string) => void; - /** - * Determines whether the user should be able to use the save query feature - */ - showSaveQuery: boolean; - /** - * Current app state of URL - */ - state: AppState; - /** - * Function to update the time filter - */ - timefilterUpdateHandler: (ranges: { from: number; to: number }) => void; - /** - * Currently selected time range - */ - timeRange?: { from: string; to: string }; - /** - * Menu data of top navigation (New, save ...) - */ - topNavMenu: TopNavMenuData[]; - /** - * Function to update the actual query - */ - updateQuery: (payload: { dateRange: TimeRange; query?: Query }, isUpdate?: boolean) => void; - /** - * Function to update the actual savedQuery id - */ - updateSavedQueryId: (savedQueryId?: string) => void; - useNewFieldsApi?: boolean; -} - -export const DocTableLegacyMemoized = React.memo((props: DocTableLegacyProps) => ( - -)); -export const SidebarMemoized = React.memo((props: DiscoverSidebarResponsiveProps) => ( - -)); - -export function DiscoverLegacy({ - fetch, - fetchCounter, - fieldCounts, - fetchError, - histogramData, - hits, - indexPattern, - minimumVisibleRows, - onAddColumn, - onAddFilter, - onChangeInterval, - onMoveColumn, - onRemoveColumn, - onSkipBottomButtonClick, - onSort, - opts, - resetQuery, - resultState, - rows, - searchSource, - setIndexPattern, - showSaveQuery, - state, - timefilterUpdateHandler, - timeRange, - topNavMenu, - updateQuery, - updateSavedQueryId, - useNewFieldsApi, -}: DiscoverProps) { - const scrollableDesktop = useRef(null); - const collapseIcon = useRef(null); - const isMobile = () => { - // collapse icon isn't displayed in mobile view, use it to detect which view is displayed - return collapseIcon && !collapseIcon.current; - }; - - const [toggleOn, toggleChart] = useState(true); - const [isSidebarClosed, setIsSidebarClosed] = useState(false); - const services = getServices(); - const { TopNavMenu } = services.navigation.ui; - const { trackUiMetric } = services; - const { savedSearch, indexPatternList } = opts; - const bucketAggConfig = opts.chartAggConfigs?.aggs[1]; - const bucketInterval = - bucketAggConfig && search.aggs.isDateHistogramBucketAggConfig(bucketAggConfig) - ? bucketAggConfig.buckets?.getInterval() - : undefined; - const contentCentered = resultState === 'uninitialized'; - - const getDisplayColumns = () => { - if (!state.columns) { - return []; - } - const columns = [...state.columns]; - if (useNewFieldsApi) { - return columns.filter((column) => column !== '_source'); - } - return columns.length === 0 ? ['_source'] : columns; - }; - - return ( - - - - -

- {savedSearch.title} -

- - - - - - - setIsSidebarClosed(!isSidebarClosed)} - data-test-subj="collapseSideBarButton" - aria-controls="discover-sidebar" - aria-expanded={isSidebarClosed ? 'false' : 'true'} - aria-label="Toggle sidebar" - buttonRef={collapseIcon} - /> - - - - - {resultState === 'none' && ( - - )} - {resultState === 'uninitialized' && } - {resultState === 'loading' && } - {resultState === 'ready' && ( - - - - - 0 ? hits : 0} - showResetButton={!!(savedSearch && savedSearch.id)} - onResetQuery={resetQuery} - /> - - {toggleOn && ( - - - - )} - {opts.timefield && ( - - { - toggleChart(!toggleOn); - }} - data-test-subj="discoverChartToggle" - > - {toggleOn - ? i18n.translate('discover.hideChart', { - defaultMessage: 'Hide chart', - }) - : i18n.translate('discover.showChart', { - defaultMessage: 'Show chart', - })} - - - )} - - - - {toggleOn && opts.timefield && ( - -
- {opts.chartAggConfigs && rows.length !== 0 && histogramData && ( -
- -
- )} -
-
- )} - - -
-

- -

- {rows && rows.length && ( -
- - {rows.length === opts.sampleSize ? ( -
- - - { - if (scrollableDesktop && scrollableDesktop.current) { - scrollableDesktop.current.focus(); - } - // Only the desktop one needs to target a specific container - if (!isMobile() && scrollableDesktop.current) { - scrollableDesktop.current.scrollTo(0, 0); - } else if (window) { - window.scrollTo(0, 0); - } - }} - > - - -
- ) : ( - - ​ - - )} -
- )} -
-
-
- )} -
-
-
-
-
-
- ); -} diff --git a/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.scss b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.scss index 5bae3d64a6b69..95a50b54b5364 100644 --- a/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.scss +++ b/src/plugins/discover/public/application/components/doc_viewer/doc_viewer.scss @@ -19,6 +19,14 @@ padding-top: $euiSizeS; } + .kbnDocViewer__multifield_row:hover td { + background-color: transparent; + } + + .kbnDocViewer__multifield_title { + font-family: $euiFontFamily; + } + .dscFieldName { color: $euiColorDarkShade; } diff --git a/src/plugins/discover/public/application/components/table/table.test.tsx b/src/plugins/discover/public/application/components/table/table.test.tsx index 1ffc0e5af95ac..ac074cf229c77 100644 --- a/src/plugins/discover/public/application/components/table/table.test.tsx +++ b/src/plugins/discover/public/application/components/table/table.test.tsx @@ -167,30 +167,6 @@ describe('DocViewTable at Discover', () => { }); }); -describe('DocViewTable at Discover Doc', () => { - const hit = { - _index: 'logstash-2014.09.09', - _score: 1, - _type: 'doc', - _id: 'id123', - _source: { - extension: 'html', - not_mapped: 'yes', - }, - }; - // here no action buttons are rendered - const props = { - hit, - indexPattern, - }; - const component = mount(); - const foundLength = findTestSubject(component, 'addInclusiveFilterButton').length; - - it(`renders no action buttons`, () => { - expect(foundLength).toBe(0); - }); -}); - describe('DocViewTable at Discover Context', () => { // here no toggleColumnButtons are rendered const hit = { @@ -243,3 +219,172 @@ describe('DocViewTable at Discover Context', () => { expect(component.html() !== html).toBeTruthy(); }); }); + +describe('DocViewTable at Discover Doc', () => { + const hit = { + _index: 'logstash-2014.09.09', + _score: 1, + _type: 'doc', + _id: 'id123', + _source: { + extension: 'html', + not_mapped: 'yes', + }, + }; + // here no action buttons are rendered + const props = { + hit, + indexPattern, + }; + const component = mount(); + const foundLength = findTestSubject(component, 'addInclusiveFilterButton').length; + + it(`renders no action buttons`, () => { + expect(foundLength).toBe(0); + }); +}); + +describe('DocViewTable at Discover Doc with Fields API', () => { + const indexPatterneCommerce = ({ + fields: { + getAll: () => [ + { + name: '_index', + type: 'string', + scripted: false, + filterable: true, + }, + { + name: 'category', + type: 'string', + scripted: false, + filterable: true, + }, + { + name: 'category.keyword', + displayName: 'category.keyword', + type: 'string', + scripted: false, + filterable: true, + spec: { + subType: { + multi: { + parent: 'category', + }, + }, + }, + }, + { + name: 'customer_first_name', + type: 'string', + scripted: false, + filterable: true, + }, + { + name: 'customer_first_name.keyword', + displayName: 'customer_first_name.keyword', + type: 'string', + scripted: false, + filterable: false, + spec: { + subType: { + multi: { + parent: 'customer_first_name', + }, + }, + }, + }, + { + name: 'customer_first_name.nickname', + displayName: 'customer_first_name.nickname', + type: 'string', + scripted: false, + filterable: false, + spec: { + subType: { + multi: { + parent: 'customer_first_name', + }, + }, + }, + }, + ], + }, + metaFields: ['_index', '_type', '_score', '_id'], + flattenHit: jest.fn((hit) => { + const result = {} as Record; + Object.keys(hit).forEach((key) => { + if (key !== 'fields') { + result[key] = hit[key]; + } else { + Object.keys(hit.fields).forEach((field) => { + result[field] = hit.fields[field]; + }); + } + }); + return result; + }), + formatHit: jest.fn((hit) => { + const result = {} as Record; + Object.keys(hit).forEach((key) => { + if (key !== 'fields') { + result[key] = hit[key]; + } else { + Object.keys(hit.fields).forEach((field) => { + result[field] = hit.fields[field]; + }); + } + }); + return result; + }), + } as unknown) as IndexPattern; + + indexPatterneCommerce.fields.getByName = (name: string) => { + return indexPatterneCommerce.fields.getAll().find((field) => field.name === name); + }; + + const fieldsHit = { + _index: 'logstash-2014.09.09', + _type: 'doc', + _id: 'id123', + _score: null, + fields: { + category: "Women's Clothing", + 'category.keyword': "Women's Clothing", + customer_first_name: 'Betty', + 'customer_first_name.keyword': 'Betty', + 'customer_first_name.nickname': 'Betsy', + }, + }; + const props = { + hit: fieldsHit, + columns: ['Document'], + indexPattern: indexPatterneCommerce, + filter: jest.fn(), + onAddColumn: jest.fn(), + onRemoveColumn: jest.fn(), + }; + // @ts-ignore + const component = mount(); + it('renders multifield rows', () => { + const categoryMultifieldRow = findTestSubject( + component, + 'tableDocViewRow-multifieldsTitle-category' + ); + expect(categoryMultifieldRow.length).toBe(1); + const categoryKeywordRow = findTestSubject(component, 'tableDocViewRow-category.keyword'); + expect(categoryKeywordRow.length).toBe(1); + + const customerNameMultiFieldRow = findTestSubject( + component, + 'tableDocViewRow-multifieldsTitle-customer_first_name' + ); + expect(customerNameMultiFieldRow.length).toBe(1); + expect(findTestSubject(component, 'tableDocViewRow-customer_first_name.keyword').length).toBe( + 1 + ); + expect(findTestSubject(component, 'tableDocViewRow-customer_first_name.nickname').length).toBe( + 1 + ); + }); +}); diff --git a/src/plugins/discover/public/application/components/table/table.tsx b/src/plugins/discover/public/application/components/table/table.tsx index 090df8baba409..7528828a06f97 100644 --- a/src/plugins/discover/public/application/components/table/table.tsx +++ b/src/plugins/discover/public/application/components/table/table.tsx @@ -5,8 +5,8 @@ * compliance with, at your election, the Elastic License or the Server Side * Public License, v 1. */ - -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; import { DocViewTableRow } from './table_row'; import { trimAngularSpan } from './table_helper'; import { isNestedFieldParent } from '../../helpers/nested_fields'; @@ -23,6 +23,36 @@ export function DocViewTable({ onRemoveColumn, }: DocViewRenderProps) { const [fieldRowOpen, setFieldRowOpen] = useState({} as Record); + const [multiFields, setMultiFields] = useState({} as Record); + const [fieldsWithParents, setFieldsWithParents] = useState([] as string[]); + + useEffect(() => { + if (!indexPattern) { + return; + } + const mapping = indexPattern.fields.getByName; + const flattened = indexPattern.flattenHit(hit); + const map: Record = {}; + const arr: string[] = []; + + Object.keys(flattened).forEach((key) => { + const field = mapping(key); + + if (field && field.spec?.subType?.multi?.parent) { + const parent = field.spec.subType.multi.parent; + if (!map[parent]) { + map[parent] = [] as string[]; + } + const value = map[parent]; + value.push(key); + map[parent] = value; + arr.push(key); + } + }); + setMultiFields(map); + setFieldsWithParents(arr); + }, [indexPattern, hit]); + if (!indexPattern) { return null; } @@ -34,11 +64,13 @@ export function DocViewTable({ fieldRowOpen[field] = !fieldRowOpen[field]; setFieldRowOpen({ ...fieldRowOpen }); } - return ( {Object.keys(flattened) + .filter((field) => { + return !fieldsWithParents.includes(field); + }) .sort((fieldA, fieldB) => { const mappingA = mapping(fieldA); const mappingB = mapping(fieldB); @@ -67,23 +99,60 @@ export function DocViewTable({ const fieldType = isNestedFieldParent(field, indexPattern) ? 'nested' : indexPattern.fields.getByName(field)?.type; - return ( - toggleValueCollapse(field)} - onToggleColumn={toggleColumn} - value={value} - valueRaw={valueRaw} - /> + + toggleValueCollapse(field)} + onToggleColumn={toggleColumn} + value={value} + valueRaw={valueRaw} + /> + {multiFields[field] ? ( + + + + + ) : null} + {multiFields[field] + ? multiFields[field].map((multiField) => { + return ( + toggleValueCollapse(field)} + onToggleColumn={toggleColumn} + value={value} + valueRaw={valueRaw} + /> + ); + }) + : null} + ); })} diff --git a/src/plugins/discover/public/application/components/table/table_row.tsx b/src/plugins/discover/public/application/components/table/table_row.tsx index 61f9fa50091c1..2b91a757e9bc2 100644 --- a/src/plugins/discover/public/application/components/table/table_row.tsx +++ b/src/plugins/discover/public/application/components/table/table_row.tsx @@ -18,7 +18,7 @@ import { DocViewTableRowIconUnderscore } from './table_row_icon_underscore'; import { FieldName } from '../field_name/field_name'; export interface Props { - field: string; + field?: string; fieldMapping?: FieldMapping; fieldType: string; displayUnderscoreWarning: boolean; @@ -51,25 +51,30 @@ export function DocViewTableRow({ kbnDocViewer__value: true, 'truncate-by-height': isCollapsible && isCollapsed, }); - + const key = field ? field : fieldMapping?.displayName; return ( - +
  + + {i18n.translate('discover.fieldChooser.discoverField.multiFields', { + defaultMessage: 'Multi fields', + })} + +
- + {field ? ( + + ) : ( +   + )} {isCollapsible && ( )} {displayUnderscoreWarning && } + {field ? null :
{key}: 
}
void; + /** + * Counter how often data was fetched (used for testing) + */ + fetchCounter: number; + /** + * Error in case of a failing document fetch + */ + fetchError?: Error; + /** + * Statistics by fields calculated using the fetched documents + */ + fieldCounts: Record; + /** + * Histogram aggregation data + */ + histogramData?: Chart; + /** + * Number of documents found by recent fetch + */ + hits: number; + /** + * Current IndexPattern + */ + indexPattern: IndexPattern; + /** + * Value needed for legacy "infinite" loading functionality + * Determins how much records are rendered using the legacy table + * Increased when scrolling down + */ + minimumVisibleRows: number; + /** + * Function to add a column to state + */ + onAddColumn: (column: string) => void; + /** + * Function to add a filter to state + */ + onAddFilter: DocViewFilterFn; + /** + * Function to change the used time interval of the date histogram + */ + onChangeInterval: (interval: string) => void; + /** + * Function to move a given column to a given index, used in legacy table + */ + onMoveColumn: (columns: string, newIdx: number) => void; + /** + * Function to remove a given column from state + */ + onRemoveColumn: (column: string) => void; + /** + * Function to replace columns in state + */ + onSetColumns: (columns: string[]) => void; + /** + * Function to scroll down the legacy table to the bottom + */ + onSkipBottomButtonClick: () => void; + /** + * Function to change sorting of the table, triggers a fetch + */ + onSort: (sort: string[][]) => void; + opts: { + /** + * Date histogram aggregation config + */ + chartAggConfigs?: AggConfigs; + /** + * Client of uiSettings + */ + config: IUiSettingsClient; + /** + * Data plugin + */ + data: DataPublicPluginStart; + /** + * Data plugin filter manager + */ + filterManager: FilterManager; + /** + * List of available index patterns + */ + indexPatternList: Array>; + /** + * The number of documents that can be displayed in the table/grid + */ + sampleSize: number; + /** + * Current instance of SavedSearch + */ + savedSearch: SavedSearch; + /** + * Function to set the header menu + */ + setHeaderActionMenu: (menuMount: MountPoint | undefined) => void; + /** + * Timefield of the currently used index pattern + */ + timefield: string; + /** + * Function to set the current state + */ + setAppState: (state: Partial) => void; + }; + /** + * Function to reset the current query + */ + resetQuery: () => void; + /** + * Current state of the actual query, one of 'uninitialized', 'loading' ,'ready', 'none' + */ + resultState: string; + /** + * Array of document of the recent successful search request + */ + rows: ElasticSearchHit[]; + /** + * Instance of SearchSource, the high level search API + */ + searchSource: ISearchSource; + /** + * Function to change the current index pattern + */ + setIndexPattern: (id: string) => void; + /** + * Current app state of URL + */ + state: AppState; + /** + * Function to update the time filter + */ + timefilterUpdateHandler: (ranges: { from: number; to: number }) => void; + /** + * Currently selected time range + */ + timeRange?: { from: string; to: string }; + /** + * Menu data of top navigation (New, save ...) + */ + topNavMenu: TopNavMenuData[]; + /** + * Function to update the actual query + */ + updateQuery: (payload: { dateRange: TimeRange; query?: Query }, isUpdate?: boolean) => void; + /** + * Function to update the actual savedQuery id + */ + updateSavedQueryId: (savedQueryId?: string) => void; +} diff --git a/src/plugins/discover/public/application/helpers/columns.test.ts b/src/plugins/discover/public/application/helpers/columns.test.ts new file mode 100644 index 0000000000000..d455fd1f42c6d --- /dev/null +++ b/src/plugins/discover/public/application/helpers/columns.test.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { getDisplayedColumns } from './columns'; +import { indexPatternWithTimefieldMock } from '../../__mocks__/index_pattern_with_timefield'; +import { indexPatternMock } from '../../__mocks__/index_pattern'; + +describe('getDisplayedColumns', () => { + test('returns default columns given a index pattern without timefield', async () => { + const result = getDisplayedColumns([], indexPatternMock); + expect(result).toMatchInlineSnapshot(` + Array [ + "_source", + ] + `); + }); + test('returns default columns given a index pattern with timefield', async () => { + const result = getDisplayedColumns([], indexPatternWithTimefieldMock); + expect(result).toMatchInlineSnapshot(` + Array [ + "_source", + ] + `); + }); + test('returns default columns when just timefield is in state', async () => { + const result = getDisplayedColumns(['timestamp'], indexPatternWithTimefieldMock); + expect(result).toMatchInlineSnapshot(` + Array [ + "_source", + ] + `); + }); + test('returns columns given by argument, no fallback ', async () => { + const result = getDisplayedColumns(['test'], indexPatternWithTimefieldMock); + expect(result).toMatchInlineSnapshot(` + Array [ + "test", + ] + `); + }); +}); diff --git a/src/plugins/discover/public/application/helpers/columns.ts b/src/plugins/discover/public/application/helpers/columns.ts new file mode 100644 index 0000000000000..d2d47c932b7bd --- /dev/null +++ b/src/plugins/discover/public/application/helpers/columns.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ +import { IndexPattern } from '../../../../data/common'; + +/** + * Function to provide fallback when + * 1) no columns are given + * 2) Just one column is given, which is the configured timefields + */ +export function getDisplayedColumns(stateColumns: string[] = [], indexPattern: IndexPattern) { + return stateColumns && + stateColumns.length > 0 && + // check if all columns where removed except the configured timeField (this can't be removed) + !(stateColumns.length === 1 && stateColumns[0] === indexPattern.timeFieldName) + ? stateColumns + : ['_source']; +} diff --git a/src/plugins/discover/public/application/helpers/get_sharing_data.test.ts b/src/plugins/discover/public/application/helpers/get_sharing_data.test.ts index 1394ceab1dd18..ea16b81615e42 100644 --- a/src/plugins/discover/public/application/helpers/get_sharing_data.test.ts +++ b/src/plugins/discover/public/application/helpers/get_sharing_data.test.ts @@ -6,7 +6,8 @@ * Public License, v 1. */ -import { getSharingData } from './get_sharing_data'; +import { Capabilities } from 'kibana/public'; +import { getSharingData, showPublicUrlSwitch } from './get_sharing_data'; import { IUiSettingsClient } from 'kibana/public'; import { createSearchSourceMock } from '../../../../data/common/search/search_source/mocks'; import { indexPatternMock } from '../../__mocks__/index_pattern'; @@ -68,3 +69,44 @@ describe('getSharingData', () => { `); }); }); + +describe('showPublicUrlSwitch', () => { + test('returns false if "discover" app is not available', () => { + const anonymousUserCapabilities: Capabilities = { + catalogue: {}, + management: {}, + navLinks: {}, + }; + const result = showPublicUrlSwitch(anonymousUserCapabilities); + + expect(result).toBe(false); + }); + + test('returns false if "discover" app is not accessible', () => { + const anonymousUserCapabilities: Capabilities = { + catalogue: {}, + management: {}, + navLinks: {}, + discover: { + show: false, + }, + }; + const result = showPublicUrlSwitch(anonymousUserCapabilities); + + expect(result).toBe(false); + }); + + test('returns true if "discover" app is not available an accessible', () => { + const anonymousUserCapabilities: Capabilities = { + catalogue: {}, + management: {}, + navLinks: {}, + discover: { + show: true, + }, + }; + const result = showPublicUrlSwitch(anonymousUserCapabilities); + + expect(result).toBe(true); + }); +}); diff --git a/src/plugins/discover/public/application/helpers/get_sharing_data.ts b/src/plugins/discover/public/application/helpers/get_sharing_data.ts index 62478f1d2830f..1d780a5573e2a 100644 --- a/src/plugins/discover/public/application/helpers/get_sharing_data.ts +++ b/src/plugins/discover/public/application/helpers/get_sharing_data.ts @@ -6,7 +6,7 @@ * Public License, v 1. */ -import { IUiSettingsClient } from 'kibana/public'; +import { Capabilities, IUiSettingsClient } from 'kibana/public'; import { DOC_HIDE_TIME_COLUMN_SETTING, SORT_DEFAULT_ORDER_SETTING } from '../../../common'; import { getSortForSearchSource } from '../angular/doc_table'; import { SearchSource } from '../../../../data/common'; @@ -76,3 +76,19 @@ export async function getSharingData( indexPatternId: index.id, }; } + +export interface DiscoverCapabilities { + createShortUrl?: boolean; + save?: boolean; + saveQuery?: boolean; + show?: boolean; + storeSearchSession?: boolean; +} + +export const showPublicUrlSwitch = (anonymousUserCapabilities: Capabilities) => { + if (!anonymousUserCapabilities.discover) return false; + + const discover = (anonymousUserCapabilities.discover as unknown) as DiscoverCapabilities; + + return !!discover.show; +}; diff --git a/src/plugins/discover/public/get_inner_angular.ts b/src/plugins/discover/public/get_inner_angular.ts index b27426a6c0621..4eda742d967f4 100644 --- a/src/plugins/discover/public/get_inner_angular.ts +++ b/src/plugins/discover/public/get_inner_angular.ts @@ -42,7 +42,6 @@ import { } from '../../kibana_legacy/public'; import { DiscoverStartPlugins } from './plugin'; import { getScopedHistory } from './kibana_services'; -import { createDiscoverLegacyDirective } from './application/components/create_discover_legacy_directive'; import { createDiscoverDirective } from './application/components/create_discover_directive'; /** @@ -124,7 +123,6 @@ export function initializeInnerAngularModule( .config(watchMultiDecorator) .run(registerListenEventListener) .directive('renderComplete', createRenderCompleteDirective) - .directive('discoverLegacy', createDiscoverLegacyDirective) .directive('discover', createDiscoverDirective); } diff --git a/src/plugins/kibana_usage_collection/README.md b/src/plugins/kibana_usage_collection/README.md index 69711d30cdc74..85d362cf0a9b1 100644 --- a/src/plugins/kibana_usage_collection/README.md +++ b/src/plugins/kibana_usage_collection/README.md @@ -6,6 +6,6 @@ This plugin registers the basic usage collectors from Kibana: - UI Metrics - Ops stats - Number of Saved Objects per type -- Non-default UI Settings +- [User-changed UI Settings](./server/collectors/management/README.md) - CSP configuration - Core Metrics diff --git a/src/plugins/kibana_usage_collection/common/constants.ts b/src/plugins/kibana_usage_collection/common/constants.ts index 4505c59e0f630..052367765a6ec 100644 --- a/src/plugins/kibana_usage_collection/common/constants.ts +++ b/src/plugins/kibana_usage_collection/common/constants.ts @@ -13,3 +13,7 @@ export const PLUGIN_NAME = 'kibana_usage_collection'; * The type name used to publish Kibana usage stats in the formatted as bulk. */ export const KIBANA_STATS_TYPE = 'kibana_stats'; +/** + * Redacted keyword; used as a value for sensitive ui settings + */ +export const REDACTED_KEYWORD = '[REDACTED]'; diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/README.md b/src/plugins/kibana_usage_collection/server/collectors/management/README.md new file mode 100644 index 0000000000000..b539136d57b89 --- /dev/null +++ b/src/plugins/kibana_usage_collection/server/collectors/management/README.md @@ -0,0 +1,51 @@ +# User-changed UI Settings - Management Collector + +The Usage Collector `stack_management` reports user changed settings. +All user changed UI Settings are automatically collected. + +After adding a new setting you will be required to do the following steps: + +1. Update the [schema](./schema.ts) to include the setting name and schema type. +``` +export const stackManagementSchema: MakeSchemaFrom = { + 'MY_UI_SETTING': { type: 'keyword' }, +} +``` + +2. Update the [UsageStats interface](./types.ts) with the setting name and typescript type. +``` +export interface UsageStats { + 'MY_UI_SETTING': string; +} +``` +3. Run the telemetry checker with `--fix` flag to automatically fix the mappings + +``` +node scripts/telemetry_check --fix +``` + +If you forget any of the steps our telemetry tools and tests will help you through the process! + +## Sensitive fields + +If the configured UI setting might contain user sensitive information simply add the property `sensitive: true` to the ui setting registration config. + +``` +uiSettings.register({ + [NEWS_FEED_URL_SETTING]: { + name: i18n.translate('xpack.securitySolution.uiSettings.newsFeedUrl', { + defaultMessage: 'News feed URL', + }), + value: NEWS_FEED_URL_SETTING_DEFAULT, + sensitive: true, + description: i18n.translate('xpack.securitySolution.uiSettings.newsFeedUrlDescription', { + defaultMessage: '

News feed content will be retrieved from this URL

', + }), + category: [APP_ID], + requiresPageReload: true, + schema: schema.string(), + }, +}), +``` + +The value of any UI setting marked as `sensitive` will be reported as a keyword `[REDACTED]` instead of the actual value. This hides the actual sensitive information while giving us some intelligence over which fields the users are interactive with the most. diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/__snapshots__/index.test.ts.snap b/src/plugins/kibana_usage_collection/server/collectors/management/__snapshots__/index.test.ts.snap deleted file mode 100644 index def230dea8d70..0000000000000 --- a/src/plugins/kibana_usage_collection/server/collectors/management/__snapshots__/index.test.ts.snap +++ /dev/null @@ -1,7 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`telemetry_application_usage_collector fetch() 1`] = ` -Object { - "my-key": "my-value", -} -`; diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/index.test.ts b/src/plugins/kibana_usage_collection/server/collectors/management/index.test.ts deleted file mode 100644 index 38baf02d6fe1b..0000000000000 --- a/src/plugins/kibana_usage_collection/server/collectors/management/index.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { loggingSystemMock, uiSettingsServiceMock } from '../../../../../core/server/mocks'; -import { - Collector, - createUsageCollectionSetupMock, - createCollectorFetchContextMock, -} from '../../../../usage_collection/server/usage_collection.mock'; - -import { registerManagementUsageCollector } from './'; - -const logger = loggingSystemMock.createLogger(); - -describe('telemetry_application_usage_collector', () => { - let collector: Collector; - - const usageCollectionMock = createUsageCollectionSetupMock(); - usageCollectionMock.makeUsageCollector.mockImplementation((config) => { - collector = new Collector(logger, config); - return createUsageCollectionSetupMock().makeUsageCollector(config); - }); - - const uiSettingsClient = uiSettingsServiceMock.createClient(); - const getUiSettingsClient = jest.fn(() => uiSettingsClient); - const mockedFetchContext = createCollectorFetchContextMock(); - - beforeAll(() => { - registerManagementUsageCollector(usageCollectionMock, getUiSettingsClient); - }); - - test('registered collector is set', () => { - expect(collector).not.toBeUndefined(); - }); - - test('isReady() => false if no client', () => { - getUiSettingsClient.mockImplementationOnce(() => undefined as any); - expect(collector.isReady()).toBe(false); - }); - - test('isReady() => true', () => { - expect(collector.isReady()).toBe(true); - }); - - test('fetch()', async () => { - uiSettingsClient.getUserProvided.mockImplementationOnce(async () => ({ - 'my-key': { userValue: 'my-value' }, - })); - await expect(collector.fetch(mockedFetchContext)).resolves.toMatchSnapshot(); - }); - - test('fetch() should not fail if invoked when not ready', async () => { - getUiSettingsClient.mockImplementationOnce(() => undefined as any); - await expect(collector.fetch(mockedFetchContext)).resolves.toBe(undefined); - }); -}); diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts index 28eeb461f7a86..b644f282c1f36 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts @@ -7,18 +7,25 @@ */ import { MakeSchemaFrom } from 'src/plugins/usage_collection/server'; -import { UsageStats } from './telemetry_management_collector'; +import { UsageStats } from './types'; -// Retrieved by changing all the current settings in Kibana (we'll need to revisit it in the future). -// I would suggest we use flattened type for the mappings of this collector. export const stackManagementSchema: MakeSchemaFrom = { + // sensitive + 'timelion:quandl.key': { type: 'keyword' }, + 'securitySolution:defaultIndex': { type: 'keyword' }, + 'securitySolution:newsFeedUrl': { type: 'keyword' }, + 'xpackReporting:customPdfLogo': { type: 'keyword' }, + 'notifications:banner': { type: 'keyword' }, + 'timelion:graphite.url': { type: 'keyword' }, + 'xpackDashboardMode:roles': { type: 'keyword' }, + 'securitySolution:ipReputationLinks': { type: 'keyword' }, + // non-sensitive 'visualize:enableLabs': { type: 'boolean' }, 'visualization:heatmap:maxBuckets': { type: 'long' }, 'visualization:colorMapping': { type: 'text' }, 'visualization:regionmap:showWarnings': { type: 'boolean' }, 'visualization:dimmingOpacity': { type: 'float' }, 'visualization:tileMap:maxPrecision': { type: 'long' }, - 'securitySolution:ipReputationLinks': { type: 'text' }, 'csv:separator': { type: 'keyword' }, 'visualization:tileMap:WMSdefaults': { type: 'text' }, 'timelion:target_buckets': { type: 'long' }, @@ -27,14 +34,11 @@ export const stackManagementSchema: MakeSchemaFrom = { 'timelion:min_interval': { type: 'keyword' }, 'timelion:default_rows': { type: 'long' }, 'timelion:default_columns': { type: 'long' }, - 'timelion:quandl.key': { type: 'keyword' }, 'timelion:es.default_index': { type: 'keyword' }, 'timelion:showTutorial': { type: 'boolean' }, 'securitySolution:timeDefaults': { type: 'keyword' }, 'securitySolution:defaultAnomalyScore': { type: 'long' }, - 'securitySolution:defaultIndex': { type: 'keyword' }, // it's an array 'securitySolution:refreshIntervalDefaults': { type: 'keyword' }, - 'securitySolution:newsFeedUrl': { type: 'keyword' }, 'securitySolution:enableNewsFeed': { type: 'boolean' }, 'search:includeFrozen': { type: 'boolean' }, 'courier:maxConcurrentShardRequests': { type: 'long' }, @@ -43,21 +47,29 @@ export const stackManagementSchema: MakeSchemaFrom = { 'courier:customRequestPreference': { type: 'keyword' }, 'courier:ignoreFilterIfFieldNotInIndex': { type: 'boolean' }, 'rollups:enableIndexPatterns': { type: 'boolean' }, - 'xpackReporting:customPdfLogo': { type: 'text' }, 'notifications:lifetime:warning': { type: 'long' }, 'notifications:lifetime:banner': { type: 'long' }, 'notifications:lifetime:info': { type: 'long' }, - 'notifications:banner': { type: 'text' }, 'notifications:lifetime:error': { type: 'long' }, 'doc_table:highlight': { type: 'boolean' }, 'discover:searchOnPageLoad': { type: 'boolean' }, // eslint-disable-next-line @typescript-eslint/naming-convention 'doc_table:hideTimeColumn': { type: 'boolean' }, 'discover:sampleSize': { type: 'long' }, - defaultColumns: { type: 'keyword' }, // it's an array + defaultColumns: { + type: 'array', + items: { + type: 'keyword', + }, + }, 'context:defaultSize': { type: 'long' }, 'discover:aggs:terms:size': { type: 'long' }, - 'context:tieBreakerFields': { type: 'keyword' }, // it's an array + 'context:tieBreakerFields': { + type: 'array', + items: { + type: 'keyword', + }, + }, 'discover:sort:defaultOrder': { type: 'keyword' }, 'context:step': { type: 'long' }, 'accessibility:disableAnimations': { type: 'boolean' }, @@ -79,7 +91,12 @@ export const stackManagementSchema: MakeSchemaFrom = { 'query:queryString:options': { type: 'keyword' }, 'metrics:max_buckets': { type: 'long' }, 'query:allowLeadingWildcards': { type: 'boolean' }, - metaFields: { type: 'keyword' }, // it's an array + metaFields: { + type: 'array', + items: { + type: 'keyword', + }, + }, 'indexPattern:placeholder': { type: 'keyword' }, 'histogram:barTarget': { type: 'long' }, 'histogram:maxBars': { type: 'long' }, @@ -101,4 +118,14 @@ export const stackManagementSchema: MakeSchemaFrom = { 'csv:quoteValues': { type: 'boolean' }, 'dateFormat:dow': { type: 'keyword' }, dateFormat: { type: 'keyword' }, + 'autocomplete:useTimeRange': { type: 'boolean' }, + 'search:timeout': { type: 'long' }, + 'visualization:visualize:legacyChartsLibrary': { type: 'boolean' }, + 'doc_table:legacy': { type: 'boolean' }, + 'discover:modifyColumnsOnSwitch': { type: 'boolean' }, + 'discover:searchFieldsFromSource': { type: 'boolean' }, + 'securitySolution:rulesTableRefresh': { type: 'text' }, + 'apm:enableSignificantTerms': { type: 'boolean' }, + 'apm:enableServiceOverview': { type: 'boolean' }, + 'apm:enableCorrelations': { type: 'boolean' }, }; diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.test.ts b/src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.test.ts new file mode 100644 index 0000000000000..4bcd98f894e2a --- /dev/null +++ b/src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.test.ts @@ -0,0 +1,140 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { loggingSystemMock, uiSettingsServiceMock } from '../../../../../core/server/mocks'; +import { + Collector, + createUsageCollectionSetupMock, + createCollectorFetchContextMock, +} from '../../../../usage_collection/server/usage_collection.mock'; + +import { + registerManagementUsageCollector, + createCollectorFetch, +} from './telemetry_management_collector'; + +const logger = loggingSystemMock.createLogger(); + +describe('telemetry_application_usage_collector', () => { + let collector: Collector; + + const usageCollectionMock = createUsageCollectionSetupMock(); + usageCollectionMock.makeUsageCollector.mockImplementation((config) => { + collector = new Collector(logger, config); + return createUsageCollectionSetupMock().makeUsageCollector(config); + }); + + const uiSettingsClient = uiSettingsServiceMock.createClient(); + const getUiSettingsClient = jest.fn(() => uiSettingsClient); + const mockedFetchContext = createCollectorFetchContextMock(); + + beforeAll(() => { + registerManagementUsageCollector(usageCollectionMock, getUiSettingsClient); + }); + + test('registered collector is set', () => { + expect(collector).not.toBeUndefined(); + }); + + test('isReady() => false if no client', () => { + getUiSettingsClient.mockImplementationOnce(() => undefined as any); + expect(collector.isReady()).toBe(false); + }); + + test('isReady() => true', () => { + expect(collector.isReady()).toBe(true); + }); + + test('fetch()', async () => { + uiSettingsClient.getUserProvided.mockImplementationOnce(async () => ({ + 'visualization:colorMapping': { userValue: 'red' }, + })); + await expect(collector.fetch(mockedFetchContext)).resolves.toEqual({ + 'visualization:colorMapping': 'red', + }); + }); + + test('fetch() should not fail if invoked when not ready', async () => { + getUiSettingsClient.mockImplementationOnce(() => undefined as any); + await expect(collector.fetch(mockedFetchContext)).resolves.toBe(undefined); + }); +}); + +describe('createCollectorFetch', () => { + const mockUserSettings = { + item1: { userValue: 'test' }, + item2: { userValue: 123 }, + item3: { userValue: false }, + }; + + const mockIsSensitive = (key: string) => { + switch (key) { + case 'item1': + case 'item2': + return false; + case 'item3': + return true; + default: + throw new Error(`Unexpected ui setting: ${key}`); + } + }; + + it('returns #fetchUsageStats function', () => { + const getUiSettingsClient = jest.fn(() => undefined); + const fetchFunction = createCollectorFetch(getUiSettingsClient); + expect(typeof fetchFunction).toBe('function'); + }); + + describe('#fetchUsageStats', () => { + it('returns undefined if no uiSettingsClient returned from getUiSettingsClient', async () => { + const getUiSettingsClient = jest.fn(() => undefined); + const fetchFunction = createCollectorFetch(getUiSettingsClient); + const result = await fetchFunction(); + expect(result).toBe(undefined); + expect(getUiSettingsClient).toBeCalledTimes(1); + }); + + it('returns all user changed settings', async () => { + const uiSettingsClient = uiSettingsServiceMock.createClient(); + const getUiSettingsClient = jest.fn(() => uiSettingsClient); + uiSettingsClient.getUserProvided.mockResolvedValue(mockUserSettings); + uiSettingsClient.isSensitive.mockImplementation(mockIsSensitive); + const fetchFunction = createCollectorFetch(getUiSettingsClient); + const result = await fetchFunction(); + expect(typeof result).toBe('object'); + expect(Object.keys(result!)).toEqual(Object.keys(mockUserSettings)); + }); + + it('returns the actual values of non-sensitive settings', async () => { + const uiSettingsClient = uiSettingsServiceMock.createClient(); + const getUiSettingsClient = jest.fn(() => uiSettingsClient); + uiSettingsClient.getUserProvided.mockResolvedValue(mockUserSettings); + uiSettingsClient.isSensitive.mockImplementation(mockIsSensitive); + const fetchFunction = createCollectorFetch(getUiSettingsClient); + const result = await fetchFunction(); + expect(typeof result).toBe('object'); + expect(result!).toMatchObject({ + item1: 'test', + item2: 123, + }); + }); + + it('returns [REDACTED] as a value for sensitive settings', async () => { + const uiSettingsClient = uiSettingsServiceMock.createClient(); + const getUiSettingsClient = jest.fn(() => uiSettingsClient); + uiSettingsClient.getUserProvided.mockResolvedValue(mockUserSettings); + uiSettingsClient.isSensitive.mockImplementation(mockIsSensitive); + const fetchFunction = createCollectorFetch(getUiSettingsClient); + const result = await fetchFunction(); + expect(typeof result).toBe('object'); + expect(result!).toMatchObject({ + item3: '[REDACTED]', + }); + }); + }); +}); diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.ts b/src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.ts index c45f3d6139d95..651fbbd5a897a 100644 --- a/src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.ts +++ b/src/plugins/kibana_usage_collection/server/collectors/management/telemetry_management_collector.ts @@ -9,12 +9,8 @@ import { IUiSettingsClient } from 'kibana/server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { stackManagementSchema } from './schema'; - -export interface UsageStats extends Record { - // We don't support `type` yet. Only interfaces. So I added at least 1 known key to the generic - // Record extension to avoid eslint reverting it back to a `type` - 'visualize:enableLabs': boolean; -} +import { UsageStats } from './types'; +import { REDACTED_KEYWORD } from '../../../common/constants'; export function createCollectorFetch(getUiSettingsClient: () => IUiSettingsClient | undefined) { return async function fetchUsageStats(): Promise { @@ -23,11 +19,12 @@ export function createCollectorFetch(getUiSettingsClient: () => IUiSettingsClien return; } - const user = await uiSettingsClient.getUserProvided(); - const modifiedEntries = Object.keys(user) - .filter((key: string) => key !== 'buildNum') - .reduce((obj: any, key: string) => { - obj[key] = user[key].userValue; + const userProvided = await uiSettingsClient.getUserProvided(); + const modifiedEntries = Object.entries(userProvided) + .filter(([key]) => key !== 'buildNum') + .reduce((obj: any, [key, { userValue }]) => { + const sensitive = uiSettingsClient.isSensitive(key); + obj[key] = sensitive ? REDACTED_KEYWORD : userValue; return obj; }, {}); diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts new file mode 100644 index 0000000000000..417841ee89569 --- /dev/null +++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts @@ -0,0 +1,117 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +export interface UsageStats { + /** + * sensitive settings + */ + 'timelion:quandl.key': string; + 'securitySolution:defaultIndex': string; + 'securitySolution:newsFeedUrl': string; + 'xpackReporting:customPdfLogo': string; + 'notifications:banner': string; + 'timelion:graphite.url': string; + 'xpackDashboardMode:roles': string; + 'securitySolution:ipReputationLinks': string; + /** + * non-sensitive settings + */ + 'autocomplete:useTimeRange': boolean; + 'search:timeout': number; + 'visualization:visualize:legacyChartsLibrary': boolean; + 'doc_table:legacy': boolean; + 'discover:modifyColumnsOnSwitch': boolean; + 'discover:searchFieldsFromSource': boolean; + 'securitySolution:rulesTableRefresh': string; + 'apm:enableSignificantTerms': boolean; + 'apm:enableServiceOverview': boolean; + 'apm:enableCorrelations': boolean; + 'visualize:enableLabs': boolean; + 'visualization:heatmap:maxBuckets': number; + 'visualization:colorMapping': string; + 'visualization:regionmap:showWarnings': boolean; + 'visualization:dimmingOpacity': number; + 'visualization:tileMap:maxPrecision': number; + 'csv:separator': string; + 'visualization:tileMap:WMSdefaults': string; + 'timelion:target_buckets': number; + 'timelion:max_buckets': number; + 'timelion:es.timefield': string; + 'timelion:min_interval': string; + 'timelion:default_rows': number; + 'timelion:default_columns': number; + 'timelion:es.default_index': string; + 'timelion:showTutorial': boolean; + 'securitySolution:timeDefaults': string; + 'securitySolution:defaultAnomalyScore': number; + 'securitySolution:refreshIntervalDefaults': string; + 'securitySolution:enableNewsFeed': boolean; + 'search:includeFrozen': boolean; + 'courier:maxConcurrentShardRequests': number; + 'courier:batchSearches': boolean; + 'courier:setRequestPreference': string; + 'courier:customRequestPreference': string; + 'courier:ignoreFilterIfFieldNotInIndex': boolean; + 'rollups:enableIndexPatterns': boolean; + 'notifications:lifetime:warning': number; + 'notifications:lifetime:banner': number; + 'notifications:lifetime:info': number; + 'notifications:lifetime:error': number; + 'doc_table:highlight': boolean; + 'discover:searchOnPageLoad': boolean; + // eslint-disable-next-line @typescript-eslint/naming-convention + 'doc_table:hideTimeColumn': boolean; + 'discover:sampleSize': number; + defaultColumns: string[]; + 'context:defaultSize': number; + 'discover:aggs:terms:size': number; + 'context:tieBreakerFields': string[]; + 'discover:sort:defaultOrder': string; + 'context:step': number; + 'accessibility:disableAnimations': boolean; + 'ml:fileDataVisualizerMaxFileSize': string; + 'ml:anomalyDetection:results:enableTimeDefaults': boolean; + 'ml:anomalyDetection:results:timeDefaults': string; + 'truncate:maxHeight': number; + 'timepicker:timeDefaults': string; + 'timepicker:refreshIntervalDefaults': string; + 'timepicker:quickRanges': string; + 'theme:version': string; + 'theme:darkMode': boolean; + 'state:storeInSessionStorage': boolean; + 'savedObjects:perPage': number; + 'search:queryLanguage': string; + 'shortDots:enable': boolean; + 'sort:options': string; + 'savedObjects:listingLimit': number; + 'query:queryString:options': string; + 'metrics:max_buckets': number; + 'query:allowLeadingWildcards': boolean; + metaFields: string[]; + 'indexPattern:placeholder': string; + 'histogram:barTarget': number; + 'histogram:maxBars': number; + 'format:number:defaultLocale': string; + 'format:percent:defaultPattern': string; + 'format:number:defaultPattern': string; + 'history:limit': number; + 'format:defaultTypeMap': string; + 'format:currency:defaultPattern': string; + defaultIndex: string; + 'format:bytes:defaultPattern': string; + 'filters:pinnedByDefault': boolean; + 'filterEditor:suggestValues': boolean; + 'fields:popularLimit': number; + dateNanosFormat: string; + defaultRoute: string; + 'dateFormat:tz': string; + 'dateFormat:scaled': string; + 'csv:quoteValues': boolean; + 'dateFormat:dow': string; + dateFormat: string; +} diff --git a/src/plugins/kibana_utils/docs/state_sync/storages/kbn_url_storage.md b/src/plugins/kibana_utils/docs/state_sync/storages/kbn_url_storage.md index ec27895eed666..36c7d7119ffe5 100644 --- a/src/plugins/kibana_utils/docs/state_sync/storages/kbn_url_storage.md +++ b/src/plugins/kibana_utils/docs/state_sync/storages/kbn_url_storage.md @@ -96,11 +96,11 @@ setTimeout(() => { }, 0); ``` -For cases, where granular control over URL updates is needed, `kbnUrlStateStorage` provides these advanced apis: +For cases, where granular control over URL updates is needed, `kbnUrlStateStorage` exposes `kbnUrlStateStorage.kbnUrlControls` that exposes these advanced apis: -- `kbnUrlStateStorage.flush({replace: boolean})` - allows to synchronously apply any pending updates. - `replace` option allows to use `history.replace()` instead of `history.push()`. Returned boolean indicates if any update happened -- `kbnUrlStateStorage.cancel()` - cancels any pending updates +- `kbnUrlStateStorage.kbnUrlControls.flush({replace: boolean})` - allows to synchronously apply any pending updates. + `replace` option allows using `history.replace()` instead of `history.push()`. +- `kbnUrlStateStorage.kbnUrlControls.cancel()` - cancels any pending updates. ### Sharing one `kbnUrlStateStorage` instance diff --git a/src/plugins/kibana_utils/public/history/history_observable.test.ts b/src/plugins/kibana_utils/public/history/history_observable.test.ts new file mode 100644 index 0000000000000..818c0d7739283 --- /dev/null +++ b/src/plugins/kibana_utils/public/history/history_observable.test.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { + createHistoryObservable, + createQueryParamObservable, + createQueryParamsObservable, +} from './history_observable'; +import { createMemoryHistory, History } from 'history'; +import { ParsedQuery } from 'query-string'; + +let history: History; + +beforeEach(() => { + history = createMemoryHistory(); +}); + +test('createHistoryObservable', () => { + const obs$ = createHistoryObservable(history); + const emits: string[] = []; + obs$.subscribe(({ location }) => { + emits.push(location.pathname + location.search); + }); + + history.push('/test'); + history.push('/'); + + expect(emits.length).toEqual(2); + expect(emits).toMatchInlineSnapshot(` + Array [ + "/test", + "/", + ] + `); +}); + +test('createQueryParamsObservable', () => { + const obs$ = createQueryParamsObservable(history); + const emits: ParsedQuery[] = []; + obs$.subscribe((params) => { + emits.push(params); + }); + + history.push('/test'); + history.push('/test?foo=bar'); + history.push('/?foo=bar'); + history.push('/test?foo=bar&foo1=bar1'); + + expect(emits.length).toEqual(2); + expect(emits).toMatchInlineSnapshot(` + Array [ + Object { + "foo": "bar", + }, + Object { + "foo": "bar", + "foo1": "bar1", + }, + ] + `); +}); + +test('createQueryParamObservable', () => { + const obs$ = createQueryParamObservable(history, 'foo'); + const emits: unknown[] = []; + obs$.subscribe((param) => { + emits.push(param); + }); + + history.push('/test'); + history.push('/test?foo=bar'); + history.push('/?foo=bar'); + history.push('/test?foo=baaaar&foo1=bar1'); + history.push('/test?foo1=bar1'); + + expect(emits.length).toEqual(3); + expect(emits).toMatchInlineSnapshot(` + Array [ + "bar", + "baaaar", + null, + ] + `); +}); diff --git a/src/plugins/kibana_utils/public/history/history_observable.ts b/src/plugins/kibana_utils/public/history/history_observable.ts new file mode 100644 index 0000000000000..f02a5e340b1a0 --- /dev/null +++ b/src/plugins/kibana_utils/public/history/history_observable.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { Action, History, Location } from 'history'; +import { Observable } from 'rxjs'; +import { ParsedQuery } from 'query-string'; +import deepEqual from 'fast-deep-equal'; +import { map } from 'rxjs/operators'; +import { getQueryParams } from './get_query_params'; +import { distinctUntilChangedWithInitialValue } from '../../common'; + +/** + * Convert history.listen into an observable + * @param history - {@link History} instance + */ +export function createHistoryObservable( + history: History +): Observable<{ location: Location; action: Action }> { + return new Observable((observer) => { + const unlisten = history.listen((location, action) => observer.next({ location, action })); + return () => { + unlisten(); + }; + }); +} + +/** + * Create an observable that emits every time any of query params change. + * Uses deepEqual check. + * @param history - {@link History} instance + */ +export function createQueryParamsObservable(history: History): Observable { + return createHistoryObservable(history).pipe( + map(({ location }) => ({ ...getQueryParams(location) })), + distinctUntilChangedWithInitialValue({ ...getQueryParams(history.location) }, deepEqual) + ); +} + +/** + * Create an observable that emits every time _paramKey_ changes + * @param history - {@link History} instance + * @param paramKey - query param key to observe + */ +export function createQueryParamObservable( + history: History, + paramKey: string +): Observable { + return createQueryParamsObservable(history).pipe( + map((params) => (params[paramKey] ?? null) as Param | null), + distinctUntilChangedWithInitialValue( + (getQueryParams(history.location)[paramKey] ?? null) as Param | null, + deepEqual + ) + ); +} diff --git a/src/plugins/kibana_utils/public/history/index.ts b/src/plugins/kibana_utils/public/history/index.ts index 4b1b610d560e2..b2ac9ed6c739e 100644 --- a/src/plugins/kibana_utils/public/history/index.ts +++ b/src/plugins/kibana_utils/public/history/index.ts @@ -9,3 +9,4 @@ export { removeQueryParam } from './remove_query_param'; export { redirectWhenMissing } from './redirect_when_missing'; export { getQueryParams } from './get_query_params'; +export * from './history_observable'; diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts index fa9cf5a52371d..29936da0117c1 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -68,7 +68,14 @@ export { StopSyncStateFnType, } from './state_sync'; export { Configurable, CollectConfigProps } from './ui'; -export { removeQueryParam, redirectWhenMissing, getQueryParams } from './history'; +export { + removeQueryParam, + redirectWhenMissing, + getQueryParams, + createQueryParamsObservable, + createHistoryObservable, + createQueryParamObservable, +} from './history'; export { applyDiff } from './state_management/utils/diff_object'; export { createStartServicesGetter, StartServicesGetter } from './core/create_start_service_getter'; diff --git a/src/plugins/kibana_utils/public/state_sync/public.api.md b/src/plugins/kibana_utils/public/state_sync/public.api.md index a4dfea82cdb59..5524563c034a8 100644 --- a/src/plugins/kibana_utils/public/state_sync/public.api.md +++ b/src/plugins/kibana_utils/public/state_sync/public.api.md @@ -22,14 +22,12 @@ export const createSessionStorageStateStorage: (storage?: Storage) => ISessionSt // @public export interface IKbnUrlStateStorage extends IStateStorage { - cancel: () => void; // (undocumented) change$: (key: string) => Observable; - flush: (opts?: { - replace?: boolean; - }) => boolean; // (undocumented) get: (key: string) => State | null; + // Warning: (ae-forgotten-export) The symbol "IKbnUrlControls" needs to be exported by the entry point index.d.ts + kbnUrlControls: IKbnUrlControls; // (undocumented) set: (key: string, state: State, opts?: { replace: boolean; diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts b/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts index c7f04bc9cdbe3..890de8f6ed6a1 100644 --- a/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts +++ b/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts @@ -255,7 +255,7 @@ describe('state_sync', () => { expect(history.length).toBe(startHistoryLength); expect(getCurrentUrl()).toMatchInlineSnapshot(`"/"`); - urlSyncStrategy.flush(); + urlSyncStrategy.kbnUrlControls.flush(); expect(history.length).toBe(startHistoryLength + 1); expect(getCurrentUrl()).toMatchInlineSnapshot( @@ -290,7 +290,7 @@ describe('state_sync', () => { expect(history.length).toBe(startHistoryLength); expect(getCurrentUrl()).toMatchInlineSnapshot(`"/"`); - urlSyncStrategy.cancel(); + urlSyncStrategy.kbnUrlControls.cancel(); expect(history.length).toBe(startHistoryLength); expect(getCurrentUrl()).toMatchInlineSnapshot(`"/"`); diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.test.ts b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.test.ts index fbd3c3f933791..037c6f9fc666d 100644 --- a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.test.ts +++ b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.test.ts @@ -39,11 +39,11 @@ describe('KbnUrlStateStorage', () => { const key = '_s'; urlStateStorage.set(key, state); expect(getCurrentUrl()).toMatchInlineSnapshot(`"/"`); - expect(urlStateStorage.flush()).toBe(true); + expect(!!urlStateStorage.kbnUrlControls.flush()).toBe(true); expect(getCurrentUrl()).toMatchInlineSnapshot(`"/#?_s=(ok:1,test:test)"`); expect(urlStateStorage.get(key)).toEqual(state); - expect(urlStateStorage.flush()).toBe(false); // nothing to flush, not update + expect(!!urlStateStorage.kbnUrlControls.flush()).toBe(false); // nothing to flush, not update }); it('should cancel url updates', async () => { @@ -51,7 +51,7 @@ describe('KbnUrlStateStorage', () => { const key = '_s'; const pr = urlStateStorage.set(key, state); expect(getCurrentUrl()).toMatchInlineSnapshot(`"/"`); - urlStateStorage.cancel(); + urlStateStorage.kbnUrlControls.cancel(); await pr; expect(getCurrentUrl()).toMatchInlineSnapshot(`"/"`); expect(urlStateStorage.get(key)).toEqual(null); @@ -215,11 +215,11 @@ describe('KbnUrlStateStorage', () => { const key = '_s'; urlStateStorage.set(key, state); expect(getCurrentUrl()).toMatchInlineSnapshot(`"/kibana/app/"`); - expect(urlStateStorage.flush()).toBe(true); + expect(!!urlStateStorage.kbnUrlControls.flush()).toBe(true); expect(getCurrentUrl()).toMatchInlineSnapshot(`"/kibana/app/#?_s=(ok:1,test:test)"`); expect(urlStateStorage.get(key)).toEqual(state); - expect(urlStateStorage.flush()).toBe(false); // nothing to flush, not update + expect(!!urlStateStorage.kbnUrlControls.flush()).toBe(false); // nothing to flush, not update }); it('should cancel url updates', async () => { @@ -227,7 +227,7 @@ describe('KbnUrlStateStorage', () => { const key = '_s'; const pr = urlStateStorage.set(key, state); expect(getCurrentUrl()).toMatchInlineSnapshot(`"/kibana/app/"`); - urlStateStorage.cancel(); + urlStateStorage.kbnUrlControls.cancel(); await pr; expect(getCurrentUrl()).toMatchInlineSnapshot(`"/kibana/app/"`); expect(urlStateStorage.get(key)).toEqual(null); diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.ts b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.ts index 700420447bf4f..0935ecd20111f 100644 --- a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.ts +++ b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.ts @@ -13,6 +13,7 @@ import { IStateStorage } from './types'; import { createKbnUrlControls, getStateFromKbnUrl, + IKbnUrlControls, setStateToKbnUrl, } from '../../state_management/url'; @@ -39,16 +40,9 @@ export interface IKbnUrlStateStorage extends IStateStorage { change$: (key: string) => Observable; /** - * cancels any pending url updates + * Lower level wrapper around history library that handles batching multiple URL updates into one history change */ - cancel: () => void; - - /** - * Synchronously runs any pending url updates, returned boolean indicates if change occurred. - * @param opts: {replace? boolean} - allows to specify if push or replace should be used for flushing update - * @returns boolean - indicates if there was an update to flush - */ - flush: (opts?: { replace?: boolean }) => boolean; + kbnUrlControls: IKbnUrlControls; } /** @@ -114,11 +108,6 @@ export const createKbnUrlStateStorage = ( }), share() ), - flush: ({ replace = false }: { replace?: boolean } = {}) => { - return !!url.flush(replace); - }, - cancel() { - url.cancel(); - }, + kbnUrlControls: url, }; }; diff --git a/src/plugins/lens_oss/README.md b/src/plugins/lens_oss/README.md deleted file mode 100644 index 187da2497026e..0000000000000 --- a/src/plugins/lens_oss/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# lens_oss - -The lens_oss plugin registers the lens visualization on OSS. -It is registered as disabled. The x-pack plugin should unregister this. - -`visualizations.unregisterAlias('lensOss')` \ No newline at end of file diff --git a/src/plugins/lens_oss/kibana.json b/src/plugins/lens_oss/kibana.json deleted file mode 100644 index 3e3d3585f37fb..0000000000000 --- a/src/plugins/lens_oss/kibana.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "id": "lensOss", - "version": "kibana", - "ui": true, - "server": true, - "requiredPlugins": [ - "visualizations" - ], - "extraPublicDirs": ["common/constants"] -} diff --git a/src/plugins/lens_oss/public/plugin.ts b/src/plugins/lens_oss/public/plugin.ts deleted file mode 100644 index 5a441614b7e24..0000000000000 --- a/src/plugins/lens_oss/public/plugin.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { DocLinksStart, CoreSetup } from 'src/core/public'; -import { VisualizationsSetup } from '../../visualizations/public'; -import { getLensAliasConfig } from './vis_type_alias'; - -export interface LensPluginSetupDependencies { - visualizations: VisualizationsSetup; -} - -export interface LensPluginStartDependencies { - docLinks: DocLinksStart; -} - -export class LensOSSPlugin { - setup( - core: CoreSetup, - { visualizations }: LensPluginSetupDependencies - ) { - core.getStartServices().then(([coreStart]) => { - visualizations.registerAlias(getLensAliasConfig(coreStart.docLinks)); - }); - } - - start() {} -} diff --git a/src/plugins/lens_oss/public/vis_type_alias.ts b/src/plugins/lens_oss/public/vis_type_alias.ts deleted file mode 100644 index b9806bbf3b4e5..0000000000000 --- a/src/plugins/lens_oss/public/vis_type_alias.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; -import { VisTypeAlias } from 'src/plugins/visualizations/public'; -import { DocLinksStart } from 'src/core/public'; -import { APP_NAME, PLUGIN_ID_OSS, APP_PATH, APP_ICON } from '../common'; - -export const getLensAliasConfig = ({ links }: DocLinksStart): VisTypeAlias => ({ - aliasPath: APP_PATH, - aliasApp: APP_NAME, - name: PLUGIN_ID_OSS, - title: i18n.translate('lensOss.visTypeAlias.title', { - defaultMessage: 'Lens', - }), - description: i18n.translate('lensOss.visTypeAlias.description', { - defaultMessage: - 'Create visualizations with our drag-and-drop editor. Switch between visualization types at any time. Best for most visualizations.', - }), - icon: APP_ICON, - stage: 'production', - disabled: true, - note: i18n.translate('lensOss.visTypeAlias.note', { - defaultMessage: 'Recommended for most users.', - }), - promoTooltip: { - description: i18n.translate('lensOss.visTypeAlias.promoTooltip.description', { - defaultMessage: 'Try Lens for free with Elastic. Learn more.', - }), - link: `${links.visualize.lens}?blade=kibanaossvizwizard`, - }, -}); diff --git a/src/plugins/lens_oss/server/index.ts b/src/plugins/lens_oss/server/index.ts deleted file mode 100644 index d13a9b2caaeb2..0000000000000 --- a/src/plugins/lens_oss/server/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { PluginConfigDescriptor } from 'kibana/server'; -import { copyFromRoot } from '@kbn/config'; -import { configSchema, ConfigSchema } from '../config'; - -export const config: PluginConfigDescriptor = { - schema: configSchema, - deprecations: () => [copyFromRoot('xpack.lens.enabled', 'lens_oss.enabled')], -}; - -export const plugin = () => ({ - setup() {}, - start() {}, -}); diff --git a/src/plugins/maps_legacy/public/index.ts b/src/plugins/maps_legacy/public/index.ts index 95550fab1ba17..9268f14995f44 100644 --- a/src/plugins/maps_legacy/public/index.ts +++ b/src/plugins/maps_legacy/public/index.ts @@ -8,9 +8,7 @@ import { PluginInitializerContext } from 'kibana/public'; import { MapsLegacyPlugin } from './plugin'; -// @ts-ignore import * as colorUtil from './map/color_util'; -// @ts-ignore import { KibanaMapLayer } from './map/kibana_map_layer'; import { VectorLayer, @@ -19,7 +17,6 @@ import { TmsLayer, IServiceSettings, } from './map/service_settings_types'; -// @ts-ignore import { mapTooltipProvider } from './tooltip_provider'; import './map/index.scss'; diff --git a/src/plugins/lens_oss/common/constants.ts b/src/plugins/maps_legacy/public/map/color_util.d.ts similarity index 69% rename from src/plugins/lens_oss/common/constants.ts rename to src/plugins/maps_legacy/public/map/color_util.d.ts index 0ff5cdd78bb1b..9ec6b3c1fb007 100644 --- a/src/plugins/lens_oss/common/constants.ts +++ b/src/plugins/maps_legacy/public/map/color_util.d.ts @@ -6,7 +6,6 @@ * Public License, v 1. */ -export const APP_NAME = 'lens'; -export const PLUGIN_ID_OSS = 'lensOss'; -export const APP_PATH = '#/'; -export const APP_ICON = 'lensApp'; +export function getLegendColors(colorRamp: unknown, numLegendColors?: number): string[]; + +export function getColor(colorRamp: unknown, i: number): string; diff --git a/src/plugins/maps_legacy/public/map/kibana_map_layer.d.ts b/src/plugins/maps_legacy/public/map/kibana_map_layer.d.ts new file mode 100644 index 0000000000000..222cb6b215f9a --- /dev/null +++ b/src/plugins/maps_legacy/public/map/kibana_map_layer.d.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +export class KibanaMapLayer { + constructor(); + + getBounds(): Promise; + + addToLeafletMap(leafletMap: unknown): void; + + removeFromLeafletMap(leafletMap: unknown): void; + + appendLegendContents(): void; + + updateExtent(): void; + + movePointer(): void; + + getAttributions(): unknown; +} diff --git a/typings/@elastic/eui/lib/format.d.ts b/src/plugins/maps_legacy/public/tooltip_provider.d.ts similarity index 79% rename from typings/@elastic/eui/lib/format.d.ts rename to src/plugins/maps_legacy/public/tooltip_provider.d.ts index 4be830ec2d98c..4082a6ef83c4d 100644 --- a/typings/@elastic/eui/lib/format.d.ts +++ b/src/plugins/maps_legacy/public/tooltip_provider.d.ts @@ -6,4 +6,4 @@ * Public License, v 1. */ -export const dateFormatAliases: any; +export function mapTooltipProvider(element: unknown, formatter: unknown): () => unknown; diff --git a/x-pack/plugins/vis_type_timeseries_enhanced/tsconfig.json b/src/plugins/maps_legacy/tsconfig.json similarity index 59% rename from x-pack/plugins/vis_type_timeseries_enhanced/tsconfig.json rename to src/plugins/maps_legacy/tsconfig.json index c5ec5571917bd..e7ea06706b64f 100644 --- a/x-pack/plugins/vis_type_timeseries_enhanced/tsconfig.json +++ b/src/plugins/maps_legacy/tsconfig.json @@ -7,9 +7,8 @@ "declaration": true, "declarationMap": true }, - "include": ["*.ts", "server/**/*"], + "include": ["common/**/*", "public/**/*", "server/**/*", "config.ts"], "references": [ - { "path": "../../../src/core/tsconfig.json" }, - { "path": "../../../src/plugins/vis_type_timeseries/tsconfig.json" } + { "path": "../vis_default_editor/tsconfig.json" }, ] } diff --git a/src/plugins/maps_oss/README.md b/src/plugins/maps_oss/README.md deleted file mode 100644 index ed91de500fbfb..0000000000000 --- a/src/plugins/maps_oss/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# maps_oss - -The maps_oss plugin registers the maps visualization on OSS. -It is registered as disabled. The x-pack plugin should unregister this. - -`visualizations.unregisterAlias('mapsOss')` \ No newline at end of file diff --git a/src/plugins/maps_oss/common/constants.ts b/src/plugins/maps_oss/common/constants.ts deleted file mode 100644 index db29f541a03df..0000000000000 --- a/src/plugins/maps_oss/common/constants.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -export const APP_NAME = 'maps'; -export const PLUGIN_ID_OSS = 'mapsOss'; -export const APP_PATH = '/map'; -export const APP_ICON = 'gisApp'; diff --git a/src/plugins/maps_oss/kibana.json b/src/plugins/maps_oss/kibana.json deleted file mode 100644 index 19770dcffaadd..0000000000000 --- a/src/plugins/maps_oss/kibana.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "id": "mapsOss", - "version": "kibana", - "ui": true, - "server": true, - "requiredPlugins": [ - "visualizations" - ], - "extraPublicDirs": ["common/constants"] -} diff --git a/src/plugins/maps_oss/public/plugin.ts b/src/plugins/maps_oss/public/plugin.ts deleted file mode 100644 index 5e27ae34257bf..0000000000000 --- a/src/plugins/maps_oss/public/plugin.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { DocLinksStart, CoreSetup } from 'src/core/public'; -import { VisualizationsSetup } from '../../visualizations/public'; -import { getMapsAliasConfig } from './vis_type_alias'; - -export interface MapsPluginSetupDependencies { - visualizations: VisualizationsSetup; -} - -export interface MapsPluginStartDependencies { - docLinks: DocLinksStart; -} - -export class MapsOSSPlugin { - setup( - core: CoreSetup, - { visualizations }: MapsPluginSetupDependencies - ) { - core.getStartServices().then(([coreStart]) => { - visualizations.registerAlias(getMapsAliasConfig(coreStart.docLinks)); - }); - } - - start() {} -} diff --git a/src/plugins/maps_oss/public/vis_type_alias.ts b/src/plugins/maps_oss/public/vis_type_alias.ts deleted file mode 100644 index a27c628755cf6..0000000000000 --- a/src/plugins/maps_oss/public/vis_type_alias.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { i18n } from '@kbn/i18n'; -import { VisTypeAlias } from 'src/plugins/visualizations/public'; -import { DocLinksStart } from 'src/core/public'; -import { APP_NAME, PLUGIN_ID_OSS, APP_PATH, APP_ICON } from '../common'; - -export const getMapsAliasConfig = ({ links }: DocLinksStart): VisTypeAlias => ({ - aliasPath: APP_PATH, - aliasApp: APP_NAME, - name: PLUGIN_ID_OSS, - title: i18n.translate('mapsOss.visTypeAlias.title', { - defaultMessage: 'Maps', - }), - description: i18n.translate('mapsOss.visTypeAlias.description', { - defaultMessage: 'Plot and style your geo data in a multi layer map.', - }), - icon: APP_ICON, - stage: 'production', - disabled: true, - promoTooltip: { - description: i18n.translate('mapsOss.visTypeAlias.promoTooltip.description', { - defaultMessage: 'Try maps for free with Elastic. Learn more.', - }), - link: `${links.visualize.maps}?blade=kibanaossvizwizard`, - }, -}); diff --git a/src/plugins/maps_oss/server/index.ts b/src/plugins/maps_oss/server/index.ts deleted file mode 100644 index 8f07beee705a6..0000000000000 --- a/src/plugins/maps_oss/server/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -import { PluginConfigDescriptor } from 'kibana/server'; -import { copyFromRoot } from '@kbn/config'; -import { configSchema, ConfigSchema } from '../config'; - -export const config: PluginConfigDescriptor = { - schema: configSchema, - deprecations: () => [copyFromRoot('xpack.maps.enabled', 'maps_oss.enabled')], -}; - -export const plugin = () => ({ - setup() {}, - start() {}, -}); diff --git a/src/plugins/lens_oss/tsconfig.json b/src/plugins/region_map/tsconfig.json similarity index 56% rename from src/plugins/lens_oss/tsconfig.json rename to src/plugins/region_map/tsconfig.json index d7bbc593fa87b..40f76ece2a6ff 100644 --- a/src/plugins/lens_oss/tsconfig.json +++ b/src/plugins/region_map/tsconfig.json @@ -7,14 +7,9 @@ "declaration": true, "declarationMap": true }, - "include": [ - "common/**/*", - "public/**/*", - "server/**/*", - "*.ts" - ], + "include": ["public/**/*", "server/**/*"], "references": [ - { "path": "../../core/tsconfig.json" }, - { "path": "../visualizations/tsconfig.json" } + { "path": "../maps_legacy/tsconfig.json" }, + { "path": "../vis_default_editor/tsconfig.json" }, ] } diff --git a/src/plugins/security_oss/public/plugin.mock.ts b/src/plugins/security_oss/public/plugin.mock.ts index 53d96b9c7a303..8fe8d56ea6576 100644 --- a/src/plugins/security_oss/public/plugin.mock.ts +++ b/src/plugins/security_oss/public/plugin.mock.ts @@ -7,6 +7,7 @@ */ import type { DeeplyMockedKeys } from '@kbn/utility-types/jest'; +import { InsecureClusterServiceStart } from './insecure_cluster_service'; import { mockInsecureClusterService } from './insecure_cluster_service/insecure_cluster_service.mock'; import { SecurityOssPluginSetup, SecurityOssPluginStart } from './plugin'; @@ -18,7 +19,11 @@ export const mockSecurityOssPlugin = { }, createStart: () => { return { - insecureCluster: mockInsecureClusterService.createStart(), + insecureCluster: mockInsecureClusterService.createStart() as jest.Mocked, + anonymousAccess: { + getAccessURLParameters: jest.fn().mockResolvedValue(null), + getCapabilities: jest.fn().mockResolvedValue({}), + }, } as DeeplyMockedKeys; }, }; diff --git a/src/plugins/share/kibana.json b/src/plugins/share/kibana.json index 7760ea321992d..8b1d28b1606d4 100644 --- a/src/plugins/share/kibana.json +++ b/src/plugins/share/kibana.json @@ -3,5 +3,6 @@ "version": "kibana", "server": true, "ui": true, - "requiredBundles": ["kibanaUtils"] + "requiredBundles": ["kibanaUtils"], + "optionalPlugins": ["securityOss"] } diff --git a/src/plugins/share/public/components/__snapshots__/url_panel_content.test.tsx.snap b/src/plugins/share/public/components/__snapshots__/url_panel_content.test.tsx.snap index 9a7191519131c..e883b550fde04 100644 --- a/src/plugins/share/public/components/__snapshots__/url_panel_content.test.tsx.snap +++ b/src/plugins/share/public/components/__snapshots__/url_panel_content.test.tsx.snap @@ -115,49 +115,68 @@ exports[`share url panel content render 1`] = ` /> + } labelType="label" > - + - - - } - onChange={[Function]} - /> - - - - } - position="bottom" - /> - - + + + } + onChange={[Function]} + /> + + + + } + position="bottom" + /> + + + + } labelType="label" > - + - - - } - onChange={[Function]} - /> - - - - } - position="bottom" - /> - - + + + } + onChange={[Function]} + /> + + + + } + position="bottom" + /> + + + + + } + labelType="label" + > + + @@ -569,49 +626,68 @@ exports[`should show url param extensions 1`] = ` /> + } labelType="label" > - + - - - } - onChange={[Function]} - /> - - - - } - position="bottom" - /> - - + + + } + onChange={[Function]} + /> + + + + } + position="bottom" + /> + + + boolean; } export class ShareContextMenu extends Component { @@ -62,6 +66,8 @@ export class ShareContextMenu extends Component { basePath={this.props.basePath} post={this.props.post} shareableUrl={this.props.shareableUrl} + anonymousAccess={this.props.anonymousAccess} + showPublicUrlSwitch={this.props.showPublicUrlSwitch} /> ), }; @@ -91,6 +97,8 @@ export class ShareContextMenu extends Component { post={this.props.post} shareableUrl={this.props.shareableUrl} urlParamExtensions={this.props.embedUrlParamExtensions} + anonymousAccess={this.props.anonymousAccess} + showPublicUrlSwitch={this.props.showPublicUrlSwitch} /> ), }; diff --git a/src/plugins/share/public/components/url_panel_content.tsx b/src/plugins/share/public/components/url_panel_content.tsx index 5901d2452e9aa..ca9025f242b78 100644 --- a/src/plugins/share/public/components/url_panel_content.tsx +++ b/src/plugins/share/public/components/url_panel_content.tsx @@ -28,9 +28,11 @@ import { format as formatUrl, parse as parseUrl } from 'url'; import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; import { HttpStart } from 'kibana/public'; import { i18n } from '@kbn/i18n'; +import type { Capabilities } from 'src/core/public'; import { shortenUrl } from '../lib/url_shortener'; import { UrlParamExtension } from '../types'; +import type { SecurityOssPluginStart } from '../../../security_oss/public'; interface Props { allowShortUrl: boolean; @@ -41,6 +43,8 @@ interface Props { basePath: string; post: HttpStart['post']; urlParamExtensions?: UrlParamExtension[]; + anonymousAccess?: SecurityOssPluginStart['anonymousAccess']; + showPublicUrlSwitch?: (anonymousUserCapabilities: Capabilities) => boolean; } export enum ExportUrlAsType { @@ -57,10 +61,13 @@ interface UrlParams { interface State { exportUrlAs: ExportUrlAsType; useShortUrl: boolean; + usePublicUrl: boolean; isCreatingShortUrl: boolean; url?: string; shortUrlErrorMsg?: string; urlParams?: UrlParams; + anonymousAccessParameters: Record | null; + showPublicUrlSwitch: boolean; } export class UrlPanelContent extends Component { @@ -75,8 +82,11 @@ export class UrlPanelContent extends Component { this.state = { exportUrlAs: ExportUrlAsType.EXPORT_URL_AS_SNAPSHOT, useShortUrl: false, + usePublicUrl: false, isCreatingShortUrl: false, url: '', + anonymousAccessParameters: null, + showPublicUrlSwitch: false, }; } @@ -91,6 +101,41 @@ export class UrlPanelContent extends Component { this.setUrl(); window.addEventListener('hashchange', this.resetUrl, false); + + if (this.props.anonymousAccess) { + (async () => { + const anonymousAccessParameters = await this.props.anonymousAccess!.getAccessURLParameters(); + + if (!this.mounted) { + return; + } + + if (!anonymousAccessParameters) { + return; + } + + let showPublicUrlSwitch: boolean = false; + + if (this.props.showPublicUrlSwitch) { + const anonymousUserCapabilities = await this.props.anonymousAccess!.getCapabilities(); + + if (!this.mounted) { + return; + } + + try { + showPublicUrlSwitch = this.props.showPublicUrlSwitch!(anonymousUserCapabilities); + } catch { + showPublicUrlSwitch = false; + } + } + + this.setState({ + anonymousAccessParameters, + showPublicUrlSwitch, + }); + })(); + } } public render() { @@ -99,7 +144,16 @@ export class UrlPanelContent extends Component { {this.renderExportAsRadioGroup()} {this.renderUrlParamExtensions()} - {this.renderShortUrlSwitch()} + + } + > + <> + + {this.renderShortUrlSwitch()} + {this.renderPublicUrlSwitch()} + + @@ -150,10 +204,10 @@ export class UrlPanelContent extends Component { }; private updateUrlParams = (url: string) => { - const embedUrl = this.props.isEmbedded ? this.makeUrlEmbeddable(url) : url; - const extendUrl = this.state.urlParams ? this.getUrlParamExtensions(embedUrl) : embedUrl; + url = this.props.isEmbedded ? this.makeUrlEmbeddable(url) : url; + url = this.state.urlParams ? this.getUrlParamExtensions(url) : url; - return extendUrl; + return url; }; private getSavedObjectUrl = () => { @@ -206,6 +260,20 @@ export class UrlPanelContent extends Component { return `${url}${embedParam}`; }; + private addUrlAnonymousAccessParameters = (url: string): string => { + if (!this.state.anonymousAccessParameters || !this.state.usePublicUrl) { + return url; + } + + const parsedUrl = new URL(url); + + for (const [name, value] of Object.entries(this.state.anonymousAccessParameters)) { + parsedUrl.searchParams.set(name, value); + } + + return parsedUrl.toString(); + }; + private getUrlParamExtensions = (url: string): string => { const { urlParams } = this.state; return urlParams @@ -232,7 +300,8 @@ export class UrlPanelContent extends Component { }; private setUrl = () => { - let url; + let url: string | undefined; + if (this.state.exportUrlAs === ExportUrlAsType.EXPORT_URL_AS_SAVED_OBJECT) { url = this.getSavedObjectUrl(); } else if (this.state.useShortUrl) { @@ -241,6 +310,10 @@ export class UrlPanelContent extends Component { url = this.getSnapshotUrl(); } + if (url) { + url = this.addUrlAnonymousAccessParameters(url); + } + if (this.props.isEmbedded) { url = this.makeIframeTag(url); } @@ -269,6 +342,14 @@ export class UrlPanelContent extends Component { this.createShortUrl(); }; + private handlePublicUrlChange = () => { + this.setState(({ usePublicUrl }) => { + return { + usePublicUrl: !usePublicUrl, + }; + }, this.setUrl); + }; + private createShortUrl = async () => { this.setState({ isCreatingShortUrl: true, @@ -280,33 +361,38 @@ export class UrlPanelContent extends Component { basePath: this.props.basePath, post: this.props.post, }); - if (this.mounted) { - this.shortUrlCache = shortUrl; - this.setState( - { - isCreatingShortUrl: false, - useShortUrl: true, - }, - this.setUrl - ); + + if (!this.mounted) { + return; } + + this.shortUrlCache = shortUrl; + this.setState( + { + isCreatingShortUrl: false, + useShortUrl: true, + }, + this.setUrl + ); } catch (fetchError) { - if (this.mounted) { - this.shortUrlCache = undefined; - this.setState( - { - useShortUrl: false, - isCreatingShortUrl: false, - shortUrlErrorMsg: i18n.translate('share.urlPanel.unableCreateShortUrlErrorMessage', { - defaultMessage: 'Unable to create short URL. Error: {errorMessage}', - values: { - errorMessage: fetchError.message, - }, - }), - }, - this.setUrl - ); + if (!this.mounted) { + return; } + + this.shortUrlCache = undefined; + this.setState( + { + useShortUrl: false, + isCreatingShortUrl: false, + shortUrlErrorMsg: i18n.translate('share.urlPanel.unableCreateShortUrlErrorMessage', { + defaultMessage: 'Unable to create short URL. Error: {errorMessage}', + values: { + errorMessage: fetchError.message, + }, + }), + }, + this.setUrl + ); } }; @@ -421,6 +507,36 @@ export class UrlPanelContent extends Component { ); }; + private renderPublicUrlSwitch = () => { + if (!this.state.anonymousAccessParameters || !this.state.showPublicUrlSwitch) { + return null; + } + + const switchLabel = ( + + ); + const switchComponent = ( + + ); + const tipContent = ( + + ); + + return ( + + {this.renderWithIconTip(switchComponent, tipContent)} + + ); + }; + private renderUrlParamExtensions = (): ReactElement | void => { if (!this.props.urlParamExtensions) { return; diff --git a/src/plugins/share/public/plugin.test.ts b/src/plugins/share/public/plugin.test.ts index 2b85564ee9ef9..5a3b335115e0d 100644 --- a/src/plugins/share/public/plugin.test.ts +++ b/src/plugins/share/public/plugin.test.ts @@ -10,6 +10,7 @@ import { registryMock, managerMock } from './plugin.test.mocks'; import { SharePlugin } from './plugin'; import { CoreStart } from 'kibana/public'; import { coreMock } from '../../../core/public/mocks'; +import { mockSecurityOssPlugin } from '../../security_oss/public/mocks'; describe('SharePlugin', () => { beforeEach(() => { @@ -21,14 +22,20 @@ describe('SharePlugin', () => { describe('setup', () => { test('wires up and returns registry', async () => { const coreSetup = coreMock.createSetup(); - const setup = await new SharePlugin().setup(coreSetup); + const plugins = { + securityOss: mockSecurityOssPlugin.createSetup(), + }; + const setup = await new SharePlugin().setup(coreSetup, plugins); expect(registryMock.setup).toHaveBeenCalledWith(); expect(setup.register).toBeDefined(); }); test('registers redirect app', async () => { const coreSetup = coreMock.createSetup(); - await new SharePlugin().setup(coreSetup); + const plugins = { + securityOss: mockSecurityOssPlugin.createSetup(), + }; + await new SharePlugin().setup(coreSetup, plugins); expect(coreSetup.application.register).toHaveBeenCalledWith( expect.objectContaining({ id: 'short_url_redirect', @@ -40,13 +47,22 @@ describe('SharePlugin', () => { describe('start', () => { test('wires up and returns show function, but not registry', async () => { const coreSetup = coreMock.createSetup(); + const pluginsSetup = { + securityOss: mockSecurityOssPlugin.createSetup(), + }; const service = new SharePlugin(); - await service.setup(coreSetup); - const start = await service.start({} as CoreStart); + await service.setup(coreSetup, pluginsSetup); + const pluginsStart = { + securityOss: mockSecurityOssPlugin.createStart(), + }; + const start = await service.start({} as CoreStart, pluginsStart); expect(registryMock.start).toHaveBeenCalled(); expect(managerMock.start).toHaveBeenCalledWith( expect.anything(), - expect.objectContaining({ getShareMenuItems: expect.any(Function) }) + expect.objectContaining({ + getShareMenuItems: expect.any(Function), + }), + expect.anything() ); expect(start.toggleShareContextMenu).toBeDefined(); }); diff --git a/src/plugins/share/public/plugin.ts b/src/plugins/share/public/plugin.ts index 55baf72cc4520..26fa1c9113f21 100644 --- a/src/plugins/share/public/plugin.ts +++ b/src/plugins/share/public/plugin.ts @@ -10,6 +10,7 @@ import './index.scss'; import { CoreSetup, CoreStart, Plugin } from 'src/core/public'; import { ShareMenuManager, ShareMenuManagerStart } from './services'; +import type { SecurityOssPluginSetup, SecurityOssPluginStart } from '../../security_oss/public'; import { ShareMenuRegistry, ShareMenuRegistrySetup } from './services'; import { createShortUrlRedirectApp } from './services/short_url_redirect_app'; import { @@ -18,12 +19,20 @@ import { UrlGeneratorsStart, } from './url_generators/url_generator_service'; +export interface ShareSetupDependencies { + securityOss?: SecurityOssPluginSetup; +} + +export interface ShareStartDependencies { + securityOss?: SecurityOssPluginStart; +} + export class SharePlugin implements Plugin { private readonly shareMenuRegistry = new ShareMenuRegistry(); private readonly shareContextMenu = new ShareMenuManager(); private readonly urlGeneratorsService = new UrlGeneratorsService(); - public setup(core: CoreSetup): SharePluginSetup { + public setup(core: CoreSetup, plugins: ShareSetupDependencies): SharePluginSetup { core.application.register(createShortUrlRedirectApp(core, window.location)); return { ...this.shareMenuRegistry.setup(), @@ -31,9 +40,13 @@ export class SharePlugin implements Plugin { }; } - public start(core: CoreStart): SharePluginStart { + public start(core: CoreStart, plugins: ShareStartDependencies): SharePluginStart { return { - ...this.shareContextMenu.start(core, this.shareMenuRegistry.start()), + ...this.shareContextMenu.start( + core, + this.shareMenuRegistry.start(), + plugins.securityOss?.anonymousAccess + ), urlGenerators: this.urlGeneratorsService.start(core), }; } diff --git a/src/plugins/share/public/services/share_menu_manager.tsx b/src/plugins/share/public/services/share_menu_manager.tsx index cc3649d33d876..7284be6a8719c 100644 --- a/src/plugins/share/public/services/share_menu_manager.tsx +++ b/src/plugins/share/public/services/share_menu_manager.tsx @@ -15,13 +15,18 @@ import { CoreStart, HttpStart } from 'kibana/public'; import { ShareContextMenu } from '../components/share_context_menu'; import { ShareMenuItem, ShowShareMenuOptions } from '../types'; import { ShareMenuRegistryStart } from './share_menu_registry'; +import type { SecurityOssPluginStart } from '../../../security_oss/public'; export class ShareMenuManager { private isOpen = false; private container = document.createElement('div'); - start(core: CoreStart, shareRegistry: ShareMenuRegistryStart) { + start( + core: CoreStart, + shareRegistry: ShareMenuRegistryStart, + anonymousAccess?: SecurityOssPluginStart['anonymousAccess'] + ) { return { /** * Collects share menu items from registered providers and mounts the share context menu under @@ -35,6 +40,7 @@ export class ShareMenuManager { menuItems, post: core.http.post, basePath: core.http.basePath.get(), + anonymousAccess, }); }, }; @@ -57,10 +63,13 @@ export class ShareMenuManager { post, basePath, embedUrlParamExtensions, + anonymousAccess, + showPublicUrlSwitch, }: ShowShareMenuOptions & { menuItems: ShareMenuItem[]; post: HttpStart['post']; basePath: string; + anonymousAccess?: SecurityOssPluginStart['anonymousAccess']; }) { if (this.isOpen) { this.onClose(); @@ -92,6 +101,8 @@ export class ShareMenuManager { post={post} basePath={basePath} embedUrlParamExtensions={embedUrlParamExtensions} + anonymousAccess={anonymousAccess} + showPublicUrlSwitch={showPublicUrlSwitch} /> diff --git a/src/plugins/share/public/types.ts b/src/plugins/share/public/types.ts index 88bb51389b001..31c9631571d35 100644 --- a/src/plugins/share/public/types.ts +++ b/src/plugins/share/public/types.ts @@ -9,6 +9,7 @@ import { ComponentType } from 'react'; import { EuiContextMenuPanelDescriptor } from '@elastic/eui'; import { EuiContextMenuPanelItemDescriptorEntry } from '@elastic/eui/src/components/context_menu/context_menu'; +import type { Capabilities } from 'src/core/public'; /** * @public @@ -35,6 +36,7 @@ export interface ShareContext { sharingData: { [key: string]: unknown }; isDirty: boolean; onClose: () => void; + showPublicUrlSwitch?: (anonymousUserCapabilities: Capabilities) => boolean; } /** diff --git a/src/plugins/share/tsconfig.json b/src/plugins/share/tsconfig.json index a6318af602b4d..985066915f1dd 100644 --- a/src/plugins/share/tsconfig.json +++ b/src/plugins/share/tsconfig.json @@ -10,6 +10,7 @@ "include": ["common/**/*", "public/**/*", "server/**/*"], "references": [ { "path": "../../core/tsconfig.json" }, - { "path": "../../plugins/kibana_utils/tsconfig.json" } + { "path": "../kibana_utils/tsconfig.json" }, + { "path": "../security_oss/tsconfig.json" } ] } diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json index 7bac6a809eca3..950fdf9405b75 100644 --- a/src/plugins/telemetry/schema/oss_plugins.json +++ b/src/plugins/telemetry/schema/oss_plugins.json @@ -4076,6 +4076,27 @@ }, "stack_management": { "properties": { + "timelion:quandl.key": { + "type": "keyword" + }, + "securitySolution:defaultIndex": { + "type": "keyword" + }, + "securitySolution:newsFeedUrl": { + "type": "keyword" + }, + "xpackReporting:customPdfLogo": { + "type": "keyword" + }, + "notifications:banner": { + "type": "keyword" + }, + "timelion:graphite.url": { + "type": "keyword" + }, + "xpackDashboardMode:roles": { + "type": "keyword" + }, "visualize:enableLabs": { "type": "boolean" }, @@ -4095,7 +4116,7 @@ "type": "long" }, "securitySolution:ipReputationLinks": { - "type": "text" + "type": "keyword" }, "csv:separator": { "type": "keyword" @@ -4121,9 +4142,6 @@ "timelion:default_columns": { "type": "long" }, - "timelion:quandl.key": { - "type": "keyword" - }, "timelion:es.default_index": { "type": "keyword" }, @@ -4136,15 +4154,9 @@ "securitySolution:defaultAnomalyScore": { "type": "long" }, - "securitySolution:defaultIndex": { - "type": "keyword" - }, "securitySolution:refreshIntervalDefaults": { "type": "keyword" }, - "securitySolution:newsFeedUrl": { - "type": "keyword" - }, "securitySolution:enableNewsFeed": { "type": "boolean" }, @@ -4169,9 +4181,6 @@ "rollups:enableIndexPatterns": { "type": "boolean" }, - "xpackReporting:customPdfLogo": { - "type": "text" - }, "notifications:lifetime:warning": { "type": "long" }, @@ -4181,9 +4190,6 @@ "notifications:lifetime:info": { "type": "long" }, - "notifications:banner": { - "type": "text" - }, "notifications:lifetime:error": { "type": "long" }, @@ -4200,7 +4206,10 @@ "type": "long" }, "defaultColumns": { - "type": "keyword" + "type": "array", + "items": { + "type": "keyword" + } }, "context:defaultSize": { "type": "long" @@ -4209,7 +4218,10 @@ "type": "long" }, "context:tieBreakerFields": { - "type": "keyword" + "type": "array", + "items": { + "type": "keyword" + } }, "discover:sort:defaultOrder": { "type": "keyword" @@ -4275,7 +4287,10 @@ "type": "boolean" }, "metaFields": { - "type": "keyword" + "type": "array", + "items": { + "type": "keyword" + } }, "indexPattern:placeholder": { "type": "keyword" @@ -4339,6 +4354,36 @@ }, "dateFormat": { "type": "keyword" + }, + "autocomplete:useTimeRange": { + "type": "boolean" + }, + "search:timeout": { + "type": "long" + }, + "visualization:visualize:legacyChartsLibrary": { + "type": "boolean" + }, + "doc_table:legacy": { + "type": "boolean" + }, + "discover:modifyColumnsOnSwitch": { + "type": "boolean" + }, + "discover:searchFieldsFromSource": { + "type": "boolean" + }, + "securitySolution:rulesTableRefresh": { + "type": "text" + }, + "apm:enableSignificantTerms": { + "type": "boolean" + }, + "apm:enableServiceOverview": { + "type": "boolean" + }, + "apm:enableCorrelations": { + "type": "boolean" } } }, diff --git a/src/plugins/tile_map/tsconfig.json b/src/plugins/tile_map/tsconfig.json new file mode 100644 index 0000000000000..40f76ece2a6ff --- /dev/null +++ b/src/plugins/tile_map/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": ["public/**/*", "server/**/*"], + "references": [ + { "path": "../maps_legacy/tsconfig.json" }, + { "path": "../vis_default_editor/tsconfig.json" }, + ] +} diff --git a/src/plugins/usage_collection/server/collector/collector.ts b/src/plugins/usage_collection/server/collector/collector.ts index ccc17ea1c5967..8e8a74902d479 100644 --- a/src/plugins/usage_collection/server/collector/collector.ts +++ b/src/plugins/usage_collection/server/collector/collector.ts @@ -14,17 +14,38 @@ import { KibanaRequest, } from 'src/core/server'; -export type AllowedSchemaNumberTypes = 'long' | 'integer' | 'short' | 'byte' | 'double' | 'float'; +export type AllowedSchemaNumberTypes = + | 'long' + | 'integer' + | 'short' + | 'byte' + | 'double' + | 'float' + | 'date'; +export type AllowedSchemaStringTypes = 'keyword' | 'text' | 'date'; +export type AllowedSchemaBooleanTypes = 'boolean'; -export type AllowedSchemaTypes = AllowedSchemaNumberTypes | 'keyword' | 'text' | 'boolean' | 'date'; +export type AllowedSchemaTypes = + | AllowedSchemaNumberTypes + | AllowedSchemaStringTypes + | AllowedSchemaBooleanTypes; export interface SchemaField { type: string; } +export type PossibleSchemaTypes = U extends string + ? AllowedSchemaStringTypes + : U extends number + ? AllowedSchemaNumberTypes + : U extends boolean + ? AllowedSchemaBooleanTypes + : // allow any schema type from the union if typescript is unable to resolve the exact U type + AllowedSchemaTypes; + export type RecursiveMakeSchemaFrom = U extends object ? MakeSchemaFrom - : { type: AllowedSchemaTypes }; + : { type: PossibleSchemaTypes }; // Using Required to enforce all optional keys in the object export type MakeSchemaFrom = { diff --git a/src/plugins/vis_type_timelion/server/plugin.ts b/src/plugins/vis_type_timelion/server/plugin.ts index f999c1dfc773a..fca557efc01e3 100644 --- a/src/plugins/vis_type_timelion/server/plugin.ts +++ b/src/plugins/vis_type_timelion/server/plugin.ts @@ -173,6 +173,7 @@ export class Plugin { defaultMessage: '{experimentalLabel} Your API key from www.quandl.com', values: { experimentalLabel: `[${experimentalLabel}]` }, }), + sensitive: true, category: ['timelion'], schema: schema.string(), }, diff --git a/src/plugins/vis_type_timeseries/public/application/components/aggs/math.js b/src/plugins/vis_type_timeseries/public/application/components/aggs/math.js index 1dab41566da67..f66c011d1f928 100644 --- a/src/plugins/vis_type_timeseries/public/application/components/aggs/math.js +++ b/src/plugins/vis_type_timeseries/public/application/components/aggs/math.js @@ -114,7 +114,7 @@ export function MathAgg(props) { values={{ link: ( = { schema: configSchema, }; -export { - AbstractSearchStrategy, - ReqFacade, -} from './lib/search_strategies/strategies/abstract_search_strategy'; - -export { VisPayload } from '../common/types'; - -export { DefaultSearchCapabilities } from './lib/search_strategies/default_search_capabilities'; - export function plugin(initializerContext: PluginInitializerContext) { return new VisTypeTimeseriesPlugin(initializerContext); } diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/default_search_capabilities.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/default_search_capabilities.test.ts similarity index 95% rename from src/plugins/vis_type_timeseries/server/lib/search_strategies/default_search_capabilities.test.ts rename to src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/default_search_capabilities.test.ts index d4e3064747ab0..105bfd53ce739 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/default_search_capabilities.test.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/default_search_capabilities.test.ts @@ -7,8 +7,8 @@ */ import { DefaultSearchCapabilities } from './default_search_capabilities'; -import { ReqFacade } from './strategies/abstract_search_strategy'; -import { VisPayload } from '../../../common/types'; +import type { ReqFacade } from '../strategies/abstract_search_strategy'; +import type { VisPayload } from '../../../../common/types'; describe('DefaultSearchCapabilities', () => { let defaultSearchCapabilities: DefaultSearchCapabilities; diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/default_search_capabilities.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/default_search_capabilities.ts similarity index 89% rename from src/plugins/vis_type_timeseries/server/lib/search_strategies/default_search_capabilities.ts rename to src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/default_search_capabilities.ts index 1755e25138e8f..996efce4ce66e 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/default_search_capabilities.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/default_search_capabilities.ts @@ -11,10 +11,10 @@ import { convertIntervalToUnit, parseInterval, getSuitableUnit, -} from '../vis_data/helpers/unit_to_seconds'; -import { RESTRICTIONS_KEYS } from '../../../common/ui_restrictions'; -import { ReqFacade } from './strategies/abstract_search_strategy'; -import { VisPayload } from '../../../common/types'; +} from '../../vis_data/helpers/unit_to_seconds'; +import { RESTRICTIONS_KEYS } from '../../../../common/ui_restrictions'; +import type { ReqFacade } from '../strategies/abstract_search_strategy'; +import type { VisPayload } from '../../../../common/types'; const getTimezoneFromRequest = (request: ReqFacade) => { return request.payload.timerange.timezone; diff --git a/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_capabilities.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/rollup_search_capabilities.test.ts similarity index 92% rename from x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_capabilities.test.ts rename to src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/rollup_search_capabilities.test.ts index 6c30895635fe5..443b700386c15 100644 --- a/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_capabilities.test.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/rollup_search_capabilities.test.ts @@ -1,12 +1,16 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. */ + import { Unit } from '@elastic/datemath'; import { RollupSearchCapabilities } from './rollup_search_capabilities'; -import { ReqFacade, VisPayload } from '../../../../../src/plugins/vis_type_timeseries/server'; +import type { VisPayload } from '../../../../common/types'; +import type { ReqFacade } from '../strategies/abstract_search_strategy'; describe('Rollup Search Capabilities', () => { const testTimeZone = 'time_zone'; diff --git a/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_capabilities.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/rollup_search_capabilities.ts similarity index 86% rename from x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_capabilities.ts rename to src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/rollup_search_capabilities.ts index 015a371bd2a35..787a8ff1b2051 100644 --- a/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_capabilities.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/capabilities/rollup_search_capabilities.ts @@ -1,16 +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. + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. */ + import { get, has } from 'lodash'; -import { leastCommonInterval, isCalendarInterval } from './lib/interval_helper'; +import { leastCommonInterval, isCalendarInterval } from '../lib/interval_helper'; + +import { DefaultSearchCapabilities } from './default_search_capabilities'; -import { - ReqFacade, - DefaultSearchCapabilities, - VisPayload, -} from '../../../../../src/plugins/vis_type_timeseries/server'; +import type { VisPayload } from '../../../../common/types'; +import type { ReqFacade } from '../strategies/abstract_search_strategy'; export class RollupSearchCapabilities extends DefaultSearchCapabilities { rollupIndex: string; diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/index.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/index.ts index 7dd7bfe780b52..2df6f002481b5 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/index.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/index.ts @@ -7,3 +7,11 @@ */ export { SearchStrategyRegistry } from './search_strategy_registry'; +export { DefaultSearchCapabilities } from './capabilities/default_search_capabilities'; + +export { + AbstractSearchStrategy, + ReqFacade, + RollupSearchStrategy, + DefaultSearchStrategy, +} from './strategies'; diff --git a/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/lib/interval_helper.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/interval_helper.test.ts similarity index 94% rename from x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/lib/interval_helper.test.ts rename to src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/interval_helper.test.ts index 31baeadce6527..158c1d74964b3 100644 --- a/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/lib/interval_helper.test.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/interval_helper.test.ts @@ -1,8 +1,11 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. */ + import { isCalendarInterval, leastCommonInterval } from './interval_helper'; describe('interval_helper', () => { diff --git a/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/lib/interval_helper.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/interval_helper.ts similarity index 74% rename from x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/lib/interval_helper.ts rename to src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/interval_helper.ts index 91d73cecdf401..f4ac715b5b0f2 100644 --- a/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/lib/interval_helper.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/lib/interval_helper.ts @@ -1,7 +1,9 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. */ import dateMath from '@elastic/datemath'; diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategies_registry.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategies_registry.test.ts index 81bf8920c54fc..21b746656c043 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategies_registry.test.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategies_registry.test.ts @@ -5,14 +5,13 @@ * compliance with, at your election, the Elastic License or the Server Side * Public License, v 1. */ +import { get } from 'lodash'; +import { RequestFacade, SearchStrategyRegistry } from './search_strategy_registry'; +import { AbstractSearchStrategy, DefaultSearchStrategy } from './strategies'; +import { DefaultSearchCapabilities } from './capabilities/default_search_capabilities'; -import { SearchStrategyRegistry } from './search_strategy_registry'; -// @ts-ignore -import { AbstractSearchStrategy } from './strategies/abstract_search_strategy'; -// @ts-ignore -import { DefaultSearchStrategy } from './strategies/default_search_strategy'; -// @ts-ignore -import { DefaultSearchCapabilities } from './default_search_capabilities'; +const getPrivateField = (registry: SearchStrategyRegistry, field: string) => + get(registry, field) as T; class MockSearchStrategy extends AbstractSearchStrategy { checkForViability() { @@ -28,23 +27,21 @@ describe('SearchStrategyRegister', () => { beforeAll(() => { registry = new SearchStrategyRegistry(); + registry.addStrategy(new DefaultSearchStrategy()); }); test('should init strategies register', () => { - expect( - registry.addStrategy({} as AbstractSearchStrategy)[0] instanceof DefaultSearchStrategy - ).toBe(true); + expect(getPrivateField(registry, 'strategies')).toHaveLength(1); }); test('should not add a strategy if it is not an instance of AbstractSearchStrategy', () => { const addedStrategies = registry.addStrategy({} as AbstractSearchStrategy); expect(addedStrategies.length).toEqual(1); - expect(addedStrategies[0] instanceof DefaultSearchStrategy).toBe(true); }); test('should return a DefaultSearchStrategy instance', async () => { - const req = {}; + const req = {} as RequestFacade; const indexPattern = '*'; const { searchStrategy, capabilities } = (await registry.getViableStrategy(req, indexPattern))!; @@ -62,7 +59,7 @@ describe('SearchStrategyRegister', () => { }); test('should return a MockSearchStrategy instance', async () => { - const req = {}; + const req = {} as RequestFacade; const indexPattern = '*'; const anotherSearchStrategy = new MockSearchStrategy(); registry.addStrategy(anotherSearchStrategy); diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategy_registry.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategy_registry.ts index 9e7272f14f146..f3bf854f00ef4 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategy_registry.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/search_strategy_registry.ts @@ -6,23 +6,15 @@ * Public License, v 1. */ -import { AbstractSearchStrategy } from './strategies/abstract_search_strategy'; -// @ts-ignore -import { DefaultSearchStrategy } from './strategies/default_search_strategy'; -// @ts-ignore import { extractIndexPatterns } from '../../../common/extract_index_patterns'; - -export type RequestFacade = any; - import { PanelSchema } from '../../../common/types'; +import { AbstractSearchStrategy, ReqFacade } from './strategies'; + +export type RequestFacade = ReqFacade; export class SearchStrategyRegistry { private strategies: AbstractSearchStrategy[] = []; - constructor() { - this.addStrategy(new DefaultSearchStrategy()); - } - public addStrategy(searchStrategy: AbstractSearchStrategy) { if (searchStrategy instanceof AbstractSearchStrategy) { this.strategies.unshift(searchStrategy); diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.ts similarity index 72% rename from src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js rename to src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.ts index a4fc48ccc6266..97876ec2579f0 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.test.ts @@ -7,29 +7,35 @@ */ import { from } from 'rxjs'; -import { AbstractSearchStrategy } from './abstract_search_strategy'; +import { AbstractSearchStrategy, ReqFacade } from './abstract_search_strategy'; +import type { VisPayload } from '../../../../common/types'; +import type { IFieldType } from '../../../../../data/common'; + +class FooSearchStrategy extends AbstractSearchStrategy {} describe('AbstractSearchStrategy', () => { - let abstractSearchStrategy; - let req; - let mockedFields; - let indexPattern; + let abstractSearchStrategy: AbstractSearchStrategy; + let req: ReqFacade; + let mockedFields: IFieldType[]; + let indexPattern: string; beforeEach(() => { mockedFields = []; - req = { + req = ({ payload: {}, pre: { indexPatternsFetcher: { getFieldsForWildcard: jest.fn().mockReturnValue(mockedFields), }, }, - getIndexPatternsService: jest.fn(() => ({ - find: jest.fn(() => []), - })), - }; + getIndexPatternsService: jest.fn(() => + Promise.resolve({ + find: jest.fn(() => []), + }) + ), + } as unknown) as ReqFacade; - abstractSearchStrategy = new AbstractSearchStrategy(); + abstractSearchStrategy = new FooSearchStrategy(); }); test('should init an AbstractSearchStrategy instance', () => { @@ -42,7 +48,7 @@ describe('AbstractSearchStrategy', () => { const fields = await abstractSearchStrategy.getFieldsForWildcard(req, indexPattern); expect(fields).toEqual(mockedFields); - expect(req.pre.indexPatternsFetcher.getFieldsForWildcard).toHaveBeenCalledWith({ + expect(req.pre.indexPatternsFetcher!.getFieldsForWildcard).toHaveBeenCalledWith({ pattern: indexPattern, metaFields: [], fieldCapsOptions: { allow_no_indices: true }, @@ -54,7 +60,7 @@ describe('AbstractSearchStrategy', () => { const searchFn = jest.fn().mockReturnValue(from(Promise.resolve({}))); const responses = await abstractSearchStrategy.search( - { + ({ payload: { searchSession: { sessionId: '1', @@ -65,7 +71,7 @@ describe('AbstractSearchStrategy', () => { requestContext: { search: { search: searchFn }, }, - }, + } as unknown) as ReqFacade, searches ); diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts index 966daca87a208..bf7088145f347 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/abstract_search_strategy.ts @@ -8,12 +8,13 @@ import type { FakeRequest, IUiSettingsClient, SavedObjectsClientContract } from 'kibana/server'; +import { indexPatterns } from '../../../../../data/server'; + import type { Framework } from '../../../plugin'; import type { IndexPatternsFetcher, IFieldType } from '../../../../../data/server'; import type { VisPayload } from '../../../../common/types'; import type { IndexPatternsService } from '../../../../../data/common'; -import { indexPatterns } from '../../../../../data/server'; -import { SanitizedFieldType } from '../../../../common/types'; +import type { SanitizedFieldType } from '../../../../common/types'; import type { VisTypeTimeseriesRequestHandlerContext } from '../../../types'; /** diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.test.ts index c6f7474ed86bf..00dbf17945011 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.test.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.test.ts @@ -7,8 +7,8 @@ */ import { DefaultSearchStrategy } from './default_search_strategy'; -import { ReqFacade } from './abstract_search_strategy'; -import { VisPayload } from '../../../../common/types'; +import type { ReqFacade } from './abstract_search_strategy'; +import type { VisPayload } from '../../../../common/types'; describe('DefaultSearchStrategy', () => { let defaultSearchStrategy: DefaultSearchStrategy; @@ -20,7 +20,6 @@ describe('DefaultSearchStrategy', () => { }); test('should init an DefaultSearchStrategy instance', () => { - expect(defaultSearchStrategy.name).toBe('default'); expect(defaultSearchStrategy.checkForViability).toBeDefined(); expect(defaultSearchStrategy.search).toBeDefined(); expect(defaultSearchStrategy.getFieldsForWildcard).toBeDefined(); diff --git a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.ts index 803926ad58c50..791ff4efd3936 100644 --- a/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/default_search_strategy.ts @@ -7,12 +7,10 @@ */ import { AbstractSearchStrategy, ReqFacade } from './abstract_search_strategy'; -import { DefaultSearchCapabilities } from '../default_search_capabilities'; +import { DefaultSearchCapabilities } from '../capabilities/default_search_capabilities'; import { VisPayload } from '../../../../common/types'; export class DefaultSearchStrategy extends AbstractSearchStrategy { - name = 'default'; - checkForViability(req: ReqFacade) { return Promise.resolve({ isViable: true, diff --git a/src/plugins/lens_oss/config.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/index.ts similarity index 62% rename from src/plugins/lens_oss/config.ts rename to src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/index.ts index 58c50f0104f46..953624e476dc8 100644 --- a/src/plugins/lens_oss/config.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/index.ts @@ -6,10 +6,6 @@ * Public License, v 1. */ -import { schema, TypeOf } from '@kbn/config-schema'; - -export const configSchema = schema.object({ - enabled: schema.boolean({ defaultValue: true }), -}); - -export type ConfigSchema = TypeOf; +export { AbstractSearchStrategy, ReqFacade } from './abstract_search_strategy'; +export { DefaultSearchStrategy } from './default_search_strategy'; +export { RollupSearchStrategy } from './rollup_search_strategy'; diff --git a/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_strategy.test.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.test.ts similarity index 90% rename from x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_strategy.test.ts rename to src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.test.ts index e3fbe2daa3756..8e5c2fdabca5d 100644 --- a/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_strategy.test.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.test.ts @@ -1,13 +1,17 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. */ + import { RollupSearchStrategy } from './rollup_search_strategy'; -import type { ReqFacade, VisPayload } from '../../../../../src/plugins/vis_type_timeseries/server'; -jest.mock('../../../../../src/plugins/vis_type_timeseries/server', () => { - const actual = jest.requireActual('../../../../../src/plugins/vis_type_timeseries/server'); +import type { VisPayload } from '../../../../common/types'; +import type { ReqFacade } from './abstract_search_strategy'; + +jest.mock('./abstract_search_strategy', () => { class AbstractSearchStrategyMock { getFieldsForWildcard() { return [ @@ -23,7 +27,6 @@ jest.mock('../../../../../src/plugins/vis_type_timeseries/server', () => { } return { - ...actual, AbstractSearchStrategy: AbstractSearchStrategyMock, }; }); @@ -52,7 +55,7 @@ describe('Rollup Search Strategy', () => { test('should create instance of RollupSearchRequest', () => { const rollupSearchStrategy = new RollupSearchStrategy(); - expect(rollupSearchStrategy.name).toBe('rollup'); + expect(rollupSearchStrategy).toBeDefined(); }); describe('checkForViability', () => { diff --git a/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_strategy.ts b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.ts similarity index 81% rename from x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_strategy.ts rename to src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.ts index 60fa51d0995db..5b5a1bd5db79e 100644 --- a/x-pack/plugins/vis_type_timeseries_enhanced/server/search_strategies/rollup_search_strategy.ts +++ b/src/plugins/vis_type_timeseries/server/lib/search_strategies/strategies/rollup_search_strategy.ts @@ -1,17 +1,16 @@ /* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. */ -import { - AbstractSearchStrategy, - ReqFacade, - VisPayload, -} from '../../../../../src/plugins/vis_type_timeseries/server'; -import { getCapabilitiesForRollupIndices } from '../../../../../src/plugins/data/server'; +import { ReqFacade, AbstractSearchStrategy } from './abstract_search_strategy'; +import { RollupSearchCapabilities } from '../capabilities/rollup_search_capabilities'; +import type { VisPayload } from '../../../../common/types'; -import { RollupSearchCapabilities } from './rollup_search_capabilities'; +import { getCapabilitiesForRollupIndices } from '../../../../../data/server'; const getRollupIndices = (rollupData: { [key: string]: any }) => Object.keys(rollupData); const isIndexPatternContainsWildcard = (indexPattern: string) => indexPattern.includes('*'); @@ -19,8 +18,6 @@ const isIndexPatternValid = (indexPattern: string) => indexPattern && typeof indexPattern === 'string' && !isIndexPatternContainsWildcard(indexPattern); export class RollupSearchStrategy extends AbstractSearchStrategy { - name = 'rollup'; - async search(req: ReqFacade, bodies: any[]) { return super.search(req, bodies, 'rollup'); } diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/fields_fetcher.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/fields_fetcher.ts index d94362e681642..d13efcfe37149 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/fields_fetcher.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/fields_fetcher.ts @@ -6,7 +6,11 @@ * Public License, v 1. */ -import { AbstractSearchStrategy, DefaultSearchCapabilities, ReqFacade } from '../../..'; +import { + AbstractSearchStrategy, + DefaultSearchCapabilities, + ReqFacade, +} from '../../search_strategies'; export const createFieldsFetcher = ( req: ReqFacade, diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_timerange.test.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_timerange.test.ts index d97e948551b1a..a20df9145d987 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_timerange.test.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_timerange.test.ts @@ -8,7 +8,8 @@ import moment from 'moment'; import { getTimerange } from './get_timerange'; -import { ReqFacade, VisPayload } from '../../..'; +import type { ReqFacade } from '../../search_strategies'; +import type { VisPayload } from '../../../../common/types'; describe('getTimerange(req)', () => { test('should return a moment object for to and from', () => { diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_timerange.ts b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_timerange.ts index b690ad0fb0325..2797839988ded 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_timerange.ts +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/helpers/get_timerange.ts @@ -7,7 +7,8 @@ */ import { utc } from 'moment'; -import { ReqFacade, VisPayload } from '../../..'; +import type { ReqFacade } from '../../search_strategies'; +import type { VisPayload } from '../../../../common/types'; export const getTimerange = (req: ReqFacade) => { const { min, max } = req.payload.timerange; diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js index 06ff882190ce5..bcb158ebfe2bb 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/request_processors/series/date_histogram.test.js @@ -6,7 +6,7 @@ * Public License, v 1. */ -import { DefaultSearchCapabilities } from '../../../search_strategies/default_search_capabilities'; +import { DefaultSearchCapabilities } from '../../../search_strategies/capabilities/default_search_capabilities'; import { dateHistogram } from './date_histogram'; import { UI_SETTINGS } from '../../../../../../data/common'; diff --git a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.js b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.js index 9d3296e76727e..1ea197976fb4f 100644 --- a/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.js +++ b/src/plugins/vis_type_timeseries/server/lib/vis_data/response_processors/series/math.js @@ -12,7 +12,7 @@ import { getDefaultDecoration } from '../../helpers/get_default_decoration'; import { getSiblingAggValue } from '../../helpers/get_sibling_agg_value'; import { getSplits } from '../../helpers/get_splits'; import { mapBucket } from '../../helpers/map_bucket'; -import { evaluate } from 'tinymath'; +import { evaluate } from '@kbn/tinymath'; export function mathAgg(resp, panel, series, meta, extractFields) { return (next) => async (results) => { diff --git a/src/plugins/vis_type_timeseries/server/plugin.ts b/src/plugins/vis_type_timeseries/server/plugin.ts index adcd7e8bbf0d5..43b61f37ba3d3 100644 --- a/src/plugins/vis_type_timeseries/server/plugin.ts +++ b/src/plugins/vis_type_timeseries/server/plugin.ts @@ -23,10 +23,15 @@ import { PluginStart } from '../../data/server'; import { visDataRoutes } from './routes/vis'; // @ts-ignore import { fieldsRoutes } from './routes/fields'; -import { SearchStrategyRegistry } from './lib/search_strategies'; import { uiSettings } from './ui_settings'; import type { VisTypeTimeseriesRequestHandlerContext, VisTypeTimeseriesRouter } from './types'; +import { + SearchStrategyRegistry, + DefaultSearchStrategy, + RollupSearchStrategy, +} from './lib/search_strategies'; + export interface LegacySetup { server: Server; } @@ -45,7 +50,6 @@ export interface VisTypeTimeseriesSetup { fakeRequest: FakeRequest, options: GetVisDataOptions ) => ReturnType; - addSearchStrategy: SearchStrategyRegistry['addStrategy']; } export interface Framework { @@ -76,6 +80,9 @@ export class VisTypeTimeseriesPlugin implements Plugin { const searchStrategyRegistry = new SearchStrategyRegistry(); + searchStrategyRegistry.addStrategy(new DefaultSearchStrategy()); + searchStrategyRegistry.addStrategy(new RollupSearchStrategy()); + const framework: Framework = { core, plugins, @@ -97,7 +104,6 @@ export class VisTypeTimeseriesPlugin implements Plugin { ) => { return await getVisData(requestContext, { ...fakeRequest, body: options }, framework); }, - addSearchStrategy: searchStrategyRegistry.addStrategy.bind(searchStrategyRegistry), }; } diff --git a/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts b/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts index 839d2b1f57c34..cb6bc624801d9 100644 --- a/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts +++ b/src/plugins/visualizations/public/vis_types/vis_type_alias_registry.ts @@ -31,11 +31,6 @@ export interface VisualizationsAppExtension { toListItem: (savedObject: SavedObject) => VisualizationListItem; } -export interface VisTypeAliasPromoTooltip { - description: string; - link: string; -} - export interface VisTypeAlias { aliasPath: string; aliasApp: string; @@ -43,10 +38,8 @@ export interface VisTypeAlias { title: string; icon: string; promotion?: boolean; - promoTooltip?: VisTypeAliasPromoTooltip; description: string; note?: string; - disabled?: boolean; getSupportedTriggers?: () => string[]; stage: VisualizationStage; diff --git a/src/plugins/visualizations/public/wizard/group_selection/group_selection.test.tsx b/src/plugins/visualizations/public/wizard/group_selection/group_selection.test.tsx index 74163296e31fd..396be30aca6d0 100644 --- a/src/plugins/visualizations/public/wizard/group_selection/group_selection.test.tsx +++ b/src/plugins/visualizations/public/wizard/group_selection/group_selection.test.tsx @@ -39,11 +39,6 @@ describe('GroupSelection', () => { title: 'Vis with alias Url', aliasApp: 'aliasApp', aliasPath: '#/aliasApp', - disabled: true, - promoTooltip: { - description: 'Learn More', - link: '#/anotherUrl', - }, description: 'Vis with alias Url', stage: 'production', group: VisGroups.PROMOTED, @@ -227,41 +222,6 @@ describe('GroupSelection', () => { ]); }); - it('should render disabled aliases with a disabled class', () => { - const wrapper = mountWithIntl( - - ); - expect(wrapper.find('[data-test-subj="visType-visWithAliasUrl"]').exists()).toBe(true); - expect( - wrapper - .find('[data-test-subj="visType-visWithAliasUrl"]') - .at(1) - .hasClass('euiCard-isDisabled') - ).toBe(true); - }); - - it('should render a basic badge with link for disabled aliases with promoTooltip', () => { - const wrapper = mountWithIntl( - - ); - expect(wrapper.find('[data-test-subj="visTypeBadge"]').exists()).toBe(true); - expect(wrapper.find('[data-test-subj="visTypeBadge"]').at(0).prop('tooltipContent')).toBe( - 'Learn More' - ); - }); - it('should not show tools experimental visualizations if showExperimentalis false', () => { const expVis = { name: 'visExp', diff --git a/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx b/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx index 9b61b2c415e9f..594e37f6a7608 100644 --- a/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx +++ b/src/plugins/visualizations/public/wizard/group_selection/group_selection.tsx @@ -48,10 +48,6 @@ interface VisCardProps { showExperimental?: boolean | undefined; } -function isVisTypeAlias(type: BaseVisType | VisTypeAlias): type is VisTypeAlias { - return 'aliasPath' in type; -} - function GroupSelection(props: GroupSelectionProps) { const visualizeGuideLink = props.docLinks.links.dashboard.guide; const promotedVisGroups = useMemo( @@ -185,29 +181,8 @@ const VisGroup = ({ visType, onVisTypeSelected }: VisCardProps) => { const onClick = useCallback(() => { onVisTypeSelected(visType); }, [onVisTypeSelected, visType]); - const shouldDisableCard = isVisTypeAlias(visType) && visType.disabled; - const betaBadgeContent = - shouldDisableCard && 'promoTooltip' in visType ? ( - - - - ) : undefined; return ( - {betaBadgeContent} { } onClick={onClick} - isDisabled={shouldDisableCard} data-test-subj={`visType-${visType.name}`} data-vis-stage={!('aliasPath' in visType) ? visType.stage : 'alias'} aria-label={`visType-${visType.name}`} diff --git a/src/plugins/visualize/common/constants.ts b/src/plugins/visualize/common/constants.ts index b37eadd9b67e5..7e353ca86698a 100644 --- a/src/plugins/visualize/common/constants.ts +++ b/src/plugins/visualize/common/constants.ts @@ -7,3 +7,5 @@ */ export const AGGS_TERMS_SIZE_SETTING = 'discover:aggs:terms:size'; +export const STATE_STORAGE_KEY = '_a'; +export const GLOBAL_STATE_STORAGE_KEY = '_g'; diff --git a/src/plugins/visualize/public/application/components/visualize_listing.tsx b/src/plugins/visualize/public/application/components/visualize_listing.tsx index 38e2b59009b38..34131ae2dc7fb 100644 --- a/src/plugins/visualize/public/application/components/visualize_listing.tsx +++ b/src/plugins/visualize/public/application/components/visualize_listing.tsx @@ -40,6 +40,7 @@ export const VisualizeListing = () => { savedObjectsTagging, uiSettings, visualizeCapabilities, + kbnUrlStateStorage, }, } = useKibana(); const { pathname } = useLocation(); @@ -94,11 +95,10 @@ export const VisualizeListing = () => { ); const noItemsFragment = useMemo(() => getNoItemsMessage(createNewVis), [createNewVis]); - const tableColumns = useMemo(() => getTableColumns(application, history, savedObjectsTagging), [ - application, - history, - savedObjectsTagging, - ]); + const tableColumns = useMemo( + () => getTableColumns(application, kbnUrlStateStorage, savedObjectsTagging), + [application, kbnUrlStateStorage, savedObjectsTagging] + ); const fetchItems = useCallback( (filter) => { diff --git a/src/plugins/visualize/public/application/utils/get_table_columns.tsx b/src/plugins/visualize/public/application/utils/get_table_columns.tsx index daa419b5f31b4..d9dafa7335671 100644 --- a/src/plugins/visualize/public/application/utils/get_table_columns.tsx +++ b/src/plugins/visualize/public/application/utils/get_table_columns.tsx @@ -7,14 +7,15 @@ */ import React from 'react'; -import { History } from 'history'; import { EuiBetaBadge, EuiButton, EuiEmptyPrompt, EuiIcon, EuiLink, EuiBadge } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; - import { ApplicationStart } from 'kibana/public'; +import { IKbnUrlStateStorage } from 'src/plugins/kibana_utils/public'; import { VisualizationListItem } from 'src/plugins/visualizations/public'; import type { SavedObjectsTaggingApi } from 'src/plugins/saved_objects_tagging_oss/public'; +import { RedirectAppLinks } from '../../../../kibana_react/public'; +import { getVisualizeListItemLink } from './get_visualize_list_item_link'; const getBadge = (item: VisualizationListItem) => { if (item.stage === 'beta') { @@ -72,7 +73,7 @@ const renderItemTypeIcon = (item: VisualizationListItem) => { export const getTableColumns = ( application: ApplicationStart, - history: History, + kbnUrlStateStorage: IKbnUrlStateStorage, taggingApi?: SavedObjectsTaggingApi ) => [ { @@ -84,18 +85,14 @@ export const getTableColumns = ( render: (field: string, { editApp, editUrl, title, error }: VisualizationListItem) => // In case an error occurs i.e. the vis has wrong type, we render the vis but without the link !error ? ( - { - if (editApp) { - application.navigateToApp(editApp, { path: editUrl }); - } else if (editUrl) { - history.push(editUrl); - } - }} - data-test-subj={`visListingTitleLink-${title.split(' ').join('-')}`} - > - {field} - + + + {field} + + ) : ( field ), diff --git a/src/plugins/visualize/public/application/utils/get_top_nav_config.test.tsx b/src/plugins/visualize/public/application/utils/get_top_nav_config.test.tsx new file mode 100644 index 0000000000000..2b7706d4f9d9f --- /dev/null +++ b/src/plugins/visualize/public/application/utils/get_top_nav_config.test.tsx @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { Capabilities } from 'src/core/public'; +import { showPublicUrlSwitch } from './get_top_nav_config'; + +describe('showPublicUrlSwitch', () => { + test('returns false if "visualize" app is not available', () => { + const anonymousUserCapabilities: Capabilities = { + catalogue: {}, + management: {}, + navLinks: {}, + }; + const result = showPublicUrlSwitch(anonymousUserCapabilities); + + expect(result).toBe(false); + }); + + test('returns false if "visualize" app is not accessible', () => { + const anonymousUserCapabilities: Capabilities = { + catalogue: {}, + management: {}, + navLinks: {}, + visualize: { + show: false, + }, + }; + const result = showPublicUrlSwitch(anonymousUserCapabilities); + + expect(result).toBe(false); + }); + + test('returns true if "visualize" app is not available an accessible', () => { + const anonymousUserCapabilities: Capabilities = { + catalogue: {}, + management: {}, + navLinks: {}, + visualize: { + show: true, + }, + }; + const result = showPublicUrlSwitch(anonymousUserCapabilities); + + expect(result).toBe(true); + }); +}); diff --git a/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx b/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx index c4aefb397cd8a..b4ac98b672ee9 100644 --- a/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx +++ b/src/plugins/visualize/public/application/utils/get_top_nav_config.tsx @@ -9,6 +9,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; +import { Capabilities } from 'src/core/public'; import { TopNavMenuData } from 'src/plugins/navigation/public'; import { VISUALIZE_EMBEDDABLE_TYPE, VisualizeInput } from '../../../../visualizations/public'; import { @@ -30,6 +31,14 @@ import { VisualizeConstants } from '../visualize_constants'; import { getEditBreadcrumbs } from './breadcrumbs'; import { EmbeddableStateTransfer } from '../../../../embeddable/public'; +interface VisualizeCapabilities { + createShortUrl: boolean; + delete: boolean; + save: boolean; + saveQuery: boolean; + show: boolean; +} + interface TopNavConfigParams { hasUnsavedChanges: boolean; setHasUnsavedChanges: (value: boolean) => void; @@ -45,6 +54,14 @@ interface TopNavConfigParams { embeddableId?: string; } +export const showPublicUrlSwitch = (anonymousUserCapabilities: Capabilities) => { + if (!anonymousUserCapabilities.visualize) return false; + + const visualize = (anonymousUserCapabilities.visualize as unknown) as VisualizeCapabilities; + + return !!visualize.show; +}; + export const getTopNavConfig = ( { hasUnsavedChanges, @@ -243,6 +260,7 @@ export const getTopNavConfig = ( title: savedVis?.title, }, isDirty: hasUnappliedChanges || hasUnsavedChanges, + showPublicUrlSwitch, }); } }, diff --git a/src/plugins/visualize/public/application/utils/get_visualize_list_item_link.test.ts b/src/plugins/visualize/public/application/utils/get_visualize_list_item_link.test.ts new file mode 100644 index 0000000000000..80fd1c8740f2c --- /dev/null +++ b/src/plugins/visualize/public/application/utils/get_visualize_list_item_link.test.ts @@ -0,0 +1,125 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ + +import { getVisualizeListItemLink } from './get_visualize_list_item_link'; +import { ApplicationStart } from 'kibana/public'; +import { createHashHistory } from 'history'; +import { createKbnUrlStateStorage } from '../../../../kibana_utils/public'; +import { esFilters } from '../../../../data/public'; +import { GLOBAL_STATE_STORAGE_KEY } from '../../../common/constants'; + +jest.mock('../../services', () => { + return { + getUISettings: () => ({ + get: jest.fn(), + }), + }; +}); + +const application = ({ + getUrlForApp: jest.fn((appId: string, options?: { path?: string; absolute?: boolean }) => { + return `/app/${appId}${options?.path}`; + }), +} as unknown) as ApplicationStart; + +const history = createHashHistory(); +const kbnUrlStateStorage = createKbnUrlStateStorage({ + history, + useHash: false, +}); +kbnUrlStateStorage.set(GLOBAL_STATE_STORAGE_KEY, { time: { from: 'now-7d', to: 'now' } }); + +describe('listing item link is correct for each app', () => { + test('creates a link to classic visualization if editApp is not defined', async () => { + const editUrl = 'edit/id'; + const url = getVisualizeListItemLink(application, kbnUrlStateStorage, undefined, editUrl); + expect(url).toMatchInlineSnapshot(`"/app/visualize#${editUrl}?_g=(time:(from:now-7d,to:now))"`); + }); + + test('creates a link for the app given if editApp is defined', async () => { + const editUrl = '#/edit/id'; + const editApp = 'lens'; + const url = getVisualizeListItemLink(application, kbnUrlStateStorage, editApp, editUrl); + expect(url).toMatchInlineSnapshot(`"/app/${editApp}${editUrl}?_g=(time:(from:now-7d,to:now))"`); + }); + + describe('when global time changes', () => { + beforeEach(() => { + kbnUrlStateStorage.set(GLOBAL_STATE_STORAGE_KEY, { + time: { + from: '2021-01-05T11:45:53.375Z', + to: '2021-01-21T11:46:00.990Z', + }, + }); + }); + + test('it propagates the correct time on the query', async () => { + const editUrl = '#/edit/id'; + const editApp = 'lens'; + const url = getVisualizeListItemLink(application, kbnUrlStateStorage, editApp, editUrl); + expect(url).toMatchInlineSnapshot( + `"/app/${editApp}${editUrl}?_g=(time:(from:'2021-01-05T11:45:53.375Z',to:'2021-01-21T11:46:00.990Z'))"` + ); + }); + }); + + describe('when global refreshInterval changes', () => { + beforeEach(() => { + kbnUrlStateStorage.set(GLOBAL_STATE_STORAGE_KEY, { + refreshInterval: { pause: false, value: 300 }, + }); + }); + + test('it propagates the refreshInterval on the query', async () => { + const editUrl = '#/edit/id'; + const editApp = 'lens'; + const url = getVisualizeListItemLink(application, kbnUrlStateStorage, editApp, editUrl); + expect(url).toMatchInlineSnapshot( + `"/app/${editApp}${editUrl}?_g=(refreshInterval:(pause:!f,value:300))"` + ); + }); + }); + + describe('when global filters change', () => { + beforeEach(() => { + const filters = [ + { + meta: { + alias: null, + disabled: false, + negate: false, + }, + query: { query: 'q1' }, + }, + { + meta: { + alias: null, + disabled: false, + negate: false, + }, + query: { query: 'q1' }, + $state: { + store: esFilters.FilterStateStore.GLOBAL_STATE, + }, + }, + ]; + kbnUrlStateStorage.set(GLOBAL_STATE_STORAGE_KEY, { + filters, + }); + }); + + test('propagates the filters on the query', async () => { + const editUrl = '#/edit/id'; + const editApp = 'lens'; + const url = getVisualizeListItemLink(application, kbnUrlStateStorage, editApp, editUrl); + expect(url).toMatchInlineSnapshot( + `"/app/${editApp}${editUrl}?_g=(filters:!((meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1)),('$state':(store:globalState),meta:(alias:!n,disabled:!f,negate:!f),query:(query:q1))))"` + ); + }); + }); +}); diff --git a/src/plugins/visualize/public/application/utils/get_visualize_list_item_link.ts b/src/plugins/visualize/public/application/utils/get_visualize_list_item_link.ts new file mode 100644 index 0000000000000..2ded3ce8c2745 --- /dev/null +++ b/src/plugins/visualize/public/application/utils/get_visualize_list_item_link.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 + * and the Server Side Public License, v 1; you may not use this file except in + * compliance with, at your election, the Elastic License or the Server Side + * Public License, v 1. + */ +import { ApplicationStart } from 'kibana/public'; +import { IKbnUrlStateStorage } from 'src/plugins/kibana_utils/public'; +import { QueryState } from '../../../../data/public'; +import { setStateToKbnUrl } from '../../../../kibana_utils/public'; +import { getUISettings } from '../../services'; +import { GLOBAL_STATE_STORAGE_KEY } from '../../../common/constants'; +import { APP_NAME } from '../visualize_constants'; + +export const getVisualizeListItemLink = ( + application: ApplicationStart, + kbnUrlStateStorage: IKbnUrlStateStorage, + editApp: string | undefined, + editUrl: string +) => { + // for visualizations the editApp is undefined + let url = application.getUrlForApp(editApp ?? APP_NAME, { + path: editApp ? editUrl : `#${editUrl}`, + }); + const useHash = getUISettings().get('state:storeInSessionStorage'); + const globalStateInUrl = kbnUrlStateStorage.get(GLOBAL_STATE_STORAGE_KEY) || {}; + + url = setStateToKbnUrl(GLOBAL_STATE_STORAGE_KEY, globalStateInUrl, { useHash }, url); + return url; +}; diff --git a/src/plugins/visualize/public/url_generator.ts b/src/plugins/visualize/public/url_generator.ts index 15f05106130de..57fa9b2ae4801 100644 --- a/src/plugins/visualize/public/url_generator.ts +++ b/src/plugins/visualize/public/url_generator.ts @@ -16,9 +16,7 @@ import { } from '../../data/public'; import { setStateToKbnUrl } from '../../kibana_utils/public'; import { UrlGeneratorsDefinition } from '../../share/public'; - -const STATE_STORAGE_KEY = '_a'; -const GLOBAL_STATE_STORAGE_KEY = '_g'; +import { STATE_STORAGE_KEY, GLOBAL_STATE_STORAGE_KEY } from '../common/constants'; export const VISUALIZE_APP_URL_GENERATOR = 'VISUALIZE_APP_URL_GENERATOR'; diff --git a/test/common/config.js b/test/common/config.js index 8a42e6c87b214..b6d12444b7017 100644 --- a/test/common/config.js +++ b/test/common/config.js @@ -61,6 +61,8 @@ export default function () { ...(!!process.env.CODE_COVERAGE ? [`--plugin-path=${path.join(__dirname, 'fixtures', 'plugins', 'coverage')}`] : []), + // Disable v2 migrations in tests for now + '--migrations.enableV2=false', ], }, services, diff --git a/test/functional/apps/dashboard/dashboard_save.ts b/test/functional/apps/dashboard/dashboard_save.ts index 27cbba7db393d..e36136cd45141 100644 --- a/test/functional/apps/dashboard/dashboard_save.ts +++ b/test/functional/apps/dashboard/dashboard_save.ts @@ -12,7 +12,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const PageObjects = getPageObjects(['dashboard', 'header']); const listingTable = getService('listingTable'); - describe('dashboard save', function describeIndexTests() { + // FLAKY: https://github.com/elastic/kibana/issues/89476 + describe.skip('dashboard save', function describeIndexTests() { this.tags('includeFirefox'); const dashboardName = 'Dashboard Save Test'; const dashboardNameEnterKey = 'Dashboard Save Test with Enter Key'; diff --git a/test/functional/apps/discover/_data_grid.ts b/test/functional/apps/discover/_data_grid.ts index 3bac05c5b18fc..1329e7657ad9c 100644 --- a/test/functional/apps/discover/_data_grid.ts +++ b/test/functional/apps/discover/_data_grid.ts @@ -38,7 +38,7 @@ export default function ({ const getTitles = async () => (await testSubjects.getVisibleText('dataGridHeader')).replace(/\s|\r?\n|\r/g, ' '); - expect(await getTitles()).to.be('Time (@timestamp) _source'); + expect(await getTitles()).to.be('Time (@timestamp) Document'); await PageObjects.discover.clickFieldListItemAdd('bytes'); expect(await getTitles()).to.be('Time (@timestamp) bytes'); @@ -50,7 +50,7 @@ export default function ({ expect(await getTitles()).to.be('Time (@timestamp) agent'); await PageObjects.discover.clickFieldListItemAdd('agent'); - expect(await getTitles()).to.be('Time (@timestamp) _source'); + expect(await getTitles()).to.be('Time (@timestamp) Document'); }); }); } diff --git a/test/functional/apps/discover/_data_grid_field_data.ts b/test/functional/apps/discover/_data_grid_field_data.ts index bdbaacc33c1bd..3eec84ad3d7c0 100644 --- a/test/functional/apps/discover/_data_grid_field_data.ts +++ b/test/functional/apps/discover/_data_grid_field_data.ts @@ -57,7 +57,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); it('doc view should show Time and _source columns', async function () { - const expectedHeader = 'Time (@timestamp) _source'; + const expectedHeader = 'Time (@timestamp) Document'; const DocHeader = await dataGrid.getHeaderFields(); expect(DocHeader.join(' ')).to.be(expectedHeader); }); diff --git a/test/functional/apps/discover/_discover.ts b/test/functional/apps/discover/_discover.ts index 1176dd6395d2c..bf0a027553832 100644 --- a/test/functional/apps/discover/_discover.ts +++ b/test/functional/apps/discover/_discover.ts @@ -227,7 +227,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { }); }); describe('usage of discover:searchOnPageLoad', () => { - it('should fetch data from ES initially when discover:searchOnPageLoad is false', async function () { + it('should not fetch data from ES initially when discover:searchOnPageLoad is false', async function () { await kibanaServer.uiSettings.replace({ 'discover:searchOnPageLoad': false }); await PageObjects.common.navigateToApp('discover'); await PageObjects.header.awaitKibanaChrome(); @@ -235,7 +235,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { expect(await PageObjects.discover.getNrOfFetches()).to.be(0); }); - it('should not fetch data from ES initially when discover:searchOnPageLoad is true', async function () { + it('should fetch data from ES initially when discover:searchOnPageLoad is true', async function () { await kibanaServer.uiSettings.replace({ 'discover:searchOnPageLoad': true }); await PageObjects.common.navigateToApp('discover'); await PageObjects.header.awaitKibanaChrome(); diff --git a/test/functional/apps/discover/_saved_queries.ts b/test/functional/apps/discover/_saved_queries.ts index 6e6c53ec04985..ec6c455ecc979 100644 --- a/test/functional/apps/discover/_saved_queries.ts +++ b/test/functional/apps/discover/_saved_queries.ts @@ -26,7 +26,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const savedQueryManagementComponent = getService('savedQueryManagementComponent'); const testSubjects = getService('testSubjects'); - describe('saved queries saved objects', function describeIndexTests() { + // FLAKY: https://github.com/elastic/kibana/issues/89477 + describe.skip('saved queries saved objects', function describeIndexTests() { before(async function () { log.debug('load kibana index with default index pattern'); await esArchiver.load('discover'); diff --git a/test/functional/apps/getting_started/_shakespeare.ts b/test/functional/apps/getting_started/_shakespeare.ts index 95abbf9fa8a78..5a891af0de93d 100644 --- a/test/functional/apps/getting_started/_shakespeare.ts +++ b/test/functional/apps/getting_started/_shakespeare.ts @@ -30,8 +30,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // https://www.elastic.co/guide/en/kibana/current/tutorial-load-dataset.html - // Failing: See https://github.com/elastic/kibana/issues/82206 - describe.skip('Shakespeare', function describeIndexTests() { + describe('Shakespeare', function describeIndexTests() { // index starts on the first "count" metric at 1 // Each new metric or aggregation added to a visualization gets the next index. // So to modify a metric or aggregation tests need to keep track of the diff --git a/test/functional/apps/home/_navigation.ts b/test/functional/apps/home/_navigation.ts index c90398fa84afc..12f97a5349419 100644 --- a/test/functional/apps/home/_navigation.ts +++ b/test/functional/apps/home/_navigation.ts @@ -15,7 +15,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const appsMenu = getService('appsMenu'); const esArchiver = getService('esArchiver'); - describe('Kibana browser back navigation should work', function describeIndexTests() { + // Failing: See https://github.com/elastic/kibana/issues/88826 + describe.skip('Kibana browser back navigation should work', function describeIndexTests() { before(async () => { await esArchiver.loadIfNeeded('discover'); await esArchiver.loadIfNeeded('logstash_functional'); diff --git a/test/functional/apps/home/_sample_data.ts b/test/functional/apps/home/_sample_data.ts index a9fe2026112b6..438dd6f8adce2 100644 --- a/test/functional/apps/home/_sample_data.ts +++ b/test/functional/apps/home/_sample_data.ts @@ -20,7 +20,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const dashboardExpect = getService('dashboardExpect'); const PageObjects = getPageObjects(['common', 'header', 'home', 'dashboard', 'timePicker']); - describe('sample data', function describeIndexTests() { + // Failing: See https://github.com/elastic/kibana/issues/89379 + describe.skip('sample data', function describeIndexTests() { before(async () => { await security.testUser.setRoles(['kibana_admin', 'kibana_sample_admin']); await PageObjects.common.navigateToUrl('home', '/tutorial_directory/sampleData', { diff --git a/test/functional/apps/management/_import_objects.ts b/test/functional/apps/management/_import_objects.ts index 07811c9c68e45..754406938e47b 100644 --- a/test/functional/apps/management/_import_objects.ts +++ b/test/functional/apps/management/_import_objects.ts @@ -23,7 +23,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const log = getService('log'); - describe('import objects', function describeIndexTests() { + // FLAKY: https://github.com/elastic/kibana/issues/89478 + describe.skip('import objects', function describeIndexTests() { describe('.ndjson file', () => { beforeEach(async function () { await kibanaServer.uiSettings.replace({}); diff --git a/test/functional/apps/management/_scripted_fields_preview.js b/test/functional/apps/management/_scripted_fields_preview.js index 104d41b7e2a75..46619b89dfc59 100644 --- a/test/functional/apps/management/_scripted_fields_preview.js +++ b/test/functional/apps/management/_scripted_fields_preview.js @@ -13,7 +13,8 @@ export default function ({ getService, getPageObjects }) { const PageObjects = getPageObjects(['settings']); const SCRIPTED_FIELD_NAME = 'myScriptedField'; - describe('scripted fields preview', () => { + // FLAKY: https://github.com/elastic/kibana/issues/89475 + describe.skip('scripted fields preview', () => { before(async function () { await browser.setWindowSize(1200, 800); await PageObjects.settings.createIndexPattern(); diff --git a/test/functional/apps/management/_test_huge_fields.js b/test/functional/apps/management/_test_huge_fields.js index ca95af8cb4205..2ab619276d2b9 100644 --- a/test/functional/apps/management/_test_huge_fields.js +++ b/test/functional/apps/management/_test_huge_fields.js @@ -13,7 +13,8 @@ export default function ({ getService, getPageObjects }) { const security = getService('security'); const PageObjects = getPageObjects(['common', 'home', 'settings']); - describe('test large number of fields', function () { + // Failing: See https://github.com/elastic/kibana/issues/89031 + describe.skip('test large number of fields', function () { this.tags(['skipCloud']); const EXPECTED_FIELD_COUNT = '10006'; diff --git a/test/functional/apps/visualize/_chart_types.ts b/test/functional/apps/visualize/_chart_types.ts index 55b68b7370148..69403f2090594 100644 --- a/test/functional/apps/visualize/_chart_types.ts +++ b/test/functional/apps/visualize/_chart_types.ts @@ -12,21 +12,17 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function ({ getService, getPageObjects }: FtrProviderContext) { - const deployment = getService('deployment'); const log = getService('log'); const PageObjects = getPageObjects(['visualize']); - let isOss = true; describe('chart types', function () { before(async function () { log.debug('navigateToApp visualize'); - isOss = await deployment.isOss(); await PageObjects.visualize.navigateToNewVisualization(); }); it('should show the promoted vis types for the first step', async function () { const expectedChartTypes = ['Custom visualization', 'Lens', 'Maps', 'TSVB']; - log.debug('oss= ' + isOss); // find all the chart types and make sure there all there const chartTypes = (await PageObjects.visualize.getPromotedVisTypes()).sort(); @@ -37,9 +33,8 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { it('should show the correct agg based chart types', async function () { await PageObjects.visualize.clickAggBasedVisualizations(); - let expectedChartTypes = [ + const expectedChartTypes = [ 'Area', - 'Coordinate Map', 'Data table', 'Gauge', 'Goal', @@ -48,21 +43,10 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'Line', 'Metric', 'Pie', - 'Region Map', 'Tag cloud', 'Timelion', 'Vertical bar', ]; - if (!isOss) { - expectedChartTypes = _.remove(expectedChartTypes, function (n) { - return n !== 'Coordinate Map'; - }); - expectedChartTypes = _.remove(expectedChartTypes, function (n) { - return n !== 'Region Map'; - }); - expectedChartTypes.sort(); - } - log.debug('oss= ' + isOss); // find all the chart types and make sure there all there const chartTypes = (await PageObjects.visualize.getChartTypes()).sort(); diff --git a/test/functional/apps/visualize/index.ts b/test/functional/apps/visualize/index.ts index 8dd2854419693..4170ada692e67 100644 --- a/test/functional/apps/visualize/index.ts +++ b/test/functional/apps/visualize/index.ts @@ -67,11 +67,15 @@ export default function ({ getService, loadTestFile }: FtrProviderContext) { this.tags('ciGroup9'); loadTestFile(require.resolve('./_embedding_chart')); - loadTestFile(require.resolve('./_chart_types')); loadTestFile(require.resolve('./_area_chart')); loadTestFile(require.resolve('./_data_table')); loadTestFile(require.resolve('./_data_table_nontimeindex')); loadTestFile(require.resolve('./_data_table_notimeindex_filters')); + + // this check is not needed when the CI doesn't run anymore for the OSS + if (!isOss) { + loadTestFile(require.resolve('./_chart_types')); + } }); describe('', function () { diff --git a/test/functional/page_objects/common_page.ts b/test/functional/page_objects/common_page.ts index 1149f1b100788..ed817b8b55e80 100644 --- a/test/functional/page_objects/common_page.ts +++ b/test/functional/page_objects/common_page.ts @@ -376,7 +376,7 @@ export function CommonPageProvider({ getService, getPageObjects }: FtrProviderCo } async closeToast() { - const toast = await find.byCssSelector('.euiToast', 2 * defaultFindTimeout); + const toast = await find.byCssSelector('.euiToast', 6 * defaultFindTimeout); await toast.moveMouseTo(); const title = await (await find.byCssSelector('.euiToastHeader__title')).getVisibleText(); diff --git a/test/plugin_functional/test_suites/data_plugin/session.ts b/test/plugin_functional/test_suites/data_plugin/session.ts index ac958ead321bc..5567958cfd878 100644 --- a/test/plugin_functional/test_suites/data_plugin/session.ts +++ b/test/plugin_functional/test_suites/data_plugin/session.ts @@ -42,10 +42,7 @@ export default function ({ getService, getPageObjects }: PluginFunctionalProvide await PageObjects.header.waitUntilLoadingHasFinished(); const sessionIds = await getSessionIds(); - // Discover calls destroy on index pattern change, which explicitly closes a session - expect(sessionIds.length).to.be(2); - expect(sessionIds[0].length).to.be(0); - expect(sessionIds[1].length).not.to.be(0); + expect(sessionIds.length).to.be(1); }); it('Starts on a refresh', async () => { diff --git a/test/scripts/checks/test_projects.sh b/test/scripts/checks/test_projects.sh index 56f15f6839e9d..be3fe4c4be9d0 100755 --- a/test/scripts/checks/test_projects.sh +++ b/test/scripts/checks/test_projects.sh @@ -3,4 +3,4 @@ source src/dev/ci_setup/setup_env.sh checks-reporter-with-killswitch "Test Projects" \ - yarn kbn run test --exclude kibana --oss --skip-kibana-plugins + yarn kbn run test --exclude kibana --oss --skip-kibana-plugins --skip-missing diff --git a/tsconfig.base.json b/tsconfig.base.json index 247813da51cfb..f8e07911e71ce 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -14,6 +14,8 @@ "strict": true, // save information about the project graph on disk "incremental": true, + // Do not check d.ts files by default + "skipLibCheck": true, // enables "core language features" "lib": [ "esnext", diff --git a/tsconfig.json b/tsconfig.json index b6742bffeab55..bdd4ba296d1c9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,10 +26,11 @@ "src/plugins/kibana_react/**/*", "src/plugins/kibana_usage_collection/**/*", "src/plugins/kibana_utils/**/*", - "src/plugins/lens_oss/**/*", "src/plugins/management/**/*", + "src/plugins/maps_legacy/**/*", "src/plugins/navigation/**/*", "src/plugins/newsfeed/**/*", + "src/plugins/region_map/**/*", "src/plugins/saved_objects/**/*", "src/plugins/saved_objects_management/**/*", "src/plugins/saved_objects_tagging_oss/**/*", @@ -38,6 +39,7 @@ "src/plugins/spaces_oss/**/*", "src/plugins/telemetry/**/*", "src/plugins/telemetry_collection_manager/**/*", + "src/plugins/tile_map/**/*", "src/plugins/timelion/**/*", "src/plugins/ui_actions/**/*", "src/plugins/url_forwarding/**/*", @@ -81,10 +83,11 @@ { "path": "./src/plugins/kibana_react/tsconfig.json" }, { "path": "./src/plugins/kibana_usage_collection/tsconfig.json" }, { "path": "./src/plugins/kibana_utils/tsconfig.json" }, - { "path": "./src/plugins/lens_oss/tsconfig.json" }, { "path": "./src/plugins/management/tsconfig.json" }, + { "path": "./src/plugins/maps_legacy/tsconfig.json" }, { "path": "./src/plugins/navigation/tsconfig.json" }, { "path": "./src/plugins/newsfeed/tsconfig.json" }, + { "path": "./src/plugins/region_map/tsconfig.json" }, { "path": "./src/plugins/saved_objects/tsconfig.json" }, { "path": "./src/plugins/saved_objects_management/tsconfig.json" }, { "path": "./src/plugins/saved_objects_tagging_oss/tsconfig.json" }, @@ -93,6 +96,7 @@ { "path": "./src/plugins/spaces_oss/tsconfig.json" }, { "path": "./src/plugins/telemetry/tsconfig.json" }, { "path": "./src/plugins/telemetry_collection_manager/tsconfig.json" }, + { "path": "./src/plugins/tile_map/tsconfig.json" }, { "path": "./src/plugins/timelion/tsconfig.json" }, { "path": "./src/plugins/ui_actions/tsconfig.json" }, { "path": "./src/plugins/url_forwarding/tsconfig.json" }, diff --git a/tsconfig.refs.json b/tsconfig.refs.json index 1bce19e2ee44a..211a50ec1a539 100644 --- a/tsconfig.refs.json +++ b/tsconfig.refs.json @@ -22,10 +22,11 @@ { "path": "./src/plugins/kibana_react/tsconfig.json" }, { "path": "./src/plugins/kibana_usage_collection/tsconfig.json" }, { "path": "./src/plugins/kibana_utils/tsconfig.json" }, - { "path": "./src/plugins/lens_oss/tsconfig.json" }, { "path": "./src/plugins/management/tsconfig.json" }, + { "path": "./src/plugins/maps_legacy/tsconfig.json" }, { "path": "./src/plugins/navigation/tsconfig.json" }, { "path": "./src/plugins/newsfeed/tsconfig.json" }, + { "path": "./src/plugins/region_map/tsconfig.json" }, { "path": "./src/plugins/saved_objects/tsconfig.json" }, { "path": "./src/plugins/saved_objects_management/tsconfig.json" }, { "path": "./src/plugins/saved_objects_tagging_oss/tsconfig.json" }, @@ -35,6 +36,7 @@ { "path": "./src/plugins/spaces_oss/tsconfig.json" }, { "path": "./src/plugins/telemetry/tsconfig.json" }, { "path": "./src/plugins/telemetry_collection_manager/tsconfig.json" }, + { "path": "./src/plugins/tile_map/tsconfig.json" }, { "path": "./src/plugins/timelion/tsconfig.json" }, { "path": "./src/plugins/ui_actions/tsconfig.json" }, { "path": "./src/plugins/url_forwarding/tsconfig.json" }, diff --git a/typings/@elastic/eui/index.d.ts b/typings/@elastic/eui/index.d.ts index 74f89608bc04f..e5cf7864a83b2 100644 --- a/typings/@elastic/eui/index.d.ts +++ b/typings/@elastic/eui/index.d.ts @@ -6,6 +6,12 @@ * Public License, v 1. */ -import { Direction } from '@elastic/eui/src/services/sort/sort_direction'; - // TODO: Remove once typescript definitions are in EUI + +declare module '@elastic/eui/lib/services' { + export const RIGHT_ALIGNMENT: any; +} + +declare module '@elastic/eui/lib/services/format' { + export const dateFormatAliases: any; +} diff --git a/typings/@elastic/eui/lib/services.d.ts b/typings/@elastic/eui/lib/services.d.ts deleted file mode 100644 index a667d111ceb72..0000000000000 --- a/typings/@elastic/eui/lib/services.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * and the Server Side Public License, v 1; you may not use this file except in - * compliance with, at your election, the Elastic License or the Server Side - * Public License, v 1. - */ - -export const RIGHT_ALIGNMENT: any; diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index bfac437f3500a..f95c4286b3f26 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -38,6 +38,7 @@ "xpack.maps": ["plugins/maps"], "xpack.ml": ["plugins/ml"], "xpack.monitoring": ["plugins/monitoring"], + "xpack.osquery": ["plugins/osquery"], "xpack.painlessLab": "plugins/painless_lab", "xpack.remoteClusters": "plugins/remote_clusters", "xpack.reporting": ["plugins/reporting"], diff --git a/x-pack/plugins/actions/server/actions_client.test.ts b/x-pack/plugins/actions/server/actions_client.test.ts index 8b6c25e1c3f24..cd97213a64dcc 100644 --- a/x-pack/plugins/actions/server/actions_client.test.ts +++ b/x-pack/plugins/actions/server/actions_client.test.ts @@ -402,6 +402,9 @@ describe('create()', () => { enabled: true, enabledActionTypes: ['some-not-ignored-action-type'], allowedHosts: ['*'], + preconfigured: {}, + proxyRejectUnauthorizedCertificates: true, + rejectUnauthorized: true, }); const localActionTypeRegistryParams = { diff --git a/x-pack/plugins/actions/server/actions_config.mock.ts b/x-pack/plugins/actions/server/actions_config.mock.ts index 67ab495fc9678..e403fd99fe985 100644 --- a/x-pack/plugins/actions/server/actions_config.mock.ts +++ b/x-pack/plugins/actions/server/actions_config.mock.ts @@ -14,6 +14,8 @@ const createActionsConfigMock = () => { ensureHostnameAllowed: jest.fn().mockReturnValue({}), ensureUriAllowed: jest.fn().mockReturnValue({}), ensureActionTypeEnabled: jest.fn().mockReturnValue({}), + isRejectUnauthorizedCertificatesEnabled: jest.fn().mockReturnValue(true), + getProxySettings: jest.fn().mockReturnValue(undefined), }; return mocked; }; diff --git a/x-pack/plugins/actions/server/actions_config.test.ts b/x-pack/plugins/actions/server/actions_config.test.ts index 56c58054ca799..c8b771b647e0d 100644 --- a/x-pack/plugins/actions/server/actions_config.test.ts +++ b/x-pack/plugins/actions/server/actions_config.test.ts @@ -4,22 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ActionsConfigType } from './types'; +import { ActionsConfig } from './config'; import { getActionsConfigurationUtilities, AllowedHosts, EnabledActionTypes, } from './actions_config'; -const DefaultActionsConfig: ActionsConfigType = { +const defaultActionsConfig: ActionsConfig = { enabled: false, allowedHosts: [], enabledActionTypes: [], + preconfigured: {}, + proxyRejectUnauthorizedCertificates: true, + rejectUnauthorized: true, }; describe('ensureUriAllowed', () => { test('returns true when "any" hostnames are allowed', () => { - const config: ActionsConfigType = { + const config: ActionsConfig = { + ...defaultActionsConfig, enabled: false, allowedHosts: [AllowedHosts.Any], enabledActionTypes: [], @@ -30,7 +34,7 @@ describe('ensureUriAllowed', () => { }); test('throws when the hostname in the requested uri is not in the allowedHosts', () => { - const config: ActionsConfigType = DefaultActionsConfig; + const config: ActionsConfig = defaultActionsConfig; expect(() => getActionsConfigurationUtilities(config).ensureUriAllowed('https://github.com/elastic/kibana') ).toThrowErrorMatchingInlineSnapshot( @@ -39,7 +43,7 @@ describe('ensureUriAllowed', () => { }); test('throws when the uri cannot be parsed as a valid URI', () => { - const config: ActionsConfigType = DefaultActionsConfig; + const config: ActionsConfig = defaultActionsConfig; expect(() => getActionsConfigurationUtilities(config).ensureUriAllowed('github.com/elastic') ).toThrowErrorMatchingInlineSnapshot( @@ -48,7 +52,8 @@ describe('ensureUriAllowed', () => { }); test('returns true when the hostname in the requested uri is in the allowedHosts', () => { - const config: ActionsConfigType = { + const config: ActionsConfig = { + ...defaultActionsConfig, enabled: false, allowedHosts: ['github.com'], enabledActionTypes: [], @@ -61,7 +66,8 @@ describe('ensureUriAllowed', () => { describe('ensureHostnameAllowed', () => { test('returns true when "any" hostnames are allowed', () => { - const config: ActionsConfigType = { + const config: ActionsConfig = { + ...defaultActionsConfig, enabled: false, allowedHosts: [AllowedHosts.Any], enabledActionTypes: [], @@ -72,7 +78,7 @@ describe('ensureHostnameAllowed', () => { }); test('throws when the hostname in the requested uri is not in the allowedHosts', () => { - const config: ActionsConfigType = DefaultActionsConfig; + const config: ActionsConfig = defaultActionsConfig; expect(() => getActionsConfigurationUtilities(config).ensureHostnameAllowed('github.com') ).toThrowErrorMatchingInlineSnapshot( @@ -81,7 +87,8 @@ describe('ensureHostnameAllowed', () => { }); test('returns true when the hostname in the requested uri is in the allowedHosts', () => { - const config: ActionsConfigType = { + const config: ActionsConfig = { + ...defaultActionsConfig, enabled: false, allowedHosts: ['github.com'], enabledActionTypes: [], @@ -94,7 +101,8 @@ describe('ensureHostnameAllowed', () => { describe('isUriAllowed', () => { test('returns true when "any" hostnames are allowed', () => { - const config: ActionsConfigType = { + const config: ActionsConfig = { + ...defaultActionsConfig, enabled: false, allowedHosts: [AllowedHosts.Any], enabledActionTypes: [], @@ -105,21 +113,22 @@ describe('isUriAllowed', () => { }); test('throws when the hostname in the requested uri is not in the allowedHosts', () => { - const config: ActionsConfigType = DefaultActionsConfig; + const config: ActionsConfig = defaultActionsConfig; expect( getActionsConfigurationUtilities(config).isUriAllowed('https://github.com/elastic/kibana') ).toEqual(false); }); test('throws when the uri cannot be parsed as a valid URI', () => { - const config: ActionsConfigType = DefaultActionsConfig; + const config: ActionsConfig = defaultActionsConfig; expect(getActionsConfigurationUtilities(config).isUriAllowed('github.com/elastic')).toEqual( false ); }); test('returns true when the hostname in the requested uri is in the allowedHosts', () => { - const config: ActionsConfigType = { + const config: ActionsConfig = { + ...defaultActionsConfig, enabled: false, allowedHosts: ['github.com'], enabledActionTypes: [], @@ -132,7 +141,8 @@ describe('isUriAllowed', () => { describe('isHostnameAllowed', () => { test('returns true when "any" hostnames are allowed', () => { - const config: ActionsConfigType = { + const config: ActionsConfig = { + ...defaultActionsConfig, enabled: false, allowedHosts: [AllowedHosts.Any], enabledActionTypes: [], @@ -141,12 +151,13 @@ describe('isHostnameAllowed', () => { }); test('throws when the hostname in the requested uri is not in the allowedHosts', () => { - const config: ActionsConfigType = DefaultActionsConfig; + const config: ActionsConfig = defaultActionsConfig; expect(getActionsConfigurationUtilities(config).isHostnameAllowed('github.com')).toEqual(false); }); test('returns true when the hostname in the requested uri is in the allowedHosts', () => { - const config: ActionsConfigType = { + const config: ActionsConfig = { + ...defaultActionsConfig, enabled: false, allowedHosts: ['github.com'], enabledActionTypes: [], @@ -157,7 +168,8 @@ describe('isHostnameAllowed', () => { describe('isActionTypeEnabled', () => { test('returns true when "any" actionTypes are allowed', () => { - const config: ActionsConfigType = { + const config: ActionsConfig = { + ...defaultActionsConfig, enabled: false, allowedHosts: [], enabledActionTypes: ['ignore', EnabledActionTypes.Any], @@ -166,7 +178,8 @@ describe('isActionTypeEnabled', () => { }); test('returns false when no actionType is allowed', () => { - const config: ActionsConfigType = { + const config: ActionsConfig = { + ...defaultActionsConfig, enabled: false, allowedHosts: [], enabledActionTypes: [], @@ -175,7 +188,8 @@ describe('isActionTypeEnabled', () => { }); test('returns false when the actionType is not in the enabled list', () => { - const config: ActionsConfigType = { + const config: ActionsConfig = { + ...defaultActionsConfig, enabled: false, allowedHosts: [], enabledActionTypes: ['foo'], @@ -184,7 +198,8 @@ describe('isActionTypeEnabled', () => { }); test('returns true when the actionType is in the enabled list', () => { - const config: ActionsConfigType = { + const config: ActionsConfig = { + ...defaultActionsConfig, enabled: false, allowedHosts: [], enabledActionTypes: ['ignore', 'foo'], @@ -195,7 +210,8 @@ describe('isActionTypeEnabled', () => { describe('ensureActionTypeEnabled', () => { test('does not throw when any actionType is allowed', () => { - const config: ActionsConfigType = { + const config: ActionsConfig = { + ...defaultActionsConfig, enabled: false, allowedHosts: [], enabledActionTypes: ['ignore', EnabledActionTypes.Any], @@ -204,7 +220,7 @@ describe('ensureActionTypeEnabled', () => { }); test('throws when no actionType is not allowed', () => { - const config: ActionsConfigType = DefaultActionsConfig; + const config: ActionsConfig = defaultActionsConfig; expect(() => getActionsConfigurationUtilities(config).ensureActionTypeEnabled('foo') ).toThrowErrorMatchingInlineSnapshot( @@ -213,7 +229,8 @@ describe('ensureActionTypeEnabled', () => { }); test('throws when actionType is not enabled', () => { - const config: ActionsConfigType = { + const config: ActionsConfig = { + ...defaultActionsConfig, enabled: false, allowedHosts: [], enabledActionTypes: ['ignore'], @@ -226,7 +243,8 @@ describe('ensureActionTypeEnabled', () => { }); test('does not throw when actionType is enabled', () => { - const config: ActionsConfigType = { + const config: ActionsConfig = { + ...defaultActionsConfig, enabled: false, allowedHosts: [], enabledActionTypes: ['ignore', 'foo'], diff --git a/x-pack/plugins/actions/server/actions_config.ts b/x-pack/plugins/actions/server/actions_config.ts index ebac80e70f4a8..396f59094a2d9 100644 --- a/x-pack/plugins/actions/server/actions_config.ts +++ b/x-pack/plugins/actions/server/actions_config.ts @@ -10,8 +10,9 @@ import url from 'url'; import { curry } from 'lodash'; import { pipe } from 'fp-ts/lib/pipeable'; -import { ActionsConfigType } from './types'; +import { ActionsConfig } from './config'; import { ActionTypeDisabledError } from './lib'; +import { ProxySettings } from './types'; export enum AllowedHosts { Any = '*', @@ -33,6 +34,8 @@ export interface ActionsConfigurationUtilities { ensureHostnameAllowed: (hostname: string) => void; ensureUriAllowed: (uri: string) => void; ensureActionTypeEnabled: (actionType: string) => void; + isRejectUnauthorizedCertificatesEnabled: () => boolean; + getProxySettings: () => undefined | ProxySettings; } function allowListErrorMessage(field: AllowListingField, value: string) { @@ -56,14 +59,14 @@ function disabledActionTypeErrorMessage(actionType: string) { }); } -function isAllowed({ allowedHosts }: ActionsConfigType, hostname: string | null): boolean { +function isAllowed({ allowedHosts }: ActionsConfig, hostname: string | null): boolean { const allowed = new Set(allowedHosts); if (allowed.has(AllowedHosts.Any)) return true; if (hostname && allowed.has(hostname)) return true; return false; } -function isHostnameAllowedInUri(config: ActionsConfigType, uri: string): boolean { +function isHostnameAllowedInUri(config: ActionsConfig, uri: string): boolean { return pipe( tryCatch(() => url.parse(uri)), map((parsedUrl) => parsedUrl.hostname), @@ -73,7 +76,7 @@ function isHostnameAllowedInUri(config: ActionsConfigType, uri: string): boolean } function isActionTypeEnabledInConfig( - { enabledActionTypes }: ActionsConfigType, + { enabledActionTypes }: ActionsConfig, actionType: string ): boolean { const enabled = new Set(enabledActionTypes); @@ -82,8 +85,20 @@ function isActionTypeEnabledInConfig( return false; } +function getProxySettingsFromConfig(config: ActionsConfig): undefined | ProxySettings { + if (!config.proxyUrl) { + return undefined; + } + + return { + proxyUrl: config.proxyUrl, + proxyHeaders: config.proxyHeaders, + proxyRejectUnauthorizedCertificates: config.proxyRejectUnauthorizedCertificates, + }; +} + export function getActionsConfigurationUtilities( - config: ActionsConfigType + config: ActionsConfig ): ActionsConfigurationUtilities { const isHostnameAllowed = curry(isAllowed)(config); const isUriAllowed = curry(isHostnameAllowedInUri)(config); @@ -92,6 +107,8 @@ export function getActionsConfigurationUtilities( isHostnameAllowed, isUriAllowed, isActionTypeEnabled, + getProxySettings: () => getProxySettingsFromConfig(config), + isRejectUnauthorizedCertificatesEnabled: () => config.rejectUnauthorized, ensureUriAllowed(uri: string) { if (!isUriAllowed(uri)) { throw new Error(allowListErrorMessage(AllowListingField.URL, uri)); diff --git a/x-pack/plugins/actions/server/builtin_action_types/email.test.ts b/x-pack/plugins/actions/server/builtin_action_types/email.test.ts index 91c71a78a8ee0..5d803e504593e 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/email.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/email.test.ts @@ -277,6 +277,16 @@ describe('execute()', () => { `); expect(sendEmailMock.mock.calls[0][1]).toMatchInlineSnapshot(` Object { + "configurationUtilities": Object { + "ensureActionTypeEnabled": [MockFunction], + "ensureHostnameAllowed": [MockFunction], + "ensureUriAllowed": [MockFunction], + "getProxySettings": [MockFunction], + "isActionTypeEnabled": [MockFunction], + "isHostnameAllowed": [MockFunction], + "isRejectUnauthorizedCertificatesEnabled": [MockFunction], + "isUriAllowed": [MockFunction], + }, "content": Object { "message": "a message to you @@ -286,7 +296,6 @@ describe('execute()', () => { "subject": "the subject", }, "hasAuth": true, - "proxySettings": undefined, "routing": Object { "bcc": Array [ "jimmy@example.com", @@ -327,6 +336,16 @@ describe('execute()', () => { await actionType.executor(customExecutorOptions); expect(sendEmailMock.mock.calls[0][1]).toMatchInlineSnapshot(` Object { + "configurationUtilities": Object { + "ensureActionTypeEnabled": [MockFunction], + "ensureHostnameAllowed": [MockFunction], + "ensureUriAllowed": [MockFunction], + "getProxySettings": [MockFunction], + "isActionTypeEnabled": [MockFunction], + "isHostnameAllowed": [MockFunction], + "isRejectUnauthorizedCertificatesEnabled": [MockFunction], + "isUriAllowed": [MockFunction], + }, "content": Object { "message": "a message to you @@ -336,7 +355,6 @@ describe('execute()', () => { "subject": "the subject", }, "hasAuth": false, - "proxySettings": undefined, "routing": Object { "bcc": Array [ "jimmy@example.com", diff --git a/x-pack/plugins/actions/server/builtin_action_types/email.ts b/x-pack/plugins/actions/server/builtin_action_types/email.ts index 4afbbb3a33615..b8a3467b27b54 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/email.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/email.ts @@ -156,7 +156,7 @@ export function getActionType(params: GetActionTypeParams): EmailActionType { params: ParamsSchema, }, renderParameterTemplates, - executor: curry(executor)({ logger, publicBaseUrl }), + executor: curry(executor)({ logger, publicBaseUrl, configurationUtilities }), }; } @@ -178,7 +178,12 @@ async function executor( { logger, publicBaseUrl, - }: { logger: GetActionTypeParams['logger']; publicBaseUrl: GetActionTypeParams['publicBaseUrl'] }, + configurationUtilities, + }: { + logger: GetActionTypeParams['logger']; + publicBaseUrl: GetActionTypeParams['publicBaseUrl']; + configurationUtilities: ActionsConfigurationUtilities; + }, execOptions: EmailActionTypeExecutorOptions ): Promise> { const actionId = execOptions.actionId; @@ -221,8 +226,8 @@ async function executor( subject: params.subject, message: `${params.message}${EMAIL_FOOTER_DIVIDER}${footerMessage}`, }, - proxySettings: execOptions.proxySettings, hasAuth: config.hasAuth, + configurationUtilities, }; let result; diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/index.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/index.ts index d701fad0e0c2f..6fdd1cb28d7bb 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/jira/index.ts @@ -72,13 +72,16 @@ export function getActionType( }), params: ExecutorParamsSchema, }, - executor: curry(executor)({ logger }), + executor: curry(executor)({ logger, configurationUtilities }), }; } // action executor async function executor( - { logger }: { logger: Logger }, + { + logger, + configurationUtilities, + }: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities }, execOptions: ActionTypeExecutorOptions< JiraPublicConfigurationType, JiraSecretConfigurationType, @@ -95,7 +98,7 @@ async function executor( secrets, }, logger, - execOptions.proxySettings + configurationUtilities ); if (!api[subAction]) { diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/service.test.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/service.test.ts index 7e8770ffbd629..aa33389303081 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/service.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/jira/service.test.ts @@ -11,6 +11,7 @@ import * as utils from '../lib/axios_utils'; import { ExternalService } from './types'; import { Logger } from '../../../../../../src/core/server'; import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; +import { actionsConfigMock } from '../../actions_config.mock'; const logger = loggingSystemMock.create().get() as jest.Mocked; interface ResponseError extends Error { @@ -28,6 +29,7 @@ jest.mock('../lib/axios_utils', () => { axios.create = jest.fn(() => axios); const requestMock = utils.request as jest.Mock; +const configurationUtilities = actionsConfigMock.create(); const issueTypesResponse = { data: { @@ -116,7 +118,8 @@ describe('Jira service', () => { config: { apiUrl: 'https://siem-kibana.atlassian.net/', projectKey: 'CK' }, secrets: { apiToken: 'token', email: 'elastic@elastic.com' }, }, - logger + logger, + configurationUtilities ); }); @@ -132,7 +135,8 @@ describe('Jira service', () => { config: { apiUrl: null, projectKey: 'CK' }, secrets: { apiToken: 'token', email: 'elastic@elastic.com' }, }, - logger + logger, + configurationUtilities ) ).toThrow(); }); @@ -144,7 +148,8 @@ describe('Jira service', () => { config: { apiUrl: 'test.com', projectKey: null }, secrets: { apiToken: 'token', email: 'elastic@elastic.com' }, }, - logger + logger, + configurationUtilities ) ).toThrow(); }); @@ -156,7 +161,8 @@ describe('Jira service', () => { config: { apiUrl: 'test.com' }, secrets: { apiToken: '', email: 'elastic@elastic.com' }, }, - logger + logger, + configurationUtilities ) ).toThrow(); }); @@ -168,7 +174,8 @@ describe('Jira service', () => { config: { apiUrl: 'test.com' }, secrets: { apiToken: '', email: undefined }, }, - logger + logger, + configurationUtilities ) ).toThrow(); }); @@ -193,6 +200,7 @@ describe('Jira service', () => { axios, url: 'https://siem-kibana.atlassian.net/rest/api/2/issue/1', logger, + configurationUtilities, }); }); @@ -293,6 +301,7 @@ describe('Jira service', () => { url: 'https://siem-kibana.atlassian.net/rest/api/2/issue', logger, method: 'post', + configurationUtilities, data: { fields: { summary: 'title', @@ -331,6 +340,7 @@ describe('Jira service', () => { url: 'https://siem-kibana.atlassian.net/rest/api/2/issue', logger, method: 'post', + configurationUtilities, data: { fields: { summary: 'title', @@ -424,6 +434,7 @@ describe('Jira service', () => { axios, logger, method: 'put', + configurationUtilities, url: 'https://siem-kibana.atlassian.net/rest/api/2/issue/1', data: { fields: { @@ -510,6 +521,7 @@ describe('Jira service', () => { axios, logger, method: 'post', + configurationUtilities, url: 'https://siem-kibana.atlassian.net/rest/api/2/issue/1/comment', data: { body: 'comment' }, }); @@ -568,6 +580,7 @@ describe('Jira service', () => { axios, logger, method: 'get', + configurationUtilities, url: 'https://siem-kibana.atlassian.net/rest/capabilities', }); }); @@ -642,6 +655,7 @@ describe('Jira service', () => { axios, logger, method: 'get', + configurationUtilities, url: 'https://siem-kibana.atlassian.net/rest/api/2/issue/createmeta?projectKeys=CK&expand=projects.issuetypes.fields', }); @@ -724,6 +738,7 @@ describe('Jira service', () => { axios, logger, method: 'get', + configurationUtilities, url: 'https://siem-kibana.atlassian.net/rest/api/2/issue/createmeta/CK/issuetypes', }); }); @@ -807,6 +822,7 @@ describe('Jira service', () => { axios, logger, method: 'get', + configurationUtilities, url: 'https://siem-kibana.atlassian.net/rest/api/2/issue/createmeta?projectKeys=CK&issuetypeIds=10006&expand=projects.issuetypes.fields', }); @@ -928,6 +944,7 @@ describe('Jira service', () => { axios, logger, method: 'get', + configurationUtilities, url: 'https://siem-kibana.atlassian.net/rest/api/2/issue/createmeta/CK/issuetypes/10006', }); }); @@ -988,6 +1005,7 @@ describe('Jira service', () => { axios, logger, method: 'get', + configurationUtilities, url: `https://siem-kibana.atlassian.net/rest/api/2/search?jql=project%3D%22CK%22%20and%20summary%20~%22Test%20title%22`, }); }); @@ -1032,6 +1050,7 @@ describe('Jira service', () => { axios, logger, method: 'get', + configurationUtilities, url: `https://siem-kibana.atlassian.net/rest/api/2/issue/RJ-107`, }); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/jira/service.ts b/x-pack/plugins/actions/server/builtin_action_types/jira/service.ts index f5e1b2e4411e3..791bfbaf5d69b 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/jira/service.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/jira/service.ts @@ -26,7 +26,7 @@ import { import * as i18n from './translations'; import { request, getErrorMessage } from '../lib/axios_utils'; -import { ProxySettings } from '../../types'; +import { ActionsConfigurationUtilities } from '../../actions_config'; const VERSION = '2'; const BASE_URL = `rest/api/${VERSION}`; @@ -39,7 +39,7 @@ const createMetaCapabilities = ['list-project-issuetypes', 'list-issuetype-field export const createExternalService = ( { config, secrets }: ExternalServiceCredentials, logger: Logger, - proxySettings?: ProxySettings + configurationUtilities: ActionsConfigurationUtilities ): ExternalService => { const { apiUrl: url, projectKey } = config as JiraPublicConfigurationType; const { apiToken, email } = secrets as JiraSecretConfigurationType; @@ -173,7 +173,7 @@ export const createExternalService = ( axios: axiosInstance, url: `${incidentUrl}/${id}`, logger, - proxySettings, + configurationUtilities, }); const { fields, ...rest } = res.data; @@ -222,7 +222,7 @@ export const createExternalService = ( data: { fields, }, - proxySettings, + configurationUtilities, }); const updatedIncident = await getIncident(res.data.id); @@ -263,7 +263,7 @@ export const createExternalService = ( url: `${incidentUrl}/${incidentId}`, logger, data: { fields }, - proxySettings, + configurationUtilities, }); const updatedIncident = await getIncident(incidentId as string); @@ -297,7 +297,7 @@ export const createExternalService = ( url: getCommentsURL(incidentId), logger, data: { body: comment.comment }, - proxySettings, + configurationUtilities, }); return { @@ -324,7 +324,7 @@ export const createExternalService = ( method: 'get', url: capabilitiesUrl, logger, - proxySettings, + configurationUtilities, }); return { ...res.data }; @@ -350,7 +350,7 @@ export const createExternalService = ( method: 'get', url: getIssueTypesOldAPIURL, logger, - proxySettings, + configurationUtilities, }); const issueTypes = res.data.projects[0]?.issuetypes ?? []; @@ -361,7 +361,7 @@ export const createExternalService = ( method: 'get', url: getIssueTypesUrl, logger, - proxySettings, + configurationUtilities, }); const issueTypes = res.data.values; @@ -389,7 +389,7 @@ export const createExternalService = ( method: 'get', url: createGetIssueTypeFieldsUrl(getIssueTypeFieldsOldAPIURL, issueTypeId), logger, - proxySettings, + configurationUtilities, }); const fields = res.data.projects[0]?.issuetypes[0]?.fields || {}; @@ -400,7 +400,7 @@ export const createExternalService = ( method: 'get', url: createGetIssueTypeFieldsUrl(getIssueTypeFieldsUrl, issueTypeId), logger, - proxySettings, + configurationUtilities, }); const fields = res.data.values.reduce( @@ -459,7 +459,7 @@ export const createExternalService = ( method: 'get', url: query, logger, - proxySettings, + configurationUtilities, }); return normalizeSearchResults(res.data?.issues ?? []); @@ -483,7 +483,7 @@ export const createExternalService = ( method: 'get', url: getIssueUrl, logger, - proxySettings, + configurationUtilities, }); return normalizeIssue(res.data ?? {}); diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.test.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.test.ts index e106b17ad223f..23e16b7463914 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.test.ts @@ -5,12 +5,15 @@ */ import axios from 'axios'; +import { Agent as HttpsAgent } from 'https'; import { Logger } from '../../../../../../src/core/server'; import { addTimeZoneToDate, request, patch, getErrorMessage } from './axios_utils'; import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; +import { actionsConfigMock } from '../../actions_config.mock'; import { getProxyAgents } from './get_proxy_agents'; const logger = loggingSystemMock.create().get() as jest.Mocked; +const configurationUtilities = actionsConfigMock.create(); jest.mock('axios'); const axiosMock = (axios as unknown) as jest.Mock; @@ -41,13 +44,14 @@ describe('request', () => { axios, url: '/test', logger, + configurationUtilities, }); expect(axiosMock).toHaveBeenCalledWith('/test', { method: 'get', data: {}, httpAgent: undefined, - httpsAgent: undefined, + httpsAgent: expect.any(HttpsAgent), proxy: false, }); expect(res).toEqual({ @@ -58,20 +62,17 @@ describe('request', () => { }); test('it have been called with proper proxy agent for a valid url', async () => { - const proxySettings = { + configurationUtilities.getProxySettings.mockReturnValue({ proxyRejectUnauthorizedCertificates: true, proxyUrl: 'https://localhost:1212', - }; - const { httpAgent, httpsAgent } = getProxyAgents(proxySettings, logger); + }); + const { httpAgent, httpsAgent } = getProxyAgents(configurationUtilities, logger); const res = await request({ axios, url: 'http://testProxy', logger, - proxySettings: { - proxyUrl: 'https://localhost:1212', - proxyRejectUnauthorizedCertificates: true, - }, + configurationUtilities, }); expect(axiosMock).toHaveBeenCalledWith('http://testProxy', { @@ -89,21 +90,22 @@ describe('request', () => { }); test('it have been called with proper proxy agent for an invalid url', async () => { + configurationUtilities.getProxySettings.mockReturnValue({ + proxyUrl: ':nope:', + proxyRejectUnauthorizedCertificates: false, + }); const res = await request({ axios, url: 'https://testProxy', logger, - proxySettings: { - proxyUrl: ':nope:', - proxyRejectUnauthorizedCertificates: false, - }, + configurationUtilities, }); expect(axiosMock).toHaveBeenCalledWith('https://testProxy', { method: 'get', data: {}, httpAgent: undefined, - httpsAgent: undefined, + httpsAgent: expect.any(HttpsAgent), proxy: false, }); expect(res).toEqual({ @@ -114,13 +116,20 @@ describe('request', () => { }); test('it fetch correctly', async () => { - const res = await request({ axios, url: '/test', method: 'post', logger, data: { id: '123' } }); + const res = await request({ + axios, + url: '/test', + method: 'post', + logger, + data: { id: '123' }, + configurationUtilities, + }); expect(axiosMock).toHaveBeenCalledWith('/test', { method: 'post', data: { id: '123' }, httpAgent: undefined, - httpsAgent: undefined, + httpsAgent: expect.any(HttpsAgent), proxy: false, }); expect(res).toEqual({ @@ -140,12 +149,12 @@ describe('patch', () => { }); test('it fetch correctly', async () => { - await patch({ axios, url: '/test', data: { id: '123' }, logger }); + await patch({ axios, url: '/test', data: { id: '123' }, logger, configurationUtilities }); expect(axiosMock).toHaveBeenCalledWith('/test', { method: 'patch', data: { id: '123' }, httpAgent: undefined, - httpsAgent: undefined, + httpsAgent: expect.any(HttpsAgent), proxy: false, }); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.ts index 78c6b91b57dc0..a70a452737dc6 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/axios_utils.ts @@ -6,8 +6,8 @@ import { AxiosInstance, Method, AxiosResponse, AxiosBasicCredentials } from 'axios'; import { Logger } from '../../../../../../src/core/server'; -import { ProxySettings } from '../../types'; import { getProxyAgents } from './get_proxy_agents'; +import { ActionsConfigurationUtilities } from '../../actions_config'; export const request = async ({ axios, @@ -15,7 +15,7 @@ export const request = async ({ logger, method = 'get', data, - proxySettings, + configurationUtilities, ...rest }: { axios: AxiosInstance; @@ -24,12 +24,12 @@ export const request = async ({ method?: Method; data?: T; params?: unknown; - proxySettings?: ProxySettings; + configurationUtilities: ActionsConfigurationUtilities; headers?: Record | null; validateStatus?: (status: number) => boolean; auth?: AxiosBasicCredentials; }): Promise => { - const { httpAgent, httpsAgent } = getProxyAgents(proxySettings, logger); + const { httpAgent, httpsAgent } = getProxyAgents(configurationUtilities, logger); return await axios(url, { ...rest, @@ -47,13 +47,13 @@ export const patch = async ({ url, data, logger, - proxySettings, + configurationUtilities, }: { axios: AxiosInstance; url: string; data: T; logger: Logger; - proxySettings?: ProxySettings; + configurationUtilities: ActionsConfigurationUtilities; }): Promise => { return request({ axios, @@ -61,7 +61,7 @@ export const patch = async ({ logger, method: 'patch', data, - proxySettings, + configurationUtilities, }); }; diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agents.test.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agents.test.ts index 759ca92968263..da2ad9bb3990d 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agents.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agents.test.ts @@ -4,41 +4,41 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Agent as HttpsAgent } from 'https'; import HttpProxyAgent from 'http-proxy-agent'; import { HttpsProxyAgent } from 'https-proxy-agent'; import { Logger } from '../../../../../../src/core/server'; import { getProxyAgents } from './get_proxy_agents'; import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; +import { actionsConfigMock } from '../../actions_config.mock'; const logger = loggingSystemMock.create().get() as jest.Mocked; describe('getProxyAgents', () => { + const configurationUtilities = actionsConfigMock.create(); + test('get agents for valid proxy URL', () => { - const { httpAgent, httpsAgent } = getProxyAgents( - { proxyUrl: 'https://someproxyhost', proxyRejectUnauthorizedCertificates: false }, - logger - ); + configurationUtilities.getProxySettings.mockReturnValue({ + proxyUrl: 'https://someproxyhost', + proxyRejectUnauthorizedCertificates: false, + }); + const { httpAgent, httpsAgent } = getProxyAgents(configurationUtilities, logger); expect(httpAgent instanceof HttpProxyAgent).toBeTruthy(); expect(httpsAgent instanceof HttpsProxyAgent).toBeTruthy(); }); - test('return undefined agents for invalid proxy URL', () => { - const { httpAgent, httpsAgent } = getProxyAgents( - { proxyUrl: ':nope: not a valid URL', proxyRejectUnauthorizedCertificates: false }, - logger - ); - expect(httpAgent).toBe(undefined); - expect(httpsAgent).toBe(undefined); - }); - - test('return undefined agents for null proxy options', () => { - const { httpAgent, httpsAgent } = getProxyAgents(null, logger); + test('return default agents for invalid proxy URL', () => { + configurationUtilities.getProxySettings.mockReturnValue({ + proxyUrl: ':nope: not a valid URL', + proxyRejectUnauthorizedCertificates: false, + }); + const { httpAgent, httpsAgent } = getProxyAgents(configurationUtilities, logger); expect(httpAgent).toBe(undefined); - expect(httpsAgent).toBe(undefined); + expect(httpsAgent instanceof HttpsAgent).toBeTruthy(); }); - test('return undefined agents for undefined proxy options', () => { - const { httpAgent, httpsAgent } = getProxyAgents(undefined, logger); + test('return default agents for undefined proxy options', () => { + const { httpAgent, httpsAgent } = getProxyAgents(configurationUtilities, logger); expect(httpAgent).toBe(undefined); - expect(httpsAgent).toBe(undefined); + expect(httpsAgent instanceof HttpsAgent).toBeTruthy(); }); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agents.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agents.ts index 45f962429ad2b..a49889570f4bf 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agents.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/get_proxy_agents.ts @@ -4,28 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Agent } from 'http'; +import { Agent as HttpAgent } from 'http'; +import { Agent as HttpsAgent } from 'https'; import HttpProxyAgent from 'http-proxy-agent'; import { HttpsProxyAgent } from 'https-proxy-agent'; import { Logger } from '../../../../../../src/core/server'; -import { ProxySettings } from '../../types'; +import { ActionsConfigurationUtilities } from '../../actions_config'; interface GetProxyAgentsResponse { - httpAgent: Agent | undefined; - httpsAgent: Agent | undefined; + httpAgent: HttpAgent | undefined; + httpsAgent: HttpsAgent | undefined; } export function getProxyAgents( - proxySettings: ProxySettings | undefined | null, + configurationUtilities: ActionsConfigurationUtilities, logger: Logger ): GetProxyAgentsResponse { - const undefinedResponse = { + const proxySettings = configurationUtilities.getProxySettings(); + const defaultResponse = { httpAgent: undefined, - httpsAgent: undefined, + httpsAgent: new HttpsAgent({ + rejectUnauthorized: configurationUtilities.isRejectUnauthorizedCertificatesEnabled(), + }), }; if (!proxySettings) { - return undefinedResponse; + return defaultResponse; } logger.debug(`Creating proxy agents for proxy: ${proxySettings.proxyUrl}`); @@ -34,7 +38,7 @@ export function getProxyAgents( proxyUrl = new URL(proxySettings.proxyUrl); } catch (err) { logger.warn(`invalid proxy URL "${proxySettings.proxyUrl}" ignored`); - return undefinedResponse; + return defaultResponse; } const httpAgent = new HttpProxyAgent(proxySettings.proxyUrl); @@ -45,8 +49,8 @@ export function getProxyAgents( headers: proxySettings.proxyHeaders, // do not fail on invalid certs if value is false rejectUnauthorized: proxySettings.proxyRejectUnauthorizedCertificates, - }) as unknown) as Agent; - // vsCode wasn't convinced HttpsProxyAgent is an http.Agent, so we convinced it + }) as unknown) as HttpsAgent; + // vsCode wasn't convinced HttpsProxyAgent is an https.Agent, so we convinced it return { httpAgent, httpsAgent }; } diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/post_pagerduty.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/post_pagerduty.ts index d78237beb98a1..51a4e3f857153 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/post_pagerduty.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/post_pagerduty.ts @@ -6,23 +6,24 @@ import axios, { AxiosResponse } from 'axios'; import { Logger } from '../../../../../../src/core/server'; -import { Services, ProxySettings } from '../../types'; +import { Services } from '../../types'; import { request } from './axios_utils'; +import { ActionsConfigurationUtilities } from '../../actions_config'; interface PostPagerdutyOptions { apiUrl: string; data: unknown; headers: Record; services: Services; - proxySettings?: ProxySettings; } // post an event to pagerduty export async function postPagerduty( options: PostPagerdutyOptions, - logger: Logger + logger: Logger, + configurationUtilities: ActionsConfigurationUtilities ): Promise { - const { apiUrl, data, headers, proxySettings } = options; + const { apiUrl, data, headers } = options; const axiosInstance = axios.create(); return await request({ @@ -31,8 +32,8 @@ export async function postPagerduty( method: 'post', logger, data, - proxySettings, headers, + configurationUtilities, validateStatus: () => true, }); } diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts index a1c4041628bd5..bd23aba618544 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.test.ts @@ -13,6 +13,7 @@ import { sendEmail } from './send_email'; import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; import nodemailer from 'nodemailer'; import { ProxySettings } from '../../types'; +import { actionsConfigMock } from '../../actions_config.mock'; const createTransportMock = nodemailer.createTransport as jest.Mock; const sendMailMockResult = { result: 'does not matter' }; @@ -136,7 +137,7 @@ describe('send_email module', () => { "port": 1025, "secure": false, "tls": Object { - "rejectUnauthorized": undefined, + "rejectUnauthorized": true, }, }, ] @@ -223,6 +224,10 @@ function getSendEmailOptions( { content = {}, routing = {}, transport = {} } = {}, proxySettings?: ProxySettings ) { + const configurationUtilities = actionsConfigMock.create(); + if (proxySettings) { + configurationUtilities.getProxySettings.mockReturnValue(proxySettings); + } return { content: { ...content, @@ -242,8 +247,8 @@ function getSendEmailOptions( user: 'elastic', password: 'changeme', }, - proxySettings, hasAuth: true, + configurationUtilities, }; } @@ -251,6 +256,10 @@ function getSendEmailOptionsNoAuth( { content = {}, routing = {}, transport = {} } = {}, proxySettings?: ProxySettings ) { + const configurationUtilities = actionsConfigMock.create(); + if (proxySettings) { + configurationUtilities.getProxySettings.mockReturnValue(proxySettings); + } return { content: { ...content, @@ -267,7 +276,7 @@ function getSendEmailOptionsNoAuth( transport: { ...transport, }, - proxySettings, hasAuth: false, + configurationUtilities, }; } diff --git a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts index f3cdf82bfe8cd..2ade4f9e9dcb4 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/lib/send_email.ts @@ -9,7 +9,7 @@ import nodemailer from 'nodemailer'; import { default as MarkdownIt } from 'markdown-it'; import { Logger } from '../../../../../../src/core/server'; -import { ProxySettings } from '../../types'; +import { ActionsConfigurationUtilities } from '../../actions_config'; // an email "service" which doesn't actually send, just returns what it would send export const JSON_TRANSPORT_SERVICE = '__json'; @@ -18,9 +18,8 @@ export interface SendEmailOptions { transport: Transport; routing: Routing; content: Content; - proxySettings?: ProxySettings; - rejectUnauthorized?: boolean; hasAuth: boolean; + configurationUtilities: ActionsConfigurationUtilities; } // config validation ensures either service is set or host/port are set @@ -47,12 +46,14 @@ export interface Content { // send an email export async function sendEmail(logger: Logger, options: SendEmailOptions): Promise { - const { transport, routing, content, proxySettings, rejectUnauthorized, hasAuth } = options; + const { transport, routing, content, configurationUtilities, hasAuth } = options; const { service, host, port, secure, user, password } = transport; const { from, to, cc, bcc } = routing; const { subject, message } = content; const transportConfig: Record = {}; + const proxySettings = configurationUtilities.getProxySettings(); + const rejectUnauthorized = configurationUtilities.isRejectUnauthorizedCertificatesEnabled(); if (hasAuth && user != null && password != null) { transportConfig.auth = { diff --git a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts index ccd25da2397bb..688e75aece43c 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/pagerduty.ts @@ -139,7 +139,7 @@ export function getActionType({ secrets: SecretsSchema, params: ParamsSchema, }, - executor: curry(executor)({ logger }), + executor: curry(executor)({ logger, configurationUtilities }), }; } @@ -166,7 +166,10 @@ function getPagerDutyApiUrl(config: ActionTypeConfigType): string { // action executor async function executor( - { logger }: { logger: Logger }, + { + logger, + configurationUtilities, + }: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities }, execOptions: PagerDutyActionTypeExecutorOptions ): Promise> { const actionId = execOptions.actionId; @@ -174,7 +177,6 @@ async function executor( const secrets = execOptions.secrets; const params = execOptions.params; const services = execOptions.services; - const proxySettings = execOptions.proxySettings; const apiUrl = getPagerDutyApiUrl(config); const headers = { @@ -185,7 +187,11 @@ async function executor( let response; try { - response = await postPagerduty({ apiUrl, data, headers, services, proxySettings }, logger); + response = await postPagerduty( + { apiUrl, data, headers, services }, + logger, + configurationUtilities + ); } catch (err) { const message = i18n.translate('xpack.actions.builtin.pagerduty.postingErrorMessage', { defaultMessage: 'error posting pagerduty event', diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts index fca99f81d62bd..a14a1c7d4c8af 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/index.ts @@ -63,13 +63,16 @@ export function getActionType( }), params: ExecutorParamsSchema, }, - executor: curry(executor)({ logger }), + executor: curry(executor)({ logger, configurationUtilities }), }; } // action executor async function executor( - { logger }: { logger: Logger }, + { + logger, + configurationUtilities, + }: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities }, execOptions: ActionTypeExecutorOptions< ResilientPublicConfigurationType, ResilientSecretConfigurationType, @@ -86,7 +89,7 @@ async function executor( secrets, }, logger, - execOptions.proxySettings + configurationUtilities ); if (!api[subAction]) { diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/service.test.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/service.test.ts index 97d8b64fb6535..326f0a6ed5f8b 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/resilient/service.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/service.test.ts @@ -12,6 +12,7 @@ import { ExternalService } from './types'; import { Logger } from '../../../../../../src/core/server'; import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; import { incidentTypes, resilientFields, severity } from './mocks'; +import { actionsConfigMock } from '../../actions_config.mock'; const logger = loggingSystemMock.create().get() as jest.Mocked; @@ -28,6 +29,7 @@ axios.create = jest.fn(() => axios); const requestMock = utils.request as jest.Mock; const now = Date.now; const TIMESTAMP = 1589391874472; +const configurationUtilities = actionsConfigMock.create(); // Incident update makes three calls to the API. // The function below mocks this calls. @@ -86,7 +88,8 @@ describe('IBM Resilient service', () => { config: { apiUrl: 'https://resilient.elastic.co/', orgId: '201' }, secrets: { apiKeyId: 'keyId', apiKeySecret: 'secret' }, }, - logger + logger, + configurationUtilities ); }); @@ -155,7 +158,8 @@ describe('IBM Resilient service', () => { config: { apiUrl: null, orgId: '201' }, secrets: { apiKeyId: 'token', apiKeySecret: 'secret' }, }, - logger + logger, + configurationUtilities ) ).toThrow(); }); @@ -167,7 +171,8 @@ describe('IBM Resilient service', () => { config: { apiUrl: 'test.com', orgId: null }, secrets: { apiKeyId: 'token', apiKeySecret: 'secret' }, }, - logger + logger, + configurationUtilities ) ).toThrow(); }); @@ -179,7 +184,8 @@ describe('IBM Resilient service', () => { config: { apiUrl: 'test.com', orgId: '201' }, secrets: { apiKeyId: '', apiKeySecret: 'secret' }, }, - logger + logger, + configurationUtilities ) ).toThrow(); }); @@ -191,7 +197,8 @@ describe('IBM Resilient service', () => { config: { apiUrl: 'test.com', orgId: '201' }, secrets: { apiKeyId: '', apiKeySecret: undefined }, }, - logger + logger, + configurationUtilities ) ).toThrow(); }); @@ -226,6 +233,7 @@ describe('IBM Resilient service', () => { params: { text_content_output_format: 'objects_convert', }, + configurationUtilities, }); }); @@ -294,6 +302,7 @@ describe('IBM Resilient service', () => { 'https://resilient.elastic.co/rest/orgs/201/incidents?text_content_output_format=objects_convert', logger, method: 'post', + configurationUtilities, data: { name: 'title', description: { @@ -367,6 +376,7 @@ describe('IBM Resilient service', () => { axios, logger, method: 'patch', + configurationUtilities, url: 'https://resilient.elastic.co/rest/orgs/201/incidents/1', data: { changes: [ @@ -480,7 +490,7 @@ describe('IBM Resilient service', () => { axios, logger, method: 'post', - proxySettings: undefined, + configurationUtilities, url: 'https://resilient.elastic.co/rest/orgs/201/incidents/1/comments', data: { text: { @@ -584,6 +594,7 @@ describe('IBM Resilient service', () => { expect(requestMock).toHaveBeenCalledWith({ axios, logger, + configurationUtilities, url: 'https://resilient.elastic.co/rest/orgs/201/types/incident/fields', }); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/resilient/service.ts b/x-pack/plugins/actions/server/builtin_action_types/resilient/service.ts index a13204f8bb1d8..ec31de4f2afd5 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/resilient/service.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/resilient/service.ts @@ -24,7 +24,7 @@ import { import * as i18n from './translations'; import { getErrorMessage, request } from '../lib/axios_utils'; -import { ProxySettings } from '../../types'; +import { ActionsConfigurationUtilities } from '../../actions_config'; const VIEW_INCIDENT_URL = `#incidents`; @@ -93,7 +93,7 @@ export const formatUpdateRequest = ({ export const createExternalService = ( { config, secrets }: ExternalServiceCredentials, logger: Logger, - proxySettings?: ProxySettings + configurationUtilities: ActionsConfigurationUtilities ): ExternalService => { const { apiUrl: url, orgId } = config as ResilientPublicConfigurationType; const { apiKeyId, apiKeySecret } = secrets as ResilientSecretConfigurationType; @@ -130,7 +130,7 @@ export const createExternalService = ( params: { text_content_output_format: 'objects_convert', }, - proxySettings, + configurationUtilities, }); return { ...res.data, description: res.data.description?.content ?? '' }; @@ -178,7 +178,7 @@ export const createExternalService = ( method: 'post', logger, data, - proxySettings, + configurationUtilities, }); return { @@ -208,7 +208,7 @@ export const createExternalService = ( url: `${incidentUrl}/${incidentId}`, logger, data, - proxySettings, + configurationUtilities, }); if (!res.data.success) { @@ -241,7 +241,7 @@ export const createExternalService = ( url: getCommentsURL(incidentId), logger, data: { text: { format: 'text', content: comment.comment } }, - proxySettings, + configurationUtilities, }); return { @@ -266,7 +266,7 @@ export const createExternalService = ( method: 'get', url: incidentTypesUrl, logger, - proxySettings, + configurationUtilities, }); const incidentTypes = res.data?.values ?? []; @@ -288,7 +288,7 @@ export const createExternalService = ( method: 'get', url: severityUrl, logger, - proxySettings, + configurationUtilities, }); const incidentTypes = res.data?.values ?? []; @@ -309,7 +309,7 @@ export const createExternalService = ( axios: axiosInstance, url: incidentFieldsUrl, logger, - proxySettings, + configurationUtilities, }); return res.data ?? []; } catch (error) { diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts index 1f75d439200e3..107d86f111deb 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/index.ts @@ -60,14 +60,17 @@ export function getActionType( }), params: ExecutorParamsSchema, }, - executor: curry(executor)({ logger }), + executor: curry(executor)({ logger, configurationUtilities }), }; } // action executor const supportedSubActions: string[] = ['getFields', 'pushToService']; async function executor( - { logger }: { logger: Logger }, + { + logger, + configurationUtilities, + }: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities }, execOptions: ActionTypeExecutorOptions< ServiceNowPublicConfigurationType, ServiceNowSecretConfigurationType, @@ -84,7 +87,7 @@ async function executor( secrets, }, logger, - execOptions.proxySettings + configurationUtilities ); if (!api[subAction]) { diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts index 1a6412f9ceb5b..4ef0e7da166e6 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.test.ts @@ -11,6 +11,7 @@ import * as utils from '../lib/axios_utils'; import { ExternalService } from './types'; import { Logger } from '../../../../../../src/core/server'; import { loggingSystemMock } from '../../../../../../src/core/server/mocks'; +import { actionsConfigMock } from '../../actions_config.mock'; import { serviceNowCommonFields } from './mocks'; const logger = loggingSystemMock.create().get() as jest.Mocked; @@ -27,6 +28,7 @@ jest.mock('../lib/axios_utils', () => { axios.create = jest.fn(() => axios); const requestMock = utils.request as jest.Mock; const patchMock = utils.patch as jest.Mock; +const configurationUtilities = actionsConfigMock.create(); describe('ServiceNow service', () => { let service: ExternalService; @@ -39,7 +41,8 @@ describe('ServiceNow service', () => { config: { apiUrl: 'https://dev102283.service-now.com/' }, secrets: { username: 'admin', password: 'admin' }, }, - logger + logger, + configurationUtilities ); }); @@ -55,7 +58,8 @@ describe('ServiceNow service', () => { config: { apiUrl: null }, secrets: { username: 'admin', password: 'admin' }, }, - logger + logger, + configurationUtilities ) ).toThrow(); }); @@ -67,7 +71,8 @@ describe('ServiceNow service', () => { config: { apiUrl: 'test.com' }, secrets: { username: '', password: 'admin' }, }, - logger + logger, + configurationUtilities ) ).toThrow(); }); @@ -79,7 +84,8 @@ describe('ServiceNow service', () => { config: { apiUrl: 'test.com' }, secrets: { username: '', password: undefined }, }, - logger + logger, + configurationUtilities ) ).toThrow(); }); @@ -103,6 +109,7 @@ describe('ServiceNow service', () => { expect(requestMock).toHaveBeenCalledWith({ axios, logger, + configurationUtilities, url: 'https://dev102283.service-now.com/api/now/v2/table/incident/1', }); }); @@ -147,6 +154,7 @@ describe('ServiceNow service', () => { expect(requestMock).toHaveBeenCalledWith({ axios, logger, + configurationUtilities, url: 'https://dev102283.service-now.com/api/now/v2/table/incident', method: 'post', data: { short_description: 'title', description: 'desc' }, @@ -200,6 +208,7 @@ describe('ServiceNow service', () => { expect(patchMock).toHaveBeenCalledWith({ axios, logger, + configurationUtilities, url: 'https://dev102283.service-now.com/api/now/v2/table/incident/1', data: { short_description: 'title', description: 'desc' }, }); @@ -248,6 +257,7 @@ describe('ServiceNow service', () => { expect(requestMock).toHaveBeenCalledWith({ axios, logger, + configurationUtilities, url: 'https://dev102283.service-now.com/api/now/v2/table/sys_dictionary?sysparm_query=name=task^internal_type=string&active=true&array=false&read_only=false&sysparm_fields=max_length,element,column_label,mandatory', }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts index 96faf6d338b90..108fe06bcbcaa 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/servicenow/service.ts @@ -12,7 +12,7 @@ import * as i18n from './translations'; import { Logger } from '../../../../../../src/core/server'; import { ServiceNowPublicConfigurationType, ServiceNowSecretConfigurationType } from './types'; import { request, getErrorMessage, addTimeZoneToDate, patch } from '../lib/axios_utils'; -import { ProxySettings } from '../../types'; +import { ActionsConfigurationUtilities } from '../../actions_config'; const API_VERSION = 'v2'; const INCIDENT_URL = `api/now/${API_VERSION}/table/incident`; @@ -24,7 +24,7 @@ const VIEW_INCIDENT_URL = `nav_to.do?uri=incident.do?sys_id=`; export const createExternalService = ( { config, secrets }: ExternalServiceCredentials, logger: Logger, - proxySettings?: ProxySettings + configurationUtilities: ActionsConfigurationUtilities ): ExternalService => { const { apiUrl: url } = config as ServiceNowPublicConfigurationType; const { username, password } = secrets as ServiceNowSecretConfigurationType; @@ -58,7 +58,7 @@ export const createExternalService = ( axios: axiosInstance, url: `${incidentUrl}/${id}`, logger, - proxySettings, + configurationUtilities, }); checkInstance(res); return { ...res.data.result }; @@ -75,8 +75,8 @@ export const createExternalService = ( axios: axiosInstance, url: incidentUrl, logger, - proxySettings, params, + configurationUtilities, }); checkInstance(res); return res.data.result.length > 0 ? { ...res.data.result } : undefined; @@ -93,9 +93,9 @@ export const createExternalService = ( axios: axiosInstance, url: `${incidentUrl}`, logger, - proxySettings, method: 'post', data: { ...(incident as Record) }, + configurationUtilities, }); checkInstance(res); return { @@ -118,7 +118,7 @@ export const createExternalService = ( url: `${incidentUrl}/${incidentId}`, logger, data: { ...(incident as Record) }, - proxySettings, + configurationUtilities, }); checkInstance(res); return { @@ -143,7 +143,7 @@ export const createExternalService = ( axios: axiosInstance, url: fieldsUrl, logger, - proxySettings, + configurationUtilities, }); checkInstance(res); return res.data.result.length > 0 ? res.data.result : []; diff --git a/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts b/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts index e73f8d91b0847..cfac23e624a04 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/slack.test.ts @@ -165,10 +165,6 @@ describe('execute()', () => { config: {}, secrets: { webhookUrl: 'http://example.com' }, params: { message: 'this invocation should succeed' }, - proxySettings: { - proxyUrl: 'https://someproxyhost', - proxyRejectUnauthorizedCertificates: false, - }, }); expect(response).toMatchInlineSnapshot(` Object { @@ -194,9 +190,14 @@ describe('execute()', () => { }); test('calls the mock executor with success proxy', async () => { + const configurationUtilities = actionsConfigMock.create(); + configurationUtilities.getProxySettings.mockReturnValue({ + proxyUrl: 'https://someproxyhost', + proxyRejectUnauthorizedCertificates: false, + }); const actionTypeProxy = getActionType({ logger: mockedLogger, - configurationUtilities: actionsConfigMock.create(), + configurationUtilities, }); await actionTypeProxy.executor({ actionId: 'some-id', @@ -204,10 +205,6 @@ describe('execute()', () => { config: {}, secrets: { webhookUrl: 'http://example.com' }, params: { message: 'this invocation should succeed' }, - proxySettings: { - proxyUrl: 'https://someproxyhost', - proxyRejectUnauthorizedCertificates: false, - }, }); expect(mockedLogger.debug).toHaveBeenCalledWith( 'IncomingWebhook was called with proxyUrl https://someproxyhost' diff --git a/x-pack/plugins/actions/server/builtin_action_types/slack.ts b/x-pack/plugins/actions/server/builtin_action_types/slack.ts index 02026eb729727..5d2c5a24b3edd 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/slack.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/slack.ts @@ -6,7 +6,6 @@ import { URL } from 'url'; import { curry } from 'lodash'; -import { Agent } from 'http'; import { i18n } from '@kbn/i18n'; import { schema, TypeOf } from '@kbn/config-schema'; import { IncomingWebhook, IncomingWebhookResult } from '@slack/webhook'; @@ -56,7 +55,7 @@ export const ActionTypeId = '.slack'; export function getActionType({ logger, configurationUtilities, - executor = curry(slackExecutor)({ logger }), + executor = curry(slackExecutor)({ logger, configurationUtilities }), }: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities; @@ -116,7 +115,10 @@ function validateActionTypeConfig( // action executor async function slackExecutor( - { logger }: { logger: Logger }, + { + logger, + configurationUtilities, + }: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities }, execOptions: SlackActionTypeExecutorOptions ): Promise> { const actionId = execOptions.actionId; @@ -126,15 +128,15 @@ async function slackExecutor( let result: IncomingWebhookResult; const { webhookUrl } = secrets; const { message } = params; + const proxySettings = configurationUtilities.getProxySettings(); - let httpProxyAgent: Agent | undefined; - if (execOptions.proxySettings) { - const httpProxyAgents = getProxyAgents(execOptions.proxySettings, logger); - httpProxyAgent = webhookUrl.toLowerCase().startsWith('https') - ? httpProxyAgents.httpsAgent - : httpProxyAgents.httpAgent; + const proxyAgents = getProxyAgents(configurationUtilities, logger); + const httpProxyAgent = webhookUrl.toLowerCase().startsWith('https') + ? proxyAgents.httpsAgent + : proxyAgents.httpAgent; - logger.debug(`IncomingWebhook was called with proxyUrl ${execOptions.proxySettings.proxyUrl}`); + if (proxySettings) { + logger.debug(`IncomingWebhook was called with proxyUrl ${proxySettings.proxyUrl}`); } try { diff --git a/x-pack/plugins/actions/server/builtin_action_types/teams.test.ts b/x-pack/plugins/actions/server/builtin_action_types/teams.test.ts index a9fce2f0a9ebf..4ca25013e9691 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/teams.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/teams.test.ts @@ -160,38 +160,47 @@ describe('execute()', () => { params: { message: 'this invocation should succeed' }, }); expect(requestMock.mock.calls[0][0]).toMatchInlineSnapshot(` - Object { - "axios": undefined, - "data": Object { - "text": "this invocation should succeed", - }, - "logger": Object { - "context": Array [], - "debug": [MockFunction] { - "calls": Array [ - Array [ - "response from teams action \\"some-id\\": [HTTP 200] ", - ], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - ], + Object { + "axios": undefined, + "configurationUtilities": Object { + "ensureActionTypeEnabled": [MockFunction], + "ensureHostnameAllowed": [MockFunction], + "ensureUriAllowed": [MockFunction], + "getProxySettings": [MockFunction], + "isActionTypeEnabled": [MockFunction], + "isHostnameAllowed": [MockFunction], + "isRejectUnauthorizedCertificatesEnabled": [MockFunction], + "isUriAllowed": [MockFunction], + }, + "data": Object { + "text": "this invocation should succeed", + }, + "logger": Object { + "context": Array [], + "debug": [MockFunction] { + "calls": Array [ + Array [ + "response from teams action \\"some-id\\": [HTTP 200] ", + ], + ], + "results": Array [ + Object { + "type": "return", + "value": undefined, }, - "error": [MockFunction], - "fatal": [MockFunction], - "get": [MockFunction], - "info": [MockFunction], - "log": [MockFunction], - "trace": [MockFunction], - "warn": [MockFunction], - }, - "method": "post", - "proxySettings": undefined, - "url": "http://example.com", - } + ], + }, + "error": [MockFunction], + "fatal": [MockFunction], + "get": [MockFunction], + "info": [MockFunction], + "log": [MockFunction], + "trace": [MockFunction], + "warn": [MockFunction], + }, + "method": "post", + "url": "http://example.com", + } `); expect(response).toMatchInlineSnapshot(` Object { @@ -211,47 +220,49 @@ describe('execute()', () => { config: {}, secrets: { webhookUrl: 'http://example.com' }, params: { message: 'this invocation should succeed' }, - proxySettings: { - proxyUrl: 'https://someproxyhost', - proxyRejectUnauthorizedCertificates: false, - }, }); expect(requestMock.mock.calls[0][0]).toMatchInlineSnapshot(` - Object { - "axios": undefined, - "data": Object { - "text": "this invocation should succeed", - }, - "logger": Object { - "context": Array [], - "debug": [MockFunction] { - "calls": Array [ - Array [ - "response from teams action \\"some-id\\": [HTTP 200] ", - ], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - ], + Object { + "axios": undefined, + "configurationUtilities": Object { + "ensureActionTypeEnabled": [MockFunction], + "ensureHostnameAllowed": [MockFunction], + "ensureUriAllowed": [MockFunction], + "getProxySettings": [MockFunction], + "isActionTypeEnabled": [MockFunction], + "isHostnameAllowed": [MockFunction], + "isRejectUnauthorizedCertificatesEnabled": [MockFunction], + "isUriAllowed": [MockFunction], + }, + "data": Object { + "text": "this invocation should succeed", + }, + "logger": Object { + "context": Array [], + "debug": [MockFunction] { + "calls": Array [ + Array [ + "response from teams action \\"some-id\\": [HTTP 200] ", + ], + ], + "results": Array [ + Object { + "type": "return", + "value": undefined, }, - "error": [MockFunction], - "fatal": [MockFunction], - "get": [MockFunction], - "info": [MockFunction], - "log": [MockFunction], - "trace": [MockFunction], - "warn": [MockFunction], - }, - "method": "post", - "proxySettings": Object { - "proxyRejectUnauthorizedCertificates": false, - "proxyUrl": "https://someproxyhost", - }, - "url": "http://example.com", - } + ], + }, + "error": [MockFunction], + "fatal": [MockFunction], + "get": [MockFunction], + "info": [MockFunction], + "log": [MockFunction], + "trace": [MockFunction], + "warn": [MockFunction], + }, + "method": "post", + "url": "http://example.com", + } `); expect(response).toMatchInlineSnapshot(` Object { diff --git a/x-pack/plugins/actions/server/builtin_action_types/teams.ts b/x-pack/plugins/actions/server/builtin_action_types/teams.ts index 088e30db4e3ce..857110d2f53c4 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/teams.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/teams.ts @@ -63,7 +63,7 @@ export function getActionType({ }), params: ParamsSchema, }, - executor: curry(teamsExecutor)({ logger }), + executor: curry(teamsExecutor)({ logger, configurationUtilities }), }; } @@ -95,7 +95,10 @@ function validateActionTypeConfig( // action executor async function teamsExecutor( - { logger }: { logger: Logger }, + { + logger, + configurationUtilities, + }: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities }, execOptions: TeamsActionTypeExecutorOptions ): Promise> { const actionId = execOptions.actionId; @@ -114,7 +117,7 @@ async function teamsExecutor( url: webhookUrl, logger, data, - proxySettings: execOptions.proxySettings, + configurationUtilities, }) ); diff --git a/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts b/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts index dbbd2a029caa9..80614e6b1336d 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/webhook.test.ts @@ -279,43 +279,52 @@ describe('execute()', () => { }); expect(requestMock.mock.calls[0][0]).toMatchInlineSnapshot(` - Object { - "auth": Object { - "password": "123", - "username": "abc", - }, - "axios": undefined, - "data": "some data", - "headers": Object { - "aheader": "a value", - }, - "logger": Object { - "context": Array [], - "debug": [MockFunction] { - "calls": Array [ - Array [ - "response from webhook action \\"some-id\\": [HTTP 200] ", - ], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - ], + Object { + "auth": Object { + "password": "123", + "username": "abc", + }, + "axios": undefined, + "configurationUtilities": Object { + "ensureActionTypeEnabled": [MockFunction], + "ensureHostnameAllowed": [MockFunction], + "ensureUriAllowed": [MockFunction], + "getProxySettings": [MockFunction], + "isActionTypeEnabled": [MockFunction], + "isHostnameAllowed": [MockFunction], + "isRejectUnauthorizedCertificatesEnabled": [MockFunction], + "isUriAllowed": [MockFunction], + }, + "data": "some data", + "headers": Object { + "aheader": "a value", + }, + "logger": Object { + "context": Array [], + "debug": [MockFunction] { + "calls": Array [ + Array [ + "response from webhook action \\"some-id\\": [HTTP 200] ", + ], + ], + "results": Array [ + Object { + "type": "return", + "value": undefined, }, - "error": [MockFunction], - "fatal": [MockFunction], - "get": [MockFunction], - "info": [MockFunction], - "log": [MockFunction], - "trace": [MockFunction], - "warn": [MockFunction], - }, - "method": "post", - "proxySettings": undefined, - "url": "https://abc.def/my-webhook", - } + ], + }, + "error": [MockFunction], + "fatal": [MockFunction], + "get": [MockFunction], + "info": [MockFunction], + "log": [MockFunction], + "trace": [MockFunction], + "warn": [MockFunction], + }, + "method": "post", + "url": "https://abc.def/my-webhook", + } `); }); @@ -338,39 +347,48 @@ describe('execute()', () => { }); expect(requestMock.mock.calls[0][0]).toMatchInlineSnapshot(` - Object { - "axios": undefined, - "data": "some data", - "headers": Object { - "aheader": "a value", - }, - "logger": Object { - "context": Array [], - "debug": [MockFunction] { - "calls": Array [ - Array [ - "response from webhook action \\"some-id\\": [HTTP 200] ", - ], - ], - "results": Array [ - Object { - "type": "return", - "value": undefined, - }, - ], + Object { + "axios": undefined, + "configurationUtilities": Object { + "ensureActionTypeEnabled": [MockFunction], + "ensureHostnameAllowed": [MockFunction], + "ensureUriAllowed": [MockFunction], + "getProxySettings": [MockFunction], + "isActionTypeEnabled": [MockFunction], + "isHostnameAllowed": [MockFunction], + "isRejectUnauthorizedCertificatesEnabled": [MockFunction], + "isUriAllowed": [MockFunction], + }, + "data": "some data", + "headers": Object { + "aheader": "a value", + }, + "logger": Object { + "context": Array [], + "debug": [MockFunction] { + "calls": Array [ + Array [ + "response from webhook action \\"some-id\\": [HTTP 200] ", + ], + ], + "results": Array [ + Object { + "type": "return", + "value": undefined, }, - "error": [MockFunction], - "fatal": [MockFunction], - "get": [MockFunction], - "info": [MockFunction], - "log": [MockFunction], - "trace": [MockFunction], - "warn": [MockFunction], - }, - "method": "post", - "proxySettings": undefined, - "url": "https://abc.def/my-webhook", - } + ], + }, + "error": [MockFunction], + "fatal": [MockFunction], + "get": [MockFunction], + "info": [MockFunction], + "log": [MockFunction], + "trace": [MockFunction], + "warn": [MockFunction], + }, + "method": "post", + "url": "https://abc.def/my-webhook", + } `); }); diff --git a/x-pack/plugins/actions/server/builtin_action_types/webhook.ts b/x-pack/plugins/actions/server/builtin_action_types/webhook.ts index fa6d2663c94ab..76063deee0f5e 100644 --- a/x-pack/plugins/actions/server/builtin_action_types/webhook.ts +++ b/x-pack/plugins/actions/server/builtin_action_types/webhook.ts @@ -94,7 +94,7 @@ export function getActionType({ params: ParamsSchema, }, renderParameterTemplates, - executor: curry(executor)({ logger }), + executor: curry(executor)({ logger, configurationUtilities }), }; } @@ -138,7 +138,10 @@ function validateActionTypeConfig( // action executor export async function executor( - { logger }: { logger: Logger }, + { + logger, + configurationUtilities, + }: { logger: Logger; configurationUtilities: ActionsConfigurationUtilities }, execOptions: WebhookActionTypeExecutorOptions ): Promise> { const actionId = execOptions.actionId; @@ -162,7 +165,7 @@ export async function executor( ...basicAuth, headers, data, - proxySettings: execOptions.proxySettings, + configurationUtilities, }) ); @@ -202,7 +205,7 @@ export async function executor( ); } return errorResultInvalid(actionId, message); - } else if (error.isAxiosError) { + } else if (error.code) { const message = `[${error.code}] ${error.message}`; logger.error(`error on ${actionId} webhook event: ${message}`); return errorResultRequestFailed(actionId, message); diff --git a/x-pack/plugins/actions/server/index.ts b/x-pack/plugins/actions/server/index.ts index 6c4857bff4e81..4e59dfd099811 100644 --- a/x-pack/plugins/actions/server/index.ts +++ b/x-pack/plugins/actions/server/index.ts @@ -6,10 +6,9 @@ import type { PublicMethodsOf } from '@kbn/utility-types'; import { PluginInitializerContext, PluginConfigDescriptor } from '../../../../src/core/server'; import { ActionsPlugin } from './plugin'; -import { configSchema } from './config'; +import { configSchema, ActionsConfig } from './config'; import { ActionsClient as ActionsClientClass } from './actions_client'; import { ActionsAuthorization as ActionsAuthorizationClass } from './authorization/actions_authorization'; -import { ActionsConfigType } from './types'; export type ActionsClient = PublicMethodsOf; export type ActionsAuthorization = PublicMethodsOf; @@ -52,7 +51,7 @@ export { asSavedObjectExecutionSource, asHttpRequestExecutionSource } from './li export const plugin = (initContext: PluginInitializerContext) => new ActionsPlugin(initContext); -export const config: PluginConfigDescriptor = { +export const config: PluginConfigDescriptor = { schema: configSchema, deprecations: ({ renameFromRoot }) => [ renameFromRoot('xpack.actions.whitelistedHosts', 'xpack.actions.allowedHosts'), diff --git a/x-pack/plugins/actions/server/lib/action_executor.ts b/x-pack/plugins/actions/server/lib/action_executor.ts index 695613a59eff1..fa33c1226ec9e 100644 --- a/x-pack/plugins/actions/server/lib/action_executor.ts +++ b/x-pack/plugins/actions/server/lib/action_executor.ts @@ -12,7 +12,6 @@ import { GetServicesFunction, RawAction, PreConfiguredAction, - ProxySettings, } from '../types'; import { EncryptedSavedObjectsClient } from '../../../encrypted_saved_objects/server'; import { SpacesServiceStart } from '../../../spaces/server'; @@ -33,7 +32,6 @@ export interface ActionExecutorContext { actionTypeRegistry: ActionTypeRegistryContract; eventLogger: IEventLogger; preconfiguredActions: PreConfiguredAction[]; - proxySettings?: ProxySettings; } export interface ExecuteOptions { @@ -87,7 +85,6 @@ export class ActionExecutor { eventLogger, preconfiguredActions, getActionsClientWithRequest, - proxySettings, } = this.actionExecutorContext!; const services = getServices(request); @@ -145,7 +142,6 @@ export class ActionExecutor { params: validatedParams, config: validatedConfig, secrets: validatedSecrets, - proxySettings, }); } catch (err) { rawResult = { diff --git a/x-pack/plugins/actions/server/plugin.ts b/x-pack/plugins/actions/server/plugin.ts index 1543f8d7a07ce..22400a08a2a09 100644 --- a/x-pack/plugins/actions/server/plugin.ts +++ b/x-pack/plugins/actions/server/plugin.ts @@ -357,15 +357,6 @@ export class ActionsPlugin implements Plugin, Plugi encryptedSavedObjectsClient, actionTypeRegistry: actionTypeRegistry!, preconfiguredActions, - proxySettings: - this.actionsConfig && this.actionsConfig.proxyUrl - ? { - proxyUrl: this.actionsConfig.proxyUrl, - proxyHeaders: this.actionsConfig.proxyHeaders, - proxyRejectUnauthorizedCertificates: this.actionsConfig - .proxyRejectUnauthorizedCertificates, - } - : undefined, }); const spaceIdToNamespace = (spaceId?: string) => { diff --git a/x-pack/plugins/actions/server/types.ts b/x-pack/plugins/actions/server/types.ts index f545c0fc96633..0bcf02c6f83ae 100644 --- a/x-pack/plugins/actions/server/types.ts +++ b/x-pack/plugins/actions/server/types.ts @@ -55,12 +55,6 @@ export interface ActionsPlugin { start: PluginStartContract; } -export interface ActionsConfigType { - enabled: boolean; - allowedHosts: string[]; - enabledActionTypes: string[]; -} - // the parameters passed to an action type executor function export interface ActionTypeExecutorOptions { actionId: string; @@ -68,7 +62,6 @@ export interface ActionTypeExecutorOptions { config: Config; secrets: Secrets; params: Params; - proxySettings?: ProxySettings; } export interface ActionResult { diff --git a/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts b/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts index 1b0fe03633531..08a3fd007554e 100644 --- a/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts +++ b/x-pack/plugins/actions/server/usage/actions_telemetry.test.ts @@ -4,16 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getTotalCount } from './actions_telemetry'; +import { getInUseTotalCount, getTotalCount } from './actions_telemetry'; describe('actions telemetry', () => { - test('getTotalCount should replace action types names with . to __', async () => { + test('getTotalCount should replace first symbol . to __ for action types names', async () => { const mockEsClient = jest.fn(); mockEsClient.mockReturnValue({ aggregations: { byActionTypeId: { value: { - types: { '.index': 1, '.server-log': 1 }, + types: { '.index': 1, '.server-log': 1, 'some.type': 1, 'another.type.': 1 }, }, }, }, @@ -56,6 +56,38 @@ describe('actions telemetry', () => { updated_at: '2020-03-26T18:46:44.449Z', }, }, + { + _id: 'action:00000000-1', + _index: '.kibana_1', + _score: 0, + _source: { + action: { + actionTypeId: 'some.type', + config: {}, + name: 'test type', + secrets: {}, + }, + references: [], + type: 'action', + updated_at: '2020-03-26T18:46:44.449Z', + }, + }, + { + _id: 'action:00000000-2', + _index: '.kibana_1', + _score: 0, + _source: { + action: { + actionTypeId: 'another.type.', + config: {}, + name: 'test another type', + secrets: {}, + }, + references: [], + type: 'action', + updated_at: '2020-03-26T18:46:44.449Z', + }, + }, ], }, }); @@ -69,6 +101,58 @@ Object { "countByType": Object { "__index": 1, "__server-log": 1, + "another.type__": 1, + "some.type": 1, + }, + "countTotal": 4, +} +`); + }); + + test('getInUseTotalCount', async () => { + const mockEsClient = jest.fn(); + mockEsClient.mockReturnValue({ + aggregations: { + refs: { + actionRefIds: { + value: { + connectorIds: { '1': 'action-0', '123': 'action-0' }, + total: 2, + }, + }, + }, + hits: { + hits: [], + }, + }, + }); + const actionsBulkGet = jest.fn(); + actionsBulkGet.mockReturnValue({ + saved_objects: [ + { + id: '1', + attributes: { + actionTypeId: '.server-log', + }, + }, + { + id: '123', + attributes: { + actionTypeId: '.slack', + }, + }, + ], + }); + const telemetry = await getInUseTotalCount(mockEsClient, actionsBulkGet, 'test'); + + expect(mockEsClient).toHaveBeenCalledTimes(1); + expect(actionsBulkGet).toHaveBeenCalledTimes(1); + + expect(telemetry).toMatchInlineSnapshot(` +Object { + "countByType": Object { + "__server-log": 1, + "__slack": 1, }, "countTotal": 2, } diff --git a/x-pack/plugins/actions/server/usage/actions_telemetry.ts b/x-pack/plugins/actions/server/usage/actions_telemetry.ts index e3ff2552fed9c..cc49232150eee 100644 --- a/x-pack/plugins/actions/server/usage/actions_telemetry.ts +++ b/x-pack/plugins/actions/server/usage/actions_telemetry.ts @@ -4,7 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LegacyAPICaller } from 'kibana/server'; +import { + LegacyAPICaller, + SavedObjectsBaseOptions, + SavedObjectsBulkGetObject, + SavedObjectsBulkResponse, +} from 'kibana/server'; +import { ActionResult } from '../types'; export async function getTotalCount(callCluster: LegacyAPICaller, kibanaIndex: string) { const scriptedMetric = { @@ -58,14 +64,23 @@ export async function getTotalCount(callCluster: LegacyAPICaller, kibanaIndex: s // eslint-disable-next-line @typescript-eslint/no-explicit-any (obj: any, key: string) => ({ ...obj, - [key.replace('.', '__')]: searchResult.aggregations.byActionTypeId.value.types[key], + [replaceFirstAndLastDotSymbols(key)]: searchResult.aggregations.byActionTypeId.value.types[ + key + ], }), {} ), }; } -export async function getInUseTotalCount(callCluster: LegacyAPICaller, kibanaIndex: string) { +export async function getInUseTotalCount( + callCluster: LegacyAPICaller, + actionsBulkGet: ( + objects?: SavedObjectsBulkGetObject[] | undefined, + options?: SavedObjectsBaseOptions | undefined + ) => Promise>>>, + kibanaIndex: string +): Promise<{ countTotal: number; countByType: Record }> { const scriptedMetric = { scripted_metric: { init_script: 'state.connectorIds = new HashMap(); state.total = 0;', @@ -145,7 +160,32 @@ export async function getInUseTotalCount(callCluster: LegacyAPICaller, kibanaInd }, }); - return actionResults.aggregations.refs.actionRefIds.value.total; + const bulkFilter = Object.entries( + actionResults.aggregations.refs.actionRefIds.value.connectorIds + ).map(([key]) => ({ + id: key, + type: 'action', + fields: ['id', 'actionTypeId'], + })); + const actions = await actionsBulkGet(bulkFilter); + const countByType = actions.saved_objects.reduce( + (actionTypeCount: Record, action) => { + const alertTypeId = replaceFirstAndLastDotSymbols(action.attributes.actionTypeId); + const currentCount = + actionTypeCount[alertTypeId] !== undefined ? actionTypeCount[alertTypeId] : 0; + actionTypeCount[alertTypeId] = currentCount + 1; + return actionTypeCount; + }, + {} + ); + return { countTotal: actionResults.aggregations.refs.actionRefIds.value.total, countByType }; +} + +function replaceFirstAndLastDotSymbols(strToReplace: string) { + const hasFirstSymbolDot = strToReplace.startsWith('.'); + const appliedString = hasFirstSymbolDot ? strToReplace.replace('.', '__') : strToReplace; + const hasLastSymbolDot = strToReplace.endsWith('.'); + return hasLastSymbolDot ? `${appliedString.slice(0, -1)}__` : appliedString; } // TODO: Implement executions count telemetry with eventLog, when it will write to index diff --git a/x-pack/plugins/actions/server/usage/task.ts b/x-pack/plugins/actions/server/usage/task.ts index 176ba29ef748a..001db21ffebcc 100644 --- a/x-pack/plugins/actions/server/usage/task.ts +++ b/x-pack/plugins/actions/server/usage/task.ts @@ -4,13 +4,20 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Logger, CoreSetup, LegacyAPICaller } from 'kibana/server'; +import { + Logger, + CoreSetup, + LegacyAPICaller, + SavedObjectsBulkGetObject, + SavedObjectsBaseOptions, +} from 'kibana/server'; import moment from 'moment'; import { RunContext, TaskManagerSetupContract, TaskManagerStartContract, } from '../../../task_manager/server'; +import { ActionResult } from '../types'; import { getTotalCount, getInUseTotalCount } from './actions_telemetry'; export const TELEMETRY_TASK_TYPE = 'actions_telemetry'; @@ -66,19 +73,30 @@ export function telemetryTaskRunner(logger: Logger, core: CoreSetup, kibanaIndex client.callAsInternalUser(...args) ); }; + const actionsBulkGet = ( + objects?: SavedObjectsBulkGetObject[], + options?: SavedObjectsBaseOptions + ) => { + return core + .getStartServices() + .then(([{ savedObjects }]) => + savedObjects.createInternalRepository(['action']).bulkGet(objects, options) + ); + }; return { async run() { return Promise.all([ getTotalCount(callCluster, kibanaIndex), - getInUseTotalCount(callCluster, kibanaIndex), + getInUseTotalCount(callCluster, actionsBulkGet, kibanaIndex), ]) - .then(([totalAggegations, countActiveTotal]) => { + .then(([totalAggegations, totalInUse]) => { return { state: { runs: (state.runs || 0) + 1, count_total: totalAggegations.countTotal, count_by_type: totalAggegations.countByType, - count_active_total: countActiveTotal, + count_active_total: totalInUse.countTotal, + count_active_by_type: totalInUse.countByType, }, runAt: getNextMidnight(), }; diff --git a/x-pack/plugins/alerts/server/usage/alerts_telemetry.test.ts b/x-pack/plugins/alerts/server/usage/alerts_telemetry.test.ts index 171f80cf11cb8..843100194e4b6 100644 --- a/x-pack/plugins/alerts/server/usage/alerts_telemetry.test.ts +++ b/x-pack/plugins/alerts/server/usage/alerts_telemetry.test.ts @@ -7,13 +7,13 @@ import { getTotalCountInUse } from './alerts_telemetry'; describe('alerts telemetry', () => { - test('getTotalCountInUse should replace action types names with . to __', async () => { + test('getTotalCountInUse should replace first "." symbol to "__" in alert types names', async () => { const mockEsClient = jest.fn(); mockEsClient.mockReturnValue({ aggregations: { byAlertTypeId: { value: { - types: { '.index-threshold': 2 }, + types: { '.index-threshold': 2, 'logs.alert.document.count': 1, 'document.test.': 1 }, }, }, }, @@ -30,8 +30,10 @@ describe('alerts telemetry', () => { Object { "countByType": Object { "__index-threshold": 2, + "document.test__": 1, + "logs.alert.document.count": 1, }, - "countTotal": 2, + "countTotal": 4, } `); }); diff --git a/x-pack/plugins/alerts/server/usage/alerts_telemetry.ts b/x-pack/plugins/alerts/server/usage/alerts_telemetry.ts index 6edebb1decb61..72b189aa67f4b 100644 --- a/x-pack/plugins/alerts/server/usage/alerts_telemetry.ts +++ b/x-pack/plugins/alerts/server/usage/alerts_telemetry.ts @@ -250,7 +250,7 @@ export async function getTotalCountAggregations(callCluster: LegacyAPICaller, ki // eslint-disable-next-line @typescript-eslint/no-explicit-any (obj: any, key: string) => ({ ...obj, - [key.replace('.', '__')]: results.aggregations.byAlertTypeId.value.types[key], + [replaceFirstAndLastDotSymbols(key)]: results.aggregations.byAlertTypeId.value.types[key], }), {} ), @@ -310,11 +310,20 @@ export async function getTotalCountInUse(callCluster: LegacyAPICaller, kibanaIne // eslint-disable-next-line @typescript-eslint/no-explicit-any (obj: any, key: string) => ({ ...obj, - [key.replace('.', '__')]: searchResult.aggregations.byAlertTypeId.value.types[key], + [replaceFirstAndLastDotSymbols(key)]: searchResult.aggregations.byAlertTypeId.value.types[ + key + ], }), {} ), }; } +function replaceFirstAndLastDotSymbols(strToReplace: string) { + const hasFirstSymbolDot = strToReplace.startsWith('.'); + const appliedString = hasFirstSymbolDot ? strToReplace.replace('.', '__') : strToReplace; + const hasLastSymbolDot = strToReplace.endsWith('.'); + return hasLastSymbolDot ? `${appliedString.slice(0, -1)}__` : appliedString; +} + // TODO: Implement executions count telemetry with eventLog, when it will write to index diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts index a41faba2e9382..b76dd85f5ee6c 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/general_settings.ts @@ -254,6 +254,6 @@ export const generalSettings: RawSettingDefinition[] = [ 'Used to restrict requests to certain URLs from being instrumented. This config accepts a comma-separated list of wildcard patterns of URL paths that should be ignored. When an incoming HTTP request is detected, its request path will be tested against each element in this list. For example, adding `/home/index` to this list would match and remove instrumentation from `http://localhost/home/index` as well as `http://whatever.com/home/index?value1=123`', } ), - includeAgents: ['java', 'nodejs', 'python', 'dotnet', 'ruby'], + includeAgents: ['java', 'nodejs', 'python', 'dotnet', 'ruby', 'go'], }, ]; diff --git a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/index.test.ts b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/index.test.ts index 4f319e4dd7016..1251f9c2f1bec 100644 --- a/x-pack/plugins/apm/common/agent_configuration/setting_definitions/index.test.ts +++ b/x-pack/plugins/apm/common/agent_configuration/setting_definitions/index.test.ts @@ -50,6 +50,7 @@ describe('filterByAgent', () => { 'sanitize_field_names', 'span_frames_min_duration', 'stack_trace_limit', + 'transaction_ignore_urls', 'transaction_max_spans', 'transaction_sample_rate', ]); diff --git a/x-pack/plugins/apm/public/hooks/use_fetcher.tsx b/x-pack/plugins/apm/public/hooks/use_fetcher.tsx index 8174f06e06b8b..2b58f30a9ec64 100644 --- a/x-pack/plugins/apm/public/hooks/use_fetcher.tsx +++ b/x-pack/plugins/apm/public/hooks/use_fetcher.tsx @@ -24,6 +24,21 @@ export interface FetcherResult { error?: IHttpFetchError; } +function getDetailsFromErrorResponse(error: IHttpFetchError) { + const message = error.body?.message ?? error.response?.statusText; + return ( + <> + {message} ({error.response?.status}) +
+ {i18n.translate('xpack.apm.fetcher.error.url', { + defaultMessage: `URL`, + })} +
+ {error.response?.url} + + ); +} + // fetcher functions can return undefined OR a promise. Previously we had a more simple type // but it led to issues when using object destructuring with default values type InferResponseType = Exclude extends Promise< @@ -82,25 +97,14 @@ export function useFetcher( if (!didCancel) { const errorDetails = - 'response' in err ? ( - <> - {err.response?.statusText} ({err.response?.status}) -
- {i18n.translate('xpack.apm.fetcher.error.url', { - defaultMessage: `URL`, - })} -
- {err.response?.url} - - ) : ( - err.message - ); + 'response' in err ? getDetailsFromErrorResponse(err) : err.message; if (showToastOnError) { - notifications.toasts.addWarning({ + notifications.toasts.addDanger({ title: i18n.translate('xpack.apm.fetcher.error.title', { defaultMessage: `Error while fetching resource`, }), + text: toMountPoint(
diff --git a/x-pack/plugins/apm/scripts/upload-telemetry-data/index.ts b/x-pack/plugins/apm/scripts/upload-telemetry-data/index.ts index 8c64c37d9b7f7..e3221c17f3f2a 100644 --- a/x-pack/plugins/apm/scripts/upload-telemetry-data/index.ts +++ b/x-pack/plugins/apm/scripts/upload-telemetry-data/index.ts @@ -17,6 +17,8 @@ import { argv } from 'yargs'; import { Logger } from 'kibana/server'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { CollectTelemetryParams } from '../../server/lib/apm_telemetry/collect_data_telemetry'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { unwrapEsResponse } from '../../../observability/server/utils/unwrap_es_response'; import { downloadTelemetryTemplate } from '../shared/download-telemetry-template'; import { mergeApmTelemetryMapping } from '../../common/apm_telemetry'; import { generateSampleDocuments } from './generate-sample-documents'; @@ -80,18 +82,18 @@ async function uploadData() { apmAgentConfigurationIndex: '.apm-agent-configuration', }, search: (body) => { - return client.search(body as any).then((res) => res.body as any); + return unwrapEsResponse(client.search(body)); }, indicesStats: (body) => { - return client.indices.stats(body as any).then((res) => res.body); + return unwrapEsResponse(client.indices.stats(body)); }, transportRequest: ((params) => { - return client.transport - .request({ + return unwrapEsResponse( + client.transport.request({ method: params.method, path: params.path, }) - .then((res) => res.body); + ); }) as CollectTelemetryParams['transportRequest'], }, }); diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/index.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/index.ts index 730645c609cb6..90aad48fe20b9 100644 --- a/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/index.ts +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/collect_data_telemetry/index.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ import { merge } from 'lodash'; -import { Logger, LegacyCallAPIOptions } from 'kibana/server'; -import { IndicesStatsParams, Client } from 'elasticsearch'; +import { Logger } from 'kibana/server'; +import { RequestParams } from '@elastic/elasticsearch'; import { ESSearchRequest, ESSearchResponse, @@ -20,9 +20,17 @@ type TelemetryTaskExecutor = (params: { params: TSearchRequest ): Promise>; indicesStats( - params: IndicesStatsParams, - options?: LegacyCallAPIOptions - ): ReturnType; + params: RequestParams.IndicesStats + // promise returned by client has an abort property + // so we cannot use its ReturnType + ): Promise<{ + _all?: { + total?: { store?: { size_in_bytes?: number }; docs?: { count?: number } }; + }; + _shards?: { + total?: number; + }; + }>; transportRequest: (params: { path: string; method: 'get'; diff --git a/x-pack/plugins/apm/server/lib/apm_telemetry/index.ts b/x-pack/plugins/apm/server/lib/apm_telemetry/index.ts index 6d91e64be034d..98abff08dab5e 100644 --- a/x-pack/plugins/apm/server/lib/apm_telemetry/index.ts +++ b/x-pack/plugins/apm/server/lib/apm_telemetry/index.ts @@ -11,6 +11,7 @@ import { Logger, SavedObjectsErrorHelpers, } from '../../../../../../src/core/server'; +import { unwrapEsResponse } from '../../../../observability/server'; import { APMConfig } from '../..'; import { TaskManagerSetupContract, @@ -65,27 +66,22 @@ export async function createApmTelemetry({ const collectAndStore = async () => { const config = await config$.pipe(take(1)).toPromise(); const [{ elasticsearch }] = await core.getStartServices(); - const esClient = elasticsearch.legacy.client; + const esClient = elasticsearch.client; const indices = await getApmIndices({ config, savedObjectsClient, }); - const search = esClient.callAsInternalUser.bind( - esClient, - 'search' - ) as CollectTelemetryParams['search']; + const search: CollectTelemetryParams['search'] = (params) => + unwrapEsResponse(esClient.asInternalUser.search(params)); - const indicesStats = esClient.callAsInternalUser.bind( - esClient, - 'indices.stats' - ) as CollectTelemetryParams['indicesStats']; + const indicesStats: CollectTelemetryParams['indicesStats'] = (params) => + unwrapEsResponse(esClient.asInternalUser.indices.stats(params)); - const transportRequest = esClient.callAsInternalUser.bind( - esClient, - 'transport.request' - ) as CollectTelemetryParams['transportRequest']; + const transportRequest: CollectTelemetryParams['transportRequest'] = ( + params + ) => unwrapEsResponse(esClient.asInternalUser.transport.request(params)); const dataTelemetry = await collectDataTelemetry({ search, diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_client_with_debug.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts similarity index 51% rename from x-pack/plugins/apm/server/lib/helpers/create_es_client/call_client_with_debug.ts rename to x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts index 9f7aaafbefb8c..9d612d82d99bb 100644 --- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_client_with_debug.ts +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/call_async_with_debug.ts @@ -7,34 +7,31 @@ /* eslint-disable no-console */ import chalk from 'chalk'; -import { - LegacyAPICaller, - KibanaRequest, -} from '../../../../../../../src/core/server'; +import { KibanaRequest } from '../../../../../../../src/core/server'; function formatObj(obj: Record) { return JSON.stringify(obj, null, 2); } -export async function callClientWithDebug({ - apiCaller, - operationName, - params, +export async function callAsyncWithDebug({ + cb, + getDebugMessage, debug, - request, }: { - apiCaller: LegacyAPICaller; - operationName: string; - params: Record; + cb: () => Promise; + getDebugMessage: () => { body: string; title: string }; debug: boolean; - request: KibanaRequest; }) { + if (!debug) { + return cb(); + } + const startTime = process.hrtime(); let res: any; let esError = null; try { - res = await apiCaller(operationName, params); + res = await cb(); } catch (e) { // catch error and throw after outputting debug info esError = e; @@ -44,23 +41,14 @@ export async function callClientWithDebug({ const highlightColor = esError ? 'bgRed' : 'inverse'; const diff = process.hrtime(startTime); const duration = `${Math.round(diff[0] * 1000 + diff[1] / 1e6)}ms`; - const routeInfo = `${request.route.method.toUpperCase()} ${ - request.route.path - }`; + + const { title, body } = getDebugMessage(); console.log( - chalk.bold[highlightColor](`=== Debug: ${routeInfo} (${duration}) ===`) + chalk.bold[highlightColor](`=== Debug: ${title} (${duration}) ===`) ); - if (operationName === 'search') { - console.log(`GET ${params.index}/_${operationName}`); - console.log(formatObj(params.body)); - } else { - console.log(chalk.bold('ES operation:'), operationName); - - console.log(chalk.bold('ES query:')); - console.log(formatObj(params)); - } + console.log(body); console.log(`\n`); } @@ -70,3 +58,19 @@ export async function callClientWithDebug({ return res; } + +export const getDebugBody = ( + params: Record, + operationName: string +) => { + if (operationName === 'search') { + return `GET ${params.index}/_search\n${formatObj(params.body)}`; + } + + return `${chalk.bold('ES operation:')} ${operationName}\n${chalk.bold( + 'ES query:' + )}\n${formatObj(params)}`; +}; + +export const getDebugTitle = (request: KibanaRequest) => + `${request.route.method.toUpperCase()} ${request.route.path}`; diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/cancel_es_request_on_abort.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/cancel_es_request_on_abort.ts new file mode 100644 index 0000000000000..e9b61a27f4380 --- /dev/null +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/cancel_es_request_on_abort.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport'; +import { KibanaRequest } from 'src/core/server'; + +export function cancelEsRequestOnAbort>( + promise: T, + request: KibanaRequest +) { + const subscription = request.events.aborted$.subscribe(() => { + promise.abort(); + }); + + // using .catch() here means unsubscribe will be called + // after it has thrown an error, so we use .then(onSuccess, onFailure) + // syntax + promise.then( + () => subscription.unsubscribe(), + () => subscription.unsubscribe() + ); + + return promise; +} diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.test.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.test.ts new file mode 100644 index 0000000000000..f58e04061254d --- /dev/null +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.test.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { contextServiceMock } from 'src/core/server/mocks'; +import { createHttpServer } from 'src/core/server/test_utils'; +import supertest from 'supertest'; +import { createApmEventClient } from '.'; + +describe('createApmEventClient', () => { + let server: ReturnType; + + beforeEach(() => { + server = createHttpServer(); + }); + + afterEach(async () => { + await server.stop(); + }); + it('cancels a search when a request is aborted', async () => { + const { server: innerServer, createRouter } = await server.setup({ + context: contextServiceMock.createSetupContract(), + }); + const router = createRouter('/'); + + const abort = jest.fn(); + router.get( + { path: '/', validate: false }, + async (context, request, res) => { + const eventClient = createApmEventClient({ + esClient: { + search: () => { + return Object.assign( + new Promise((resolve) => setTimeout(resolve, 3000)), + { abort } + ); + }, + } as any, + debug: false, + request, + indices: {} as any, + options: { + includeFrozen: false, + }, + }); + + await eventClient.search({ + apm: { + events: [], + }, + }); + + return res.ok({ body: 'ok' }); + } + ); + + await server.start(); + + const incomingRequest = supertest(innerServer.listener) + .get('/') + // end required to send request + .end(); + + await new Promise((resolve) => { + setTimeout(() => { + incomingRequest.abort(); + setTimeout(() => { + resolve(undefined); + }, 0); + }, 50); + }); + + expect(abort).toHaveBeenCalled(); + }); +}); diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts index b7c38068eb93e..b2e25994d6fe6 100644 --- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_apm_event_client/index.ts @@ -5,10 +5,11 @@ */ import { ValuesType } from 'utility-types'; +import { unwrapEsResponse } from '../../../../../../observability/server'; import { APMError } from '../../../../../typings/es_schemas/ui/apm_error'; import { + ElasticsearchClient, KibanaRequest, - LegacyScopedClusterClient, } from '../../../../../../../../src/core/server'; import { ProcessorEvent } from '../../../../../common/processor_event'; import { @@ -17,11 +18,16 @@ import { } from '../../../../../../../typings/elasticsearch'; import { ApmIndicesConfig } from '../../../settings/apm_indices/get_apm_indices'; import { addFilterToExcludeLegacyData } from './add_filter_to_exclude_legacy_data'; -import { callClientWithDebug } from '../call_client_with_debug'; import { Transaction } from '../../../../../typings/es_schemas/ui/transaction'; import { Span } from '../../../../../typings/es_schemas/ui/span'; import { Metric } from '../../../../../typings/es_schemas/ui/metric'; import { unpackProcessorEvents } from './unpack_processor_events'; +import { + callAsyncWithDebug, + getDebugTitle, + getDebugBody, +} from '../call_async_with_debug'; +import { cancelEsRequestOnAbort } from '../cancel_es_request_on_abort'; export type APMEventESSearchRequest = Omit & { apm: { @@ -59,10 +65,7 @@ export function createApmEventClient({ indices, options: { includeFrozen } = { includeFrozen: false }, }: { - esClient: Pick< - LegacyScopedClusterClient, - 'callAsInternalUser' | 'callAsCurrentUser' - >; + esClient: ElasticsearchClient; debug: boolean; request: KibanaRequest; indices: ApmIndicesConfig; @@ -71,9 +74,9 @@ export function createApmEventClient({ }; }) { return { - search( + async search( params: TParams, - { includeLegacyData } = { includeLegacyData: false } + { includeLegacyData = false } = {} ): Promise> { const withProcessorEventFilter = unpackProcessorEvents(params, indices); @@ -81,15 +84,25 @@ export function createApmEventClient({ ? addFilterToExcludeLegacyData(withProcessorEventFilter) : withProcessorEventFilter; - return callClientWithDebug({ - apiCaller: esClient.callAsCurrentUser, - operationName: 'search', - params: { - ...withPossibleLegacyDataFilter, - ignore_throttled: !includeFrozen, - ignore_unavailable: true, + const searchParams = { + ...withPossibleLegacyDataFilter, + ignore_throttled: !includeFrozen, + ignore_unavailable: true, + }; + + return callAsyncWithDebug({ + cb: () => { + const searchPromise = cancelEsRequestOnAbort( + esClient.search(searchParams), + request + ); + + return unwrapEsResponse(searchPromise); }, - request, + getDebugMessage: () => ({ + body: getDebugBody(searchParams, 'search'), + title: getDebugTitle(request), + }), debug, }); }, diff --git a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts index 8e74a7992e9ea..69f596520d216 100644 --- a/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts +++ b/x-pack/plugins/apm/server/lib/helpers/create_es_client/create_internal_es_client/index.ts @@ -3,23 +3,23 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - -import { - IndexDocumentParams, - IndicesCreateParams, - DeleteDocumentResponse, - DeleteDocumentParams, -} from 'elasticsearch'; import { KibanaRequest } from 'src/core/server'; +import { RequestParams } from '@elastic/elasticsearch'; +import { TransportRequestPromise } from '@elastic/elasticsearch/lib/Transport'; +import { unwrapEsResponse } from '../../../../../../observability/server'; import { APMRequestHandlerContext } from '../../../../routes/typings'; import { ESSearchResponse, ESSearchRequest, } from '../../../../../../../typings/elasticsearch'; -import { callClientWithDebug } from '../call_client_with_debug'; +import { + callAsyncWithDebug, + getDebugBody, + getDebugTitle, +} from '../call_async_with_debug'; +import { cancelEsRequestOnAbort } from '../cancel_es_request_on_abort'; -// `type` was deprecated in 7.0 -export type APMIndexDocumentParams = Omit, 'type'>; +export type APMIndexDocumentParams = RequestParams.Index; export type APMInternalClient = ReturnType; @@ -30,17 +30,26 @@ export function createInternalESClient({ context: APMRequestHandlerContext; request: KibanaRequest; }) { - const { callAsInternalUser } = context.core.elasticsearch.legacy.client; + const { asInternalUser } = context.core.elasticsearch.client; - const callEs = (operationName: string, params: Record) => { - return callClientWithDebug({ - apiCaller: callAsInternalUser, - operationName, - params, - request, + function callEs({ + cb, + operationName, + params, + }: { + operationName: string; + cb: () => TransportRequestPromise; + params: Record; + }) { + return callAsyncWithDebug({ + cb: () => unwrapEsResponse(cancelEsRequestOnAbort(cb(), request)), + getDebugMessage: () => ({ + title: getDebugTitle(request), + body: getDebugBody(params, operationName), + }), debug: context.params.query._debug, }); - }; + } return { search: async < @@ -49,18 +58,32 @@ export function createInternalESClient({ >( params: TSearchRequest ): Promise> => { - return callEs('search', params); + return callEs({ + operationName: 'search', + cb: () => asInternalUser.search(params), + params, + }); }, - index: (params: APMIndexDocumentParams) => { - return callEs('index', params); + index: (params: APMIndexDocumentParams) => { + return callEs({ + operationName: 'index', + cb: () => asInternalUser.index(params), + params, + }); }, - delete: ( - params: Omit - ): Promise => { - return callEs('delete', params); + delete: (params: RequestParams.Delete): Promise<{ result: string }> => { + return callEs({ + operationName: 'delete', + cb: () => asInternalUser.delete(params), + params, + }); }, - indicesCreate: (params: IndicesCreateParams) => { - return callEs('indices.create', params); + indicesCreate: (params: RequestParams.IndicesCreate) => { + return callEs({ + operationName: 'indices.create', + cb: () => asInternalUser.indices.create(params), + params, + }); }, }; } diff --git a/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts b/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts index f2d291cd053bb..f00941d6e6800 100644 --- a/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts +++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.test.ts @@ -31,6 +31,15 @@ jest.mock('../index_pattern/get_dynamic_index_pattern', () => ({ })); function getMockRequest() { + const esClientMock = { + asCurrentUser: { + search: jest.fn().mockResolvedValue({ body: {} }), + }, + asInternalUser: { + search: jest.fn().mockResolvedValue({ body: {} }), + }, + }; + const mockContext = ({ config: new Proxy( {}, @@ -45,12 +54,7 @@ function getMockRequest() { }, core: { elasticsearch: { - legacy: { - client: { - callAsCurrentUser: jest.fn(), - callAsInternalUser: jest.fn(), - }, - }, + client: esClientMock, }, uiSettings: { client: { @@ -69,12 +73,7 @@ function getMockRequest() { } as unknown) as APMRequestHandlerContext & { core: { elasticsearch: { - legacy: { - client: { - callAsCurrentUser: jest.Mock; - callAsInternalUser: jest.Mock; - }; - }; + client: typeof esClientMock; }; uiSettings: { client: { @@ -91,6 +90,11 @@ function getMockRequest() { const mockRequest = ({ url: '', + events: { + aborted$: { + subscribe: jest.fn(), + }, + }, } as unknown) as KibanaRequest; return { mockContext, mockRequest }; @@ -106,8 +110,8 @@ describe('setupRequest', () => { body: { foo: 'bar' }, }); expect( - mockContext.core.elasticsearch.legacy.client.callAsCurrentUser - ).toHaveBeenCalledWith('search', { + mockContext.core.elasticsearch.client.asCurrentUser.search + ).toHaveBeenCalledWith({ index: ['apm-*'], body: { foo: 'bar', @@ -133,8 +137,8 @@ describe('setupRequest', () => { body: { foo: 'bar' }, } as any); expect( - mockContext.core.elasticsearch.legacy.client.callAsInternalUser - ).toHaveBeenCalledWith('search', { + mockContext.core.elasticsearch.client.asInternalUser.search + ).toHaveBeenCalledWith({ index: ['apm-*'], body: { foo: 'bar', @@ -154,8 +158,8 @@ describe('setupRequest', () => { body: { query: { bool: { filter: [{ term: 'someTerm' }] } } }, }); const params = - mockContext.core.elasticsearch.legacy.client.callAsCurrentUser.mock - .calls[0][1]; + mockContext.core.elasticsearch.client.asCurrentUser.search.mock + .calls[0][0]; expect(params.body).toEqual({ query: { bool: { @@ -184,8 +188,8 @@ describe('setupRequest', () => { } ); const params = - mockContext.core.elasticsearch.legacy.client.callAsCurrentUser.mock - .calls[0][1]; + mockContext.core.elasticsearch.client.asCurrentUser.search.mock + .calls[0][0]; expect(params.body).toEqual({ query: { bool: { @@ -214,8 +218,8 @@ describe('without a bool filter', () => { }, }); const params = - mockContext.core.elasticsearch.legacy.client.callAsCurrentUser.mock - .calls[0][1]; + mockContext.core.elasticsearch.client.asCurrentUser.search.mock + .calls[0][0]; expect(params.body).toEqual({ query: { bool: { @@ -245,8 +249,8 @@ describe('with includeFrozen=false', () => { }); const params = - mockContext.core.elasticsearch.legacy.client.callAsCurrentUser.mock - .calls[0][1]; + mockContext.core.elasticsearch.client.asCurrentUser.search.mock + .calls[0][0]; expect(params.ignore_throttled).toBe(true); }); }); @@ -265,8 +269,8 @@ describe('with includeFrozen=true', () => { }); const params = - mockContext.core.elasticsearch.legacy.client.callAsCurrentUser.mock - .calls[0][1]; + mockContext.core.elasticsearch.client.asCurrentUser.search.mock + .calls[0][0]; expect(params.ignore_throttled).toBe(false); }); }); 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 47529de1042a1..947eb68e10093 100644 --- a/x-pack/plugins/apm/server/lib/helpers/setup_request.ts +++ b/x-pack/plugins/apm/server/lib/helpers/setup_request.ts @@ -86,7 +86,7 @@ export async function setupRequest( const coreSetupRequest = { indices, apmEventClient: createApmEventClient({ - esClient: context.core.elasticsearch.legacy.client, + esClient: context.core.elasticsearch.client.asCurrentUser, debug: context.params.query._debug, request, indices, diff --git a/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts b/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts index 3903298415aed..55395f3a4ca4e 100644 --- a/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts +++ b/x-pack/plugins/apm/server/lib/services/annotations/get_stored_annotations.ts @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LegacyAPICaller, Logger } from 'kibana/server'; +import { ElasticsearchClient, Logger } from 'kibana/server'; +import { unwrapEsResponse } from '../../../../../observability/server'; import { rangeFilter } from '../../../../common/utils/range_filter'; import { ESSearchResponse } from '../../../../../../typings/elasticsearch'; import { Annotation as ESAnnotation } from '../../../../../observability/common/annotations'; @@ -18,14 +19,14 @@ export async function getStoredAnnotations({ setup, serviceName, environment, - apiCaller, + client, annotationsClient, logger, }: { setup: Setup & SetupTimeRange; serviceName: string; environment?: string; - apiCaller: LegacyAPICaller; + client: ElasticsearchClient; annotationsClient: ScopedAnnotationsClient; logger: Logger; }): Promise { @@ -50,10 +51,12 @@ export async function getStoredAnnotations({ const response: ESSearchResponse< ESAnnotation, { body: typeof body } - > = (await apiCaller('search', { - index: annotationsClient.index, - body, - })) as any; + > = await unwrapEsResponse( + client.search({ + index: annotationsClient.index, + body, + }) + ); return response.hits.hits.map((hit) => { return { diff --git a/x-pack/plugins/apm/server/lib/services/annotations/index.ts b/x-pack/plugins/apm/server/lib/services/annotations/index.ts index 9516ed3777297..304485822be28 100644 --- a/x-pack/plugins/apm/server/lib/services/annotations/index.ts +++ b/x-pack/plugins/apm/server/lib/services/annotations/index.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { LegacyAPICaller, Logger } from 'kibana/server'; +import { ElasticsearchClient, Logger } from 'kibana/server'; import { ScopedAnnotationsClient } from '../../../../../observability/server'; import { getDerivedServiceAnnotations } from './get_derived_service_annotations'; import { Setup, SetupTimeRange } from '../../helpers/setup_request'; @@ -15,7 +15,7 @@ export async function getServiceAnnotations({ serviceName, environment, annotationsClient, - apiCaller, + client, logger, }: { serviceName: string; @@ -23,7 +23,7 @@ export async function getServiceAnnotations({ setup: Setup & SetupTimeRange; searchAggregatedTransactions: boolean; annotationsClient?: ScopedAnnotationsClient; - apiCaller: LegacyAPICaller; + client: ElasticsearchClient; logger: Logger; }) { // start fetching derived annotations (based on transactions), but don't wait on it @@ -41,7 +41,7 @@ export async function getServiceAnnotations({ serviceName, environment, annotationsClient, - apiCaller, + client, logger, }) : []; diff --git a/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts b/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts index 83117db1167b5..190c99d0002d8 100644 --- a/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts +++ b/x-pack/plugins/apm/server/lib/settings/agent_configuration/create_agent_config_index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyClusterClient, Logger } from 'src/core/server'; +import { ElasticsearchClient, Logger } from 'src/core/server'; import { createOrUpdateIndex, MappingsDefinition, @@ -13,18 +13,18 @@ import { APMConfig } from '../../..'; import { getApmIndicesConfig } from '../apm_indices/get_apm_indices'; export async function createApmAgentConfigurationIndex({ - esClient, + client, config, logger, }: { - esClient: ILegacyClusterClient; + client: ElasticsearchClient; config: APMConfig; logger: Logger; }) { const index = getApmIndicesConfig(config).apmAgentConfigurationIndex; return createOrUpdateIndex({ index, - apiCaller: esClient.callAsInternalUser, + client, logger, mappings, }); diff --git a/x-pack/plugins/apm/server/lib/settings/custom_link/create_custom_link_index.ts b/x-pack/plugins/apm/server/lib/settings/custom_link/create_custom_link_index.ts index 2bfe0d620e4e8..aa9e7411d1014 100644 --- a/x-pack/plugins/apm/server/lib/settings/custom_link/create_custom_link_index.ts +++ b/x-pack/plugins/apm/server/lib/settings/custom_link/create_custom_link_index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyClusterClient, Logger } from 'src/core/server'; +import { ElasticsearchClient, Logger } from 'src/core/server'; import { createOrUpdateIndex, MappingsDefinition, @@ -13,18 +13,18 @@ import { APMConfig } from '../../..'; import { getApmIndicesConfig } from '../apm_indices/get_apm_indices'; export const createApmCustomLinkIndex = async ({ - esClient, + client, config, logger, }: { - esClient: ILegacyClusterClient; + client: ElasticsearchClient; config: APMConfig; logger: Logger; }) => { const index = getApmIndicesConfig(config).apmCustomLinkIndex; return createOrUpdateIndex({ index, - apiCaller: esClient.callAsInternalUser, + client, logger, mappings, }); diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index 09b75137e12df..3e01523aa8e31 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -173,7 +173,7 @@ export class APMPlugin implements Plugin { context.core.uiSettings.client.get(UI_SETTINGS.SEARCH_INCLUDE_FROZEN), ]); - const esClient = context.core.elasticsearch.legacy.client; + const esClient = context.core.elasticsearch.client.asCurrentUser; return createApmEventClient({ debug: debug ?? false, @@ -195,13 +195,13 @@ export class APMPlugin implements Plugin { // create agent configuration index without blocking start lifecycle createApmAgentConfigurationIndex({ - esClient: core.elasticsearch.legacy.client, + client: core.elasticsearch.client.asInternalUser, config: this.currentConfig, logger: this.logger, }); // create custom action index without blocking start lifecycle createApmCustomLinkIndex({ - esClient: core.elasticsearch.legacy.client, + client: core.elasticsearch.client.asInternalUser, config: this.currentConfig, logger: this.logger, }); diff --git a/x-pack/plugins/apm/server/routes/create_api/index.ts b/x-pack/plugins/apm/server/routes/create_api/index.ts index cfb31670bd521..721badf7fc025 100644 --- a/x-pack/plugins/apm/server/routes/create_api/index.ts +++ b/x-pack/plugins/apm/server/routes/create_api/index.ts @@ -147,7 +147,7 @@ function convertBoomToKibanaResponse( error: Boom.Boom, response: KibanaResponseFactory ) { - const opts = { body: error.message }; + const opts = { body: { message: error.message } }; switch (error.output.statusCode) { case 404: return response.notFound(opts); @@ -159,9 +159,6 @@ function convertBoomToKibanaResponse( return response.forbidden(opts); default: - return response.custom({ - statusCode: error.output.statusCode, - ...opts, - }); + throw error; } } diff --git a/x-pack/plugins/apm/server/routes/services.ts b/x-pack/plugins/apm/server/routes/services.ts index bfc2ebf062ac3..ef74437f5f0e7 100644 --- a/x-pack/plugins/apm/server/routes/services.ts +++ b/x-pack/plugins/apm/server/routes/services.ts @@ -194,7 +194,7 @@ export const serviceAnnotationsRoute = createRoute({ serviceName, environment, annotationsClient, - apiCaller: context.core.elasticsearch.legacy.client.callAsCurrentUser, + client: context.core.elasticsearch.client.asCurrentUser, logger: context.logger, }); }, diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/math.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/math.ts index e36644530eae8..0f200a92a41f1 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/common/math.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/common/math.ts @@ -5,7 +5,7 @@ */ // @ts-expect-error no @typed def; Elastic library -import { evaluate } from 'tinymath'; +import { evaluate } from '@kbn/tinymath'; import { pivotObjectArray } from '../../../common/lib/pivot_object_array'; import { Datatable, isDatatable, ExpressionFunctionDefinition } from '../../../types'; import { getFunctionHelp, getFunctionErrors } from '../../../i18n'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/get_field_names.test.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/get_field_names.test.ts index 7dee587895485..a04f39c66bc26 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/get_field_names.test.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/get_field_names.test.ts @@ -5,7 +5,7 @@ */ // @ts-expect-error untyped library -import { parse } from 'tinymath'; +import { parse } from '@kbn/tinymath'; import { getFieldNames } from './pointseries/lib/get_field_names'; describe('getFieldNames', () => { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts index f79f189f363d4..b528eb63ef2b6 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/index.ts @@ -5,7 +5,7 @@ */ // @ts-expect-error Untyped Elastic library -import { evaluate } from 'tinymath'; +import { evaluate } from '@kbn/tinymath'; import { groupBy, zipObject, omit, uniqBy } from 'lodash'; import moment from 'moment'; import { ExpressionFunctionDefinition } from 'src/plugins/expressions/common'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/lib/get_expression_type.js b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/lib/get_expression_type.js index 1cbfafe8103ed..ed1f1d5e6c706 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/lib/get_expression_type.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/lib/get_expression_type.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { parse } from 'tinymath'; +import { parse } from '@kbn/tinymath'; import { getFieldType } from '../../../../../common/lib/get_field_type'; import { isColumnReference } from './is_column_reference'; import { getFieldNames } from './get_field_names'; diff --git a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/lib/is_column_reference.ts b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/lib/is_column_reference.ts index aed9861e1250c..54e1adbeddd78 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/lib/is_column_reference.ts +++ b/x-pack/plugins/canvas/canvas_plugin_src/functions/server/pointseries/lib/is_column_reference.ts @@ -5,7 +5,7 @@ */ // @ts-expect-error untyped library -import { parse } from 'tinymath'; +import { parse } from '@kbn/tinymath'; export function isColumnReference(mathExpression: string | null): boolean { if (mathExpression == null) { diff --git a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/datacolumn/get_form_object.js b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/datacolumn/get_form_object.js index 7f5fe8b2cce12..fbde9f7f63f41 100644 --- a/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/datacolumn/get_form_object.js +++ b/x-pack/plugins/canvas/canvas_plugin_src/uis/arguments/datacolumn/get_form_object.js @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { parse } from 'tinymath'; +import { parse } from '@kbn/tinymath'; import { unquoteString } from '../../../../common/lib/unquote_string'; // break out into separate function, write unit tests first diff --git a/x-pack/plugins/canvas/common/lib/handlebars.js b/x-pack/plugins/canvas/common/lib/handlebars.js index 0b7ef38fe8f6d..ae5063e173525 100644 --- a/x-pack/plugins/canvas/common/lib/handlebars.js +++ b/x-pack/plugins/canvas/common/lib/handlebars.js @@ -5,7 +5,7 @@ */ import Hbars from 'handlebars/dist/handlebars'; -import { evaluate } from 'tinymath'; +import { evaluate } from '@kbn/tinymath'; import { pivotObjectArray } from './pivot_object_array'; // example use: {{math rows 'mean(price - cost)' 2}} diff --git a/x-pack/plugins/dashboard_mode/server/ui_settings.ts b/x-pack/plugins/dashboard_mode/server/ui_settings.ts index f692ec8a33fc9..59de82cf7b3ab 100644 --- a/x-pack/plugins/dashboard_mode/server/ui_settings.ts +++ b/x-pack/plugins/dashboard_mode/server/ui_settings.ts @@ -22,6 +22,7 @@ export function getUiSettings(): Record> { }), value: [DASHBOARD_ONLY_USER_ROLE], category: ['dashboard'], + sensitive: true, deprecation: { message: i18n.translate('xpack.dashboardMode.uiSettings.dashboardsOnlyRolesDeprecation', { defaultMessage: 'This setting is deprecated and will be removed in Kibana 8.0.', diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx index 2c74f9c995a5a..f4bb7577bee53 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.test.tsx @@ -28,6 +28,12 @@ timeFilter.getRefreshInterval.mockImplementation(() => refreshInterval$.getValue beforeEach(() => { refreshInterval$.next({ value: 0, pause: true }); + sessionService.isSessionStorageReady.mockImplementation(() => true); + sessionService.getSearchSessionIndicatorUiConfig.mockImplementation(() => ({ + isDisabled: () => ({ + disabled: false, + }), + })); }); test("shouldn't show indicator in case no active search session", async () => { @@ -45,6 +51,22 @@ test("shouldn't show indicator in case no active search session", async () => { expect(container).toMatchInlineSnapshot(`
`); }); +test("shouldn't show indicator in case app hasn't opt-in", async () => { + const SearchSessionIndicator = createConnectedSearchSessionIndicator({ + sessionService, + application: coreStart.application, + timeFilter, + }); + const { getByTestId, container } = render(); + sessionService.isSessionStorageReady.mockImplementation(() => false); + + // make sure `searchSessionIndicator` isn't appearing after some time (lazy-loading) + await expect( + waitFor(() => getByTestId('searchSessionIndicator'), { timeout: 100 }) + ).rejects.toThrow(); + expect(container).toMatchInlineSnapshot(`
`); +}); + test('should show indicator in case there is an active search session', async () => { const state$ = new BehaviorSubject(SearchSessionState.Loading); const SearchSessionIndicator = createConnectedSearchSessionIndicator({ @@ -57,7 +79,7 @@ test('should show indicator in case there is an active search session', async () await waitFor(() => getByTestId('searchSessionIndicator')); }); -test('should be disabled when permissions are off', async () => { +test('should be disabled in case uiConfig says so ', async () => { const state$ = new BehaviorSubject(SearchSessionState.Loading); coreStart.application.currentAppId$ = new BehaviorSubject('discover'); (coreStart.application.capabilities as any) = { @@ -65,6 +87,12 @@ test('should be disabled when permissions are off', async () => { storeSearchSession: false, }, }; + sessionService.getSearchSessionIndicatorUiConfig.mockImplementation(() => ({ + isDisabled: () => ({ + disabled: true, + reasonText: 'reason', + }), + })); const SearchSessionIndicator = createConnectedSearchSessionIndicator({ sessionService: { ...sessionService, state$ }, application: coreStart.application, @@ -80,12 +108,7 @@ test('should be disabled when permissions are off', async () => { test('should be disabled during auto-refresh', async () => { const state$ = new BehaviorSubject(SearchSessionState.Loading); - coreStart.application.currentAppId$ = new BehaviorSubject('discover'); - (coreStart.application.capabilities as any) = { - discover: { - storeSearchSession: true, - }, - }; + const SearchSessionIndicator = createConnectedSearchSessionIndicator({ sessionService: { ...sessionService, state$ }, application: coreStart.application, diff --git a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx index 5c8c01064bff4..59c1bb4a223b1 100644 --- a/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx +++ b/x-pack/plugins/data_enhanced/public/search/ui/connected_search_session_indicator/connected_search_session_indicator.tsx @@ -29,35 +29,14 @@ export const createConnectedSearchSessionIndicator = ({ .getRefreshIntervalUpdate$() .pipe(map(isAutoRefreshEnabled), distinctUntilChanged()); - const getCapabilitiesByAppId = ( - capabilities: ApplicationStart['capabilities'], - appId?: string - ) => { - switch (appId) { - case 'dashboards': - return capabilities.dashboard; - case 'discover': - return capabilities.discover; - default: - return undefined; - } - }; - return () => { const state = useObservable(sessionService.state$.pipe(debounceTime(500))); const autoRefreshEnabled = useObservable(isAutoRefreshEnabled$, isAutoRefreshEnabled()); - const appId = useObservable(application.currentAppId$, undefined); + const isDisabledByApp = sessionService.getSearchSessionIndicatorUiConfig().isDisabled(); let disabled = false; let disabledReasonText: string = ''; - if (getCapabilitiesByAppId(application.capabilities, appId)?.storeSearchSession !== true) { - disabled = true; - disabledReasonText = i18n.translate('xpack.data.searchSessionIndicator.noCapability', { - defaultMessage: "You don't have permissions to send to background.", - }); - } - if (autoRefreshEnabled) { disabled = true; disabledReasonText = i18n.translate( @@ -68,6 +47,12 @@ export const createConnectedSearchSessionIndicator = ({ ); } + if (isDisabledByApp.disabled) { + disabled = true; + disabledReasonText = isDisabledByApp.reasonText; + } + + if (!sessionService.isSessionStorageReady()) return null; if (!state) return null; return ( diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts index 6eedc9270b83f..e72e28aa47d9b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/constants.ts @@ -500,6 +500,21 @@ export const CONFIGURE_BUTTON = i18n.translate( } ); +export const SAVE_BUTTON = i18n.translate('xpack.enterpriseSearch.workplaceSearch.save.button', { + defaultMessage: 'Save', +}); + +export const CANCEL_BUTTON = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.cancel.button', + { + defaultMessage: 'Cancel', + } +); + +export const OK_BUTTON = i18n.translate('xpack.enterpriseSearch.workplaceSearch.ok.button', { + defaultMessage: 'Ok', +}); + export const PRIVATE_PLATINUM_LICENSE_CALLOUT = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.privatePlatinumCallout.text', { @@ -527,3 +542,68 @@ export const CONNECTORS_HEADER_DESCRIPTION = i18n.translate( defaultMessage: 'All of your configurable connectors.', } ); + +export const URL_LABEL = i18n.translate('xpack.enterpriseSearch.workplaceSearch.url.label', { + defaultMessage: 'URL', +}); + +export const FIELD_LABEL = i18n.translate('xpack.enterpriseSearch.workplaceSearch.field.label', { + defaultMessage: 'Field', +}); + +export const DESCRIPTION_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.description.label', + { + defaultMessage: 'Description', + } +); + +export const UPDATE_LABEL = i18n.translate('xpack.enterpriseSearch.workplaceSearch.update.label', { + defaultMessage: 'Update', +}); + +export const ADD_LABEL = i18n.translate('xpack.enterpriseSearch.workplaceSearch.add.label', { + defaultMessage: 'Add', +}); + +export const ADD_FIELD_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.addField.label', + { + defaultMessage: 'Add field', + } +); + +export const EDIT_FIELD_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.editField.label', + { + defaultMessage: 'Edit field', + } +); + +export const REMOVE_FIELD_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.removeField.label', + { + defaultMessage: 'Remove field', + } +); + +export const RECENT_ACTIVITY_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.recentActivity.title', + { + defaultMessage: 'Recent activity', + } +); + +export const CONFIRM_MODAL_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.comfirmModal.title', + { + defaultMessage: 'Please confirm', + } +); + +export const REMOVE_BUTTON = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.remove.button', + { + defaultMessage: 'Remove', + } +); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx index 6fe87290737f5..61cf1ed34fdcc 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/connect_instance.tsx @@ -35,6 +35,7 @@ import { FeatureIds, Configuration, Features } from '../../../../types'; import { DOCUMENT_PERMISSIONS_DOCS_URL } from '../../../../routes'; import { SourceFeatures } from './source_features'; +import { LEARN_MORE_LINK } from '../../constants'; import { CONNECT_REMOTE, CONNECT_PRIVATE, @@ -206,7 +207,7 @@ export const ConnectInstance: React.FC = ({ values={{ link: ( - Learn more + {LEARN_MORE_LINK} ), }} @@ -242,7 +243,6 @@ export const ConnectInstance: React.FC = ({ - Connect {name} {i18n.translate('xpack.enterpriseSearch.workplaceSearch.contentSource.connect.button', { defaultMessage: 'Connect {name}', values: { name }, diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/constants.ts index 7d891953e618b..8ac3edeb0f308 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/constants.ts @@ -236,13 +236,6 @@ export const OAUTH_SAVE_CONFIG_BUTTON = i18n.translate( } ); -export const OAUTH_REMOVE_BUTTON = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.contentSource.remove.button', - { - defaultMessage: 'Remove', - } -); - export const OAUTH_BACK_BUTTON = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.contentSource.back.button', { @@ -292,20 +285,6 @@ export const SAVE_CUSTOM_API_KEYS_BODY = i18n.translate( } ); -export const SAVE_CUSTOM_ACCESS_TOKEN_LABEL = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.contentSource.saveCustom.accessToken.label', - { - defaultMessage: 'Access Token', - } -); - -export const SAVE_CUSTOM_ID_LABEL = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.contentSource.saveCustom.id.label', - { - defaultMessage: 'ID', - } -); - export const SAVE_CUSTOM_VISUAL_WALKTHROUGH_TITLE = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.contentSource.saveCustom.visualWalkthrough.title', { @@ -327,13 +306,6 @@ export const SAVE_CUSTOM_DOC_PERMISSIONS_TITLE = i18n.translate( } ); -export const SAVE_CUSTOM_FEATURES_BUTTON = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.contentSource.saveCustom.features.button', - { - defaultMessage: 'Learn about Platinum features', - } -); - export const SOURCE_FEATURES_SEARCHABLE = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.contentSource.sourceFeatures.searchable.text', { diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_config.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_config.tsx index e6e428ecab115..1bab035b8f379 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_config.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_config.tsx @@ -28,14 +28,10 @@ import { BASE_URL_LABEL, CLIENT_ID_LABEL, CLIENT_SECRET_LABEL, + REMOVE_BUTTON, } from '../../../../constants'; -import { - OAUTH_SAVE_CONFIG_BUTTON, - OAUTH_REMOVE_BUTTON, - OAUTH_BACK_BUTTON, - OAUTH_STEP_2, -} from './constants'; +import { OAUTH_SAVE_CONFIG_BUTTON, OAUTH_BACK_BUTTON, OAUTH_STEP_2 } from './constants'; import { LicensingLogic } from '../../../../../../applications/shared/licensing'; @@ -99,7 +95,7 @@ export const SaveConfig: React.FC = ({ const deleteButton = ( - {OAUTH_REMOVE_BUTTON} + {REMOVE_BUTTON} ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx index 28aeaec2b47df..8e3bd1c6ab2b5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/add_source/save_custom.tsx @@ -36,18 +36,17 @@ import { getSourcesPath, } from '../../../../routes'; +import { ACCESS_TOKEN_LABEL, ID_LABEL, LEARN_CUSTOM_FEATURES_BUTTON } from '../../constants'; + import { SAVE_CUSTOM_BODY1, SAVE_CUSTOM_BODY2, SAVE_CUSTOM_RETURN_BUTTON, SAVE_CUSTOM_API_KEYS_TITLE, SAVE_CUSTOM_API_KEYS_BODY, - SAVE_CUSTOM_ACCESS_TOKEN_LABEL, - SAVE_CUSTOM_ID_LABEL, SAVE_CUSTOM_VISUAL_WALKTHROUGH_TITLE, SAVE_CUSTOM_STYLING_RESULTS_TITLE, SAVE_CUSTOM_DOC_PERMISSIONS_TITLE, - SAVE_CUSTOM_FEATURES_BUTTON, } from './constants'; interface SaveCustomProps { @@ -109,10 +108,10 @@ export const SaveCustom: React.FC = ({

{SAVE_CUSTOM_API_KEYS_BODY}

- + @@ -200,7 +199,7 @@ export const SaveCustom: React.FC = ({ - {SAVE_CUSTOM_FEATURES_BUTTON} + {LEARN_CUSTOM_FEATURES_BUTTON}
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/constants.ts index 0e6cbb2560128..3b04456b1f59c 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/constants.ts +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/constants.ts @@ -7,15 +7,142 @@ import { i18n } from '@kbn/i18n'; export const LEAVE_UNASSIGNED_FIELD = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.leaveUnassignedField', + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.leaveUnassigned.field', { defaultMessage: 'Leave unassigned', } ); export const SUCCESS_MESSAGE = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.successMessage', + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.success.message', { defaultMessage: 'Display Settings have been successfuly updated.', } ); + +export const UNSAVED_MESSAGE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.unsaved.message', + { + defaultMessage: 'Your display settings have not been saved. Are you sure you want to leave?', + } +); + +export const DISPLAY_SETTINGS_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.displaySettings.title', + { + defaultMessage: 'Display Settings', + } +); + +export const DISPLAY_SETTINGS_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.displaySettings.description', + { + defaultMessage: + 'Customize the content and appearance of your Custom API Source search results.', + } +); + +export const DISPLAY_SETTINGS_EMPTY_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.displaySettingsEmpty.title', + { + defaultMessage: 'You have no content yet', + } +); + +export const DISPLAY_SETTINGS_EMPTY_BODY = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.displaySettingsEmpty.body', + { + defaultMessage: 'You need some content to display in order to configure the display settings.', + } +); + +export const SEARCH_RESULTS_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.searchResults.label', + { + defaultMessage: 'Search Results', + } +); + +export const RESULT_DETAIL_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.resultDetail.label', + { + defaultMessage: 'Result Detail', + } +); + +export const SUBTITLE_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.subtitle.label', + { + defaultMessage: 'Subtitle', + } +); + +export const TITLE_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.title.label', + { + defaultMessage: 'Title', + } +); + +export const EMPTY_FIELDS_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.emptyFields.description', + { + defaultMessage: 'Add fields and move them into the order you want them to appear.', + } +); + +export const VISIBLE_FIELDS_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.visibleFields.title', + { + defaultMessage: 'Visible fields', + } +); + +export const PREVIEW_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.preview.title', + { + defaultMessage: 'Preview', + } +); + +export const SEARCH_RESULTS_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.searchResults.title', + { + defaultMessage: 'Search Results settings', + } +); + +export const FEATURED_RESULTS_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.featuredResults.title', + { + defaultMessage: 'Featured Results', + } +); + +export const FEATURED_RESULTS_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.featuredResults.description', + { + defaultMessage: 'A matching document will appear as a single bold card.', + } +); + +export const STANDARD_RESULTS_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.standardResults.title', + { + defaultMessage: 'Standard Results', + } +); + +export const STANDARD_RESULTS_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.standardResults.description', + { + defaultMessage: 'Somewhat matching documents will appear as a set.', + } +); + +export const SEARCH_RESULTS_ROW_HELP_TEXT = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.searchResultsRow.helpText', + { + defaultMessage: 'This area is optional', + } +); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.tsx index cf066f3157e39..19ccfab11a729 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/display_settings.tsx @@ -32,15 +32,23 @@ import { AppLogic } from '../../../../app_logic'; import { Loading } from '../../../../../shared/loading'; import { ViewContentHeader } from '../../../../components/shared/view_content_header'; +import { SAVE_BUTTON } from '../../../../constants'; +import { + UNSAVED_MESSAGE, + DISPLAY_SETTINGS_TITLE, + DISPLAY_SETTINGS_DESCRIPTION, + DISPLAY_SETTINGS_EMPTY_TITLE, + DISPLAY_SETTINGS_EMPTY_BODY, + SEARCH_RESULTS_LABEL, + RESULT_DETAIL_LABEL, +} from './constants'; + import { DisplaySettingsLogic } from './display_settings_logic'; import { FieldEditorModal } from './field_editor_modal'; import { ResultDetail } from './result_detail'; import { SearchResults } from './search_results'; -const UNSAVED_MESSAGE = - 'Your display settings have not been saved. Are you sure you want to leave?'; - interface DisplaySettingsProps { tabId: number; } @@ -77,12 +85,12 @@ export const DisplaySettings: React.FC = ({ tabId }) => { const tabs = [ { id: 'search_results', - name: 'Search Results', + name: SEARCH_RESULTS_LABEL, content: , }, { id: 'result_detail', - name: 'Result Detail', + name: RESULT_DETAIL_LABEL, content: , }, ] as EuiTabbedContentTab[]; @@ -105,12 +113,12 @@ export const DisplaySettings: React.FC = ({ tabId }) => { <>
- Save + {SAVE_BUTTON} ) : null } @@ -125,10 +133,8 @@ export const DisplaySettings: React.FC = ({ tabId }) => { You have no content yet
} - body={ -

You need some content to display in order to configure the display settings.

- } + title={

{DISPLAY_SETTINGS_EMPTY_TITLE}

} + body={

{DISPLAY_SETTINGS_EMPTY_BODY}

} /> )} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_result_detail_card.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_result_detail_card.tsx index 3278140a2dfe6..3ca70979cc247 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_result_detail_card.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_result_detail_card.tsx @@ -11,6 +11,8 @@ import { useValues } from 'kea'; import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, EuiTitle } from '@elastic/eui'; +import { URL_LABEL } from '../../../../constants'; + import { DisplaySettingsLogic } from './display_settings_logic'; import { CustomSourceIcon } from './custom_source_icon'; @@ -50,7 +52,7 @@ export const ExampleResultDetailCard: React.FC = () => {
{result[urlField]}
) : ( - URL + {URL_LABEL} )}
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_search_result_group.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_search_result_group.tsx index aa7bc4d917886..7f033d8f8d97a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_search_result_group.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_search_result_group.tsx @@ -10,6 +10,8 @@ import { isColorDark, hexToRgb } from '@elastic/eui'; import classNames from 'classnames'; import { useValues } from 'kea'; +import { DESCRIPTION_LABEL } from '../../../../constants'; + import { DisplaySettingsLogic } from './display_settings_logic'; import { CustomSourceIcon } from './custom_source_icon'; @@ -65,7 +67,7 @@ export const ExampleSearchResultGroup: React.FC = () => { className="example-result-content-placeholder" data-test-subj="DefaultDescriptionLabel" > - Description + {DESCRIPTION_LABEL} )}
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_standout_result.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_standout_result.tsx index a80680d219aef..cdd883413481d 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_standout_result.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/example_standout_result.tsx @@ -11,6 +11,8 @@ import { useValues } from 'kea'; import { isColorDark, hexToRgb } from '@elastic/eui'; +import { DESCRIPTION_LABEL } from '../../../../constants'; + import { DisplaySettingsLogic } from './display_settings_logic'; import { CustomSourceIcon } from './custom_source_icon'; @@ -60,7 +62,7 @@ export const ExampleStandoutResult: React.FC = () => { className="example-result-content-placeholder" data-test-subj="DefaultDescriptionLabel" > - Description + {DESCRIPTION_LABEL} )} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/field_editor_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/field_editor_modal.tsx index 587916a741d66..e220e07153867 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/field_editor_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/field_editor_modal.tsx @@ -23,6 +23,8 @@ import { EuiSelect, } from '@elastic/eui'; +import { CANCEL_BUTTON, FIELD_LABEL, UPDATE_LABEL, ADD_LABEL } from '../../../../constants'; + import { DisplaySettingsLogic } from './display_settings_logic'; const emptyField = { fieldName: '', label: '' }; @@ -53,14 +55,16 @@ export const FieldEditorModal: React.FC = () => { } }; - const ACTION_LABEL = isEditing ? 'Update' : 'Add'; + const ACTION_LABEL = isEditing ? UPDATE_LABEL : ADD_LABEL; return ( - {ACTION_LABEL} Field + + {ACTION_LABEL} {FIELD_LABEL} + @@ -89,9 +93,9 @@ export const FieldEditorModal: React.FC = () => { - Cancel + {CANCEL_BUTTON} - {ACTION_LABEL} Field + {ACTION_LABEL} {FIELD_LABEL} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/result_detail.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/result_detail.tsx index 5ee484250ca62..48e285cdcc778 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/result_detail.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/result_detail.tsx @@ -26,6 +26,9 @@ import { EuiTitle, } from '@elastic/eui'; +import { ADD_FIELD_LABEL, EDIT_FIELD_LABEL, REMOVE_FIELD_LABEL } from '../../../../constants'; +import { VISIBLE_FIELDS_TITLE, EMPTY_FIELDS_DESCRIPTION, PREVIEW_TITLE } from './constants'; + import { DisplaySettingsLogic } from './display_settings_logic'; import { ExampleResultDetailCard } from './example_result_detail_card'; @@ -55,7 +58,7 @@ export const ResultDetail: React.FC = () => { -

Visible Fields

+

{VISIBLE_FIELDS_TITLE}

@@ -65,7 +68,7 @@ export const ResultDetail: React.FC = () => { disabled={availableFieldOptions.length < 1} data-test-subj="AddFieldButton" > - Add Field + {ADD_FIELD_LABEL}
@@ -106,13 +109,13 @@ export const ResultDetail: React.FC = () => {
openEditDetailField(index)} /> removeDetailField(index)} /> @@ -127,9 +130,7 @@ export const ResultDetail: React.FC = () => { ) : ( -

- Add fields and move them into the order you want them to appear. -

+

{EMPTY_FIELDS_DESCRIPTION}

)} @@ -138,7 +139,7 @@ export const ResultDetail: React.FC = () => { -

Preview

+

{PREVIEW_TITLE}

diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/search_results.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/search_results.tsx index c1a65d1c52b65..95096331a49d3 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/search_results.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/search_results.tsx @@ -21,7 +21,18 @@ import { } from '@elastic/eui'; import { DisplaySettingsLogic } from './display_settings_logic'; -import { LEAVE_UNASSIGNED_FIELD } from './constants'; + +import { DESCRIPTION_LABEL } from '../../../../constants'; +import { + LEAVE_UNASSIGNED_FIELD, + SEARCH_RESULTS_TITLE, + SEARCH_RESULTS_ROW_HELP_TEXT, + PREVIEW_TITLE, + FEATURED_RESULTS_TITLE, + FEATURED_RESULTS_DESCRIPTION, + STANDARD_RESULTS_TITLE, + STANDARD_RESULTS_DESCRIPTION, +} from './constants'; import { ExampleSearchResultGroup } from './example_search_result_group'; import { ExampleStandoutResult } from './example_standout_result'; @@ -51,7 +62,7 @@ export const SearchResults: React.FC = () => { -

Search Result Settings

+

{SEARCH_RESULTS_TITLE}

@@ -89,7 +100,7 @@ export const SearchResults: React.FC = () => { { /> { -

Preview

+

{PREVIEW_TITLE}

-

Featured Results

+

{FEATURED_RESULTS_TITLE}

-

- A matching document will appear as a single bold card. -

+

{FEATURED_RESULTS_DESCRIPTION}

-

Standard Results

+

{STANDARD_RESULTS_TITLE}

-

- Somewhat matching documents will appear as a set. -

+

{STANDARD_RESULTS_DESCRIPTION}

diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/subtitle_field.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/subtitle_field.tsx index d2f26cd6726df..77f77ad3d3cb9 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/subtitle_field.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/subtitle_field.tsx @@ -10,6 +10,8 @@ import classNames from 'classnames'; import { Result } from '../../../../types'; +import { SUBTITLE_LABEL } from './constants'; + interface SubtitleFieldProps { result: Result; subtitleField: string | null; @@ -31,7 +33,7 @@ export const SubtitleField: React.FC = ({
{result[subtitleField]}
) : ( - Subtitle + {SUBTITLE_LABEL} )}
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/title_field.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/title_field.tsx index fa975c8b11ce0..00b548043aae5 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/title_field.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/title_field.tsx @@ -10,6 +10,8 @@ import classNames from 'classnames'; import { Result } from '../../../../types'; +import { TITLE_LABEL } from './constants'; + interface TitleFieldProps { result: Result; titleField: string | null; @@ -32,7 +34,7 @@ export const TitleField: React.FC = ({ result, titleField, titl ) : ( - Title + {TITLE_LABEL} )} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx index a0797305de6ca..be20eefa1b481 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/overview.tsx @@ -8,6 +8,8 @@ import React from 'react'; import { useValues } from 'kea'; +import { FormattedMessage } from '@kbn/i18n/react'; + import { EuiEmptyPrompt, EuiFlexGroup, @@ -36,6 +38,38 @@ import { getGroupPath, } from '../../../routes'; +import { + RECENT_ACTIVITY_TITLE, + CREDENTIALS_TITLE, + DOCUMENTATION_LINK_TITLE, +} from '../../../constants'; +import { + SOURCES_NO_CONTENT_TITLE, + CONTENT_SUMMARY_TITLE, + CONTENT_TYPE_HEADER, + ITEMS_HEADER, + EVENT_HEADER, + STATUS_HEADER, + TIME_HEADER, + TOTAL_DOCUMENTS_LABEL, + EMPTY_ACTIVITY_TITLE, + GROUP_ACCESS_TITLE, + CONFIGURATION_TITLE, + DOCUMENT_PERMISSIONS_TITLE, + DOCUMENT_PERMISSIONS_TEXT, + DOCUMENT_PERMISSIONS_DISABLED_TEXT, + LEARN_MORE_LINK, + STATUS_HEADING, + STATUS_TEXT, + ADDITIONAL_CONFIG_HEADING, + EXTERNAL_IDENTITIES_LINK, + ACCESS_TOKEN_LABEL, + ID_LABEL, + LEARN_CUSTOM_FEATURES_BUTTON, + DOC_PERMISSIONS_DESCRIPTION, + CUSTOM_CALLOUT_TITLE, +} from '../constants'; + import { AppLogic } from '../../../app_logic'; import { ComponentLoader } from '../../../components/shared/component_loader'; @@ -88,7 +122,7 @@ export const Overview: React.FC = () => { No content yet} + title={

{SOURCES_NO_CONTENT_TITLE}

} iconType="documents" iconColor="subdued" /> @@ -100,7 +134,7 @@ export const Overview: React.FC = () => {
-

Content summary

+

{CONTENT_SUMMARY_TITLE}

@@ -111,14 +145,14 @@ export const Overview: React.FC = () => { ) : ( - Content Type - Items + {CONTENT_TYPE_HEADER} + {ITEMS_HEADER} {tableContent} - Total documents + {TOTAL_DOCUMENTS_LABEL} {totalDocuments.toLocaleString('en-US')} @@ -137,7 +171,7 @@ export const Overview: React.FC = () => { There is no recent activity} + title={

{EMPTY_ACTIVITY_TITLE}

} iconType="clock" iconColor="subdued" /> @@ -148,9 +182,9 @@ export const Overview: React.FC = () => { const activitiesTable = ( - Event - {!custom && Status} - Time + {EVENT_HEADER} + {!custom && {STATUS_HEADER}} + {TIME_HEADER} {activities.map(({ details: activityDetails, event, time, status }, i) => ( @@ -186,7 +220,7 @@ export const Overview: React.FC = () => {
-

Recent activity

+

{RECENT_ACTIVITY_TITLE}

@@ -198,7 +232,7 @@ export const Overview: React.FC = () => { const groupsSummary = ( <> -

Group Access

+

{GROUP_ACCESS_TITLE}

@@ -223,7 +257,7 @@ export const Overview: React.FC = () => { <> -

Configuration

+

{CONFIGURATION_TITLE}

@@ -251,7 +285,7 @@ export const Overview: React.FC = () => { <> -

Document-level permissions

+

{DOCUMENT_PERMISSIONS_TITLE}

@@ -261,7 +295,7 @@ export const Overview: React.FC = () => { - Using document-level permissions + {DOCUMENT_PERMISSIONS_TEXT}
@@ -273,7 +307,7 @@ export const Overview: React.FC = () => { <> -

Document-level permissions

+

{DOCUMENT_PERMISSIONS_TITLE}

@@ -284,13 +318,20 @@ export const Overview: React.FC = () => { - Disabled for this source + {DOCUMENT_PERMISSIONS_DISABLED_TEXT} - - Learn more - {' '} - about permissions + + {LEARN_MORE_LINK} + + ), + }} + /> @@ -303,7 +344,7 @@ export const Overview: React.FC = () => {
- Status + {STATUS_HEADER}
@@ -313,10 +354,10 @@ export const Overview: React.FC = () => { - Everything looks good + {STATUS_HEADING} -

Your endpoints are ready to accept requests.

+

{STATUS_TEXT}

@@ -327,7 +368,7 @@ export const Overview: React.FC = () => {
- Status + {STATUS_HEADING}
@@ -337,15 +378,21 @@ export const Overview: React.FC = () => { - Requires additional configuration + {ADDITIONAL_CONFIG_HEADING}

- The{' '} - - External Identities API - {' '} - must be used to configure user access mappings. Read the guide to learn more. + + {EXTERNAL_IDENTITIES_LINK} + + ), + }} + />

@@ -357,13 +404,13 @@ export const Overview: React.FC = () => {
- Credentials + {CREDENTIALS_TITLE}
- + - +
); @@ -377,7 +424,7 @@ export const Overview: React.FC = () => {
- Documentation + {DOCUMENTATION_LINK_TITLE}
@@ -393,18 +440,15 @@ export const Overview: React.FC = () => { -

Document-level permissions

+

{DOCUMENT_PERMISSIONS_TITLE}

-

- Document-level permissions manage content access content on individual or group - attributes. Allow or deny access to specific documents. -

+

{DOC_PERMISSIONS_DESCRIPTION}

- Learn about Platinum features + {LEARN_CUSTOM_FEATURES_BUTTON}
@@ -449,13 +493,20 @@ export const Overview: React.FC = () => {

- - Learn more - {' '} - about custom sources. + + {LEARN_MORE_LINK} + + ), + }} + />

diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.tsx index 16aceacbddcd5..3f7d99629ca4a 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_added.tsx @@ -10,6 +10,8 @@ import { Location } from 'history'; import { useActions, useValues } from 'kea'; import { Redirect, useLocation } from 'react-router-dom'; +import { i18n } from '@kbn/i18n'; + import { setErrorMessage } from '../../../../shared/flash_messages'; import { parseQueryParams } from '../../../../../applications/shared/query_params'; @@ -37,7 +39,13 @@ export const SourceAdded: React.FC = () => { const decodedName = decodeURIComponent(name); if (hasError) { - const defaultError = `${decodedName} failed to connect.`; + const defaultError = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.sourceAdded.error', + { + defaultMessage: '{decodedName} failed to connect.', + values: { decodedName }, + } + ); setErrorMessage(errorMessages ? errorMessages.join(' ') : defaultError); } else { setAddedSource(decodedName, indexPermissions, serviceType); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx index 728d21eb1530f..cac74d37f9f66 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_content.tsx @@ -10,6 +10,9 @@ import { useActions, useValues } from 'kea'; import { startCase } from 'lodash'; import moment from 'moment'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + import { EuiButton, EuiButtonEmpty, @@ -41,6 +44,16 @@ import { TablePaginationBar } from '../../../components/shared/table_pagination_ import { ViewContentHeader } from '../../../components/shared/view_content_header'; import { CUSTOM_SERVICE_TYPE } from '../../../constants'; +import { + NO_CONTENT_MESSAGE, + CUSTOM_DOCUMENTATION_LINK, + TITLE_HEADING, + LAST_UPDATED_HEADING, + GO_BUTTON, + RESET_BUTTON, + SOURCE_CONTENT_TITLE, + CONTENT_LOADING_TEXT, +} from '../constants'; import { SourceLogic } from '../source_logic'; @@ -78,8 +91,11 @@ export const SourceContent: React.FC = () => { const showPagination = totalPages > 1; const hasItems = totalItems > 0; const emptyMessage = contentFilterValue - ? `No results for '${contentFilterValue}'` - : "This source doesn't have any content yet"; + ? i18n.translate('xpack.enterpriseSearch.workplaceSearch.sources.noContentForValue.message', { + defaultMessage: "No results for '{contentFilterValue}'", + values: { contentFilterValue }, + }) + : NO_CONTENT_MESSAGE; const paginationOptions = { totalPages, @@ -101,10 +117,17 @@ export const SourceContent: React.FC = () => { body={ isCustomSource ? (

- Learn more about adding content in our{' '} - - documentation - + + {CUSTOM_DOCUMENTATION_LINK} + + ), + }} + />

) : null } @@ -143,9 +166,9 @@ export const SourceContent: React.FC = () => { - Title + {TITLE_HEADING} {startCase(urlField)} - Last Updated + {LAST_UPDATED_HEADING} {contentItems.map(contentItem)} @@ -167,12 +190,12 @@ export const SourceContent: React.FC = () => { color="primary" onClick={() => setContentFilterValue(searchTerm)} > - Go + {GO_BUTTON} - Reset + {RESET_BUTTON} @@ -180,12 +203,18 @@ export const SourceContent: React.FC = () => { return ( <> - + { {isFederatedSource && federatedSearchControls} - {sectionLoading && } + {sectionLoading && } {!sectionLoading && (hasItems ? contentTable : emptyState)} ); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_info_card.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_info_card.tsx index 8e3a116e3ac33..ee877e8f61ad6 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_info_card.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_info_card.tsx @@ -18,6 +18,8 @@ import { import { SourceIcon } from '../../../components/shared/source_icon'; +import { REMOTE_SOURCE_LABEL, CREATED_LABEL, STATUS_LABEL, READY_TEXT } from '../constants'; + interface SourceInfoCardProps { sourceName: string; sourceType: string; @@ -54,7 +56,7 @@ export const SourceInfoCard: React.FC = ({ - Remote Source + {REMOTE_SOURCE_LABEL} @@ -63,7 +65,7 @@ export const SourceInfoCard: React.FC = ({ - Created: + {CREATED_LABEL} {dateCreated} @@ -71,12 +73,12 @@ export const SourceInfoCard: React.FC = ({ - Status: + {STATUS_LABEL} - Ready to search + {READY_TEXT} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.tsx index 8d3219be9b02a..5f47fa2d5927b 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/source_settings.tsx @@ -10,6 +10,8 @@ import { useActions, useValues } from 'kea'; import { isEmpty } from 'lodash'; import { Link } from 'react-router-dom'; +import { FormattedMessage } from '@kbn/i18n/react'; + import { EuiButton, EuiButtonEmpty, @@ -21,6 +23,24 @@ import { EuiFormRow, } from '@elastic/eui'; +import { + CANCEL_BUTTON, + OK_BUTTON, + CONFIRM_MODAL_TITLE, + SAVE_CHANGES_BUTTON, + REMOVE_BUTTON, +} from '../../../constants'; +import { + SOURCE_SETTINGS_TITLE, + SOURCE_SETTINGS_DESCRIPTION, + SOURCE_NAME_LABEL, + SOURCE_CONFIG_TITLE, + SOURCE_CONFIG_DESCRIPTION, + SOURCE_CONFIG_LINK, + SOURCE_REMOVE_TITLE, + SOURCE_REMOVE_DESCRIPTION, +} from '../constants'; + import { ContentSection } from '../../../components/shared/content_section'; import { SourceConfigFields } from '../../../components/shared/source_config_fields'; import { ViewContentHeader } from '../../../components/shared/view_content_header'; @@ -85,16 +105,22 @@ export const SourceSettings: React.FC = () => { const confirmModal = ( - Your source documents will be deleted from Workplace Search.
- Are you sure you want to remove {name}? + , + }} + />
); @@ -102,10 +128,7 @@ export const SourceSettings: React.FC = () => { return ( <> - + @@ -114,7 +137,7 @@ export const SourceSettings: React.FC = () => { value={inputValue} size={64} onChange={handleNameChange} - aria-label="Source Name" + aria-label={SOURCE_NAME_LABEL} disabled={buttonLoading} data-test-subj="SourceNameInput" /> @@ -127,17 +150,14 @@ export const SourceSettings: React.FC = () => { onClick={submitNameChange} data-test-subj="SaveChangesButton" > - Save changes + {SAVE_CHANGES_BUTTON} {showConfig && ( - + { /> - Edit content source connector settings + {SOURCE_CONFIG_LINK} )} - + { color="danger" onClick={showConfirm} > - Remove + {REMOVE_BUTTON} {confirmModalVisible && confirmModal} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts new file mode 100644 index 0000000000000..48b8a06b2549c --- /dev/null +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/constants.ts @@ -0,0 +1,313 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const SOURCES_NO_CONTENT_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.noContent.title', + { + defaultMessage: 'No content yet', + } +); + +export const CONTENT_SUMMARY_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.contentSummary.title', + { + defaultMessage: 'Content summary', + } +); + +export const CONTENT_TYPE_HEADER = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.contentType.header', + { + defaultMessage: 'Content type', + } +); + +export const ITEMS_HEADER = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.items.header', + { + defaultMessage: 'Items', + } +); + +export const EVENT_HEADER = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.event.header', + { + defaultMessage: 'Event', + } +); + +export const STATUS_HEADER = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.status.header', + { + defaultMessage: 'Status', + } +); + +export const TIME_HEADER = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.time.header', + { + defaultMessage: 'Time', + } +); + +export const TOTAL_DOCUMENTS_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.totalDocuments.label', + { + defaultMessage: 'Total documents', + } +); + +export const EMPTY_ACTIVITY_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.emptyActivity.title', + { + defaultMessage: 'There is no recent activity', + } +); + +export const GROUP_ACCESS_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.groupAccess.title', + { + defaultMessage: 'Group access', + } +); + +export const CONFIGURATION_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.configuration.title', + { + defaultMessage: 'Configuration', + } +); + +export const DOCUMENT_PERMISSIONS_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.documentPermissions.title', + { + defaultMessage: 'Document-level permissions', + } +); + +export const DOCUMENT_PERMISSIONS_TEXT = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.documentPermissions.text', + { + defaultMessage: 'Using document-level permissions', + } +); + +export const DOCUMENT_PERMISSIONS_DISABLED_TEXT = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.documentPermissionsDisabled.text', + { + defaultMessage: 'Disabled for this sources', + } +); + +export const LEARN_MORE_LINK = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.learnMore.link', + { + defaultMessage: 'Learn more', + } +); + +export const STATUS_HEADING = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.status.heading', + { + defaultMessage: 'Everything looks good', + } +); + +export const STATUS_TEXT = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.status.text', + { + defaultMessage: 'Your endpoints are ready to accept requests.', + } +); + +export const ADDITIONAL_CONFIG_HEADING = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.additionalConfig.heading', + { + defaultMessage: 'Requires additional configuration', + } +); + +export const EXTERNAL_IDENTITIES_LINK = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.externalIdentities.link', + { + defaultMessage: 'External Identities API', + } +); + +export const ACCESS_TOKEN_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.accessToken.label', + { + defaultMessage: 'Access Token', + } +); + +export const ID_LABEL = i18n.translate('xpack.enterpriseSearch.workplaceSearch.sources.id.label', { + defaultMessage: 'ID', +}); + +export const LEARN_CUSTOM_FEATURES_BUTTON = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.learnCustom.features.button', + { + defaultMessage: 'Learn about Platinum features', + } +); + +export const DOC_PERMISSIONS_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.docPermissions.description', + { + defaultMessage: + 'Document-level permissions manage content access content on individual or group attributes. Allow or deny access to specific documents.', + } +); + +export const CUSTOM_CALLOUT_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.customCallout.title', + { + defaultMessage: 'Getting started with custom sources?', + } +); + +export const NO_CONTENT_MESSAGE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.noContentEmpty.message', + { + defaultMessage: "This source doesn't have any content yet", + } +); + +export const CUSTOM_DOCUMENTATION_LINK = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.customSourceDocs.link', + { + defaultMessage: 'documentation', + } +); + +export const TITLE_HEADING = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.title.heading', + { + defaultMessage: 'Title', + } +); + +export const LAST_UPDATED_HEADING = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.lastUpdated.heading', + { + defaultMessage: 'Last updated', + } +); + +export const GO_BUTTON = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.go.button', + { + defaultMessage: 'Go', + } +); + +export const RESET_BUTTON = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.reset.button', + { + defaultMessage: 'Reset', + } +); + +export const SOURCE_CONTENT_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.sourceContent.title', + { + defaultMessage: 'Source content', + } +); + +export const CONTENT_LOADING_TEXT = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.contentLoading.text', + { + defaultMessage: 'Loading content...', + } +); + +export const REMOTE_SOURCE_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.remoteSource.label', + { + defaultMessage: 'Remote source', + } +); + +export const CREATED_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.created.label', + { + defaultMessage: 'Created: ', + } +); + +export const STATUS_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.status.label', + { + defaultMessage: 'Status: ', + } +); + +export const READY_TEXT = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.ready.text', + { + defaultMessage: 'Ready to search', + } +); + +export const SOURCE_SETTINGS_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.settings.title', + { + defaultMessage: 'Content source name', + } +); + +export const SOURCE_SETTINGS_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.settings.description', + { + defaultMessage: 'Customize the name of this content source.', + } +); + +export const SOURCE_CONFIG_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.config.title', + { + defaultMessage: 'Content source configuration', + } +); + +export const SOURCE_CONFIG_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.config.description', + { + defaultMessage: 'Edit content source connector settings to change.', + } +); + +export const SOURCE_CONFIG_LINK = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.config.link', + { + defaultMessage: 'Edit content source connector settings', + } +); + +export const SOURCE_REMOVE_TITLE = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.remove.title', + { + defaultMessage: 'Remove this source', + } +); + +export const SOURCE_REMOVE_DESCRIPTION = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.config.description', + { + defaultMessage: 'Edit content source connector settings to change.', + } +); + +export const SOURCE_NAME_LABEL = i18n.translate( + 'xpack.enterpriseSearch.workplaceSearch.sources.sourceName.label', + { + defaultMessage: 'Source name', + } +); diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/add_group_modal.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/add_group_modal.tsx index 766aa511ebb2d..2402a862a62e7 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/add_group_modal.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/add_group_modal.tsx @@ -22,6 +22,8 @@ import { EuiOverlayMask, } from '@elastic/eui'; +import { CANCEL_BUTTON } from '../../../constants'; + import { GroupsLogic } from '../groups_logic'; const ADD_GROUP_HEADER = i18n.translate( @@ -30,12 +32,6 @@ const ADD_GROUP_HEADER = i18n.translate( defaultMessage: 'Add a group', } ); -const ADD_GROUP_CANCEL = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.groups.addGroup.cancel.action', - { - defaultMessage: 'Cancel', - } -); const ADD_GROUP_SUBMIT = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.groups.addGroup.submit.action', { @@ -72,7 +68,7 @@ export const AddGroupModal: React.FC<{}> = () => { - {ADD_GROUP_CANCEL} + {CANCEL_BUTTON} = ({ - {CANCEL_BUTTON_TEXT} + {CANCEL_BUTTON} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx index 6f55c03746aa8..a1cf7b2ca0a25 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/groups/components/group_overview.tsx @@ -22,6 +22,8 @@ import { EuiHorizontalRule, } from '@elastic/eui'; +import { CANCEL_BUTTON } from '../../../constants'; + import { AppLogic } from '../../../app_logic'; import { TruncatedContent } from '../../../../shared/truncate'; import { ContentSection } from '../../../components/shared/content_section'; @@ -99,12 +101,6 @@ const REMOVE_BUTTON_TEXT = i18n.translate( defaultMessage: 'Remove group', } ); -const CANCEL_REMOVE_BUTTON_TEXT = i18n.translate( - 'xpack.enterpriseSearch.workplaceSearch.groups.overview.cancelRemoveButtonText', - { - defaultMessage: 'Cancel', - } -); const CONFIRM_TITLE_TEXT = i18n.translate( 'xpack.enterpriseSearch.workplaceSearch.groups.overview.confirmTitleText', { @@ -238,7 +234,7 @@ export const GroupOverview: React.FC = () => { onConfirm={deleteGroup} confirmButtonText={CONFIRM_REMOVE_BUTTON_TEXT} title={CONFIRM_TITLE_TEXT} - cancelButtonText={CANCEL_REMOVE_BUTTON_TEXT} + cancelButtonText={CANCEL_BUTTON} defaultFocusedButton="confirm" > {CONFIRM_REMOVE_DESCRIPTION} diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/recent_activity.tsx b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/recent_activity.tsx index 6911196afa81d..a81df1aab83bd 100644 --- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/recent_activity.tsx +++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/overview/recent_activity.tsx @@ -16,6 +16,7 @@ import { ContentSection } from '../../components/shared/content_section'; import { TelemetryLogic } from '../../../shared/telemetry'; import { getWorkplaceSearchUrl } from '../../../shared/enterprise_search_url'; import { SOURCE_DETAILS_PATH, getContentSourcePath } from '../../routes'; +import { RECENT_ACTIVITY_TITLE } from '../../constants'; import { AppLogic } from '../../app_logic'; import { OverviewLogic } from './overview_logic'; @@ -38,15 +39,7 @@ export const RecentActivity: React.FC = () => { const { activityFeed } = useValues(OverviewLogic); return ( - - } - headerSpacer="m" - > + {activityFeed.length > 0 ? ( <> diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts index 545b3b1517145..32f08e685c75d 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.test.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LegacyClusterClient } from 'src/core/server'; +import { ElasticsearchClient } from 'src/core/server'; import { elasticsearchServiceMock, loggingSystemMock } from 'src/core/server/mocks'; import { ClusterClientAdapter, @@ -15,20 +15,21 @@ import { contextMock } from './context.mock'; import { findOptionsSchema } from '../event_log_client'; import { delay } from '../lib/delay'; import { times } from 'lodash'; +import { DeeplyMockedKeys } from '@kbn/utility-types/jest'; +import { RequestEvent } from '@elastic/elasticsearch'; -type EsClusterClient = Pick, 'callAsInternalUser' | 'asScoped'>; type MockedLogger = ReturnType; let logger: MockedLogger; -let clusterClient: EsClusterClient; +let clusterClient: DeeplyMockedKeys; let clusterClientAdapter: IClusterClientAdapter; beforeEach(() => { logger = loggingSystemMock.createLogger(); - clusterClient = elasticsearchServiceMock.createLegacyClusterClient(); + clusterClient = elasticsearchServiceMock.createClusterClient().asInternalUser; clusterClientAdapter = new ClusterClientAdapter({ logger, - clusterClientPromise: Promise.resolve(clusterClient), + elasticsearchClientPromise: Promise.resolve(clusterClient), context: contextMock.create(), }); }); @@ -38,16 +39,16 @@ describe('indexDocument', () => { clusterClientAdapter.indexDocument({ body: { message: 'foo' }, index: 'event-log' }); await retryUntil('cluster client bulk called', () => { - return clusterClient.callAsInternalUser.mock.calls.length !== 0; + return clusterClient.bulk.mock.calls.length !== 0; }); - expect(clusterClient.callAsInternalUser).toHaveBeenCalledWith('bulk', { + expect(clusterClient.bulk).toHaveBeenCalledWith({ body: [{ create: { _index: 'event-log' } }, { message: 'foo' }], }); }); test('should log an error when cluster client throws an error', async () => { - clusterClient.callAsInternalUser.mockRejectedValue(new Error('expected failure')); + clusterClient.bulk.mockRejectedValue(new Error('expected failure')); clusterClientAdapter.indexDocument({ body: { message: 'foo' }, index: 'event-log' }); await retryUntil('cluster client bulk called', () => { return logger.error.mock.calls.length !== 0; @@ -69,7 +70,7 @@ describe('shutdown()', () => { const resultPromise = clusterClientAdapter.shutdown(); await retryUntil('cluster client bulk called', () => { - return clusterClient.callAsInternalUser.mock.calls.length !== 0; + return clusterClient.bulk.mock.calls.length !== 0; }); const result = await resultPromise; @@ -85,7 +86,7 @@ describe('buffering documents', () => { } await retryUntil('cluster client bulk called', () => { - return clusterClient.callAsInternalUser.mock.calls.length !== 0; + return clusterClient.bulk.mock.calls.length !== 0; }); const expectedBody = []; @@ -93,7 +94,7 @@ describe('buffering documents', () => { expectedBody.push({ create: { _index: 'event-log' } }, { message: `foo ${i}` }); } - expect(clusterClient.callAsInternalUser).toHaveBeenCalledWith('bulk', { + expect(clusterClient.bulk).toHaveBeenCalledWith({ body: expectedBody, }); }); @@ -105,7 +106,7 @@ describe('buffering documents', () => { } await retryUntil('cluster client bulk called', () => { - return clusterClient.callAsInternalUser.mock.calls.length >= 2; + return clusterClient.bulk.mock.calls.length >= 2; }); const expectedBody = []; @@ -113,18 +114,18 @@ describe('buffering documents', () => { expectedBody.push({ create: { _index: 'event-log' } }, { message: `foo ${i}` }); } - expect(clusterClient.callAsInternalUser).toHaveBeenNthCalledWith(1, 'bulk', { + expect(clusterClient.bulk).toHaveBeenNthCalledWith(1, { body: expectedBody, }); - expect(clusterClient.callAsInternalUser).toHaveBeenNthCalledWith(2, 'bulk', { + expect(clusterClient.bulk).toHaveBeenNthCalledWith(2, { body: [{ create: { _index: 'event-log' } }, { message: `foo 100` }], }); }); test('should handle lots of docs correctly with a delay in the bulk index', async () => { // @ts-ignore - clusterClient.callAsInternalUser.mockImplementation = async () => await delay(100); + clusterClient.bulk.mockImplementation = async () => await delay(100); const docs = times(EVENT_BUFFER_LENGTH * 10, (i) => ({ body: { message: `foo ${i}` }, @@ -137,7 +138,7 @@ describe('buffering documents', () => { } await retryUntil('cluster client bulk called', () => { - return clusterClient.callAsInternalUser.mock.calls.length >= 10; + return clusterClient.bulk.mock.calls.length >= 10; }); for (let i = 0; i < 10; i++) { @@ -149,7 +150,7 @@ describe('buffering documents', () => { ); } - expect(clusterClient.callAsInternalUser).toHaveBeenNthCalledWith(i + 1, 'bulk', { + expect(clusterClient.bulk).toHaveBeenNthCalledWith(i + 1, { body: expectedBody, }); } @@ -164,19 +165,19 @@ describe('doesIlmPolicyExist', () => { test('should call cluster with proper arguments', async () => { await clusterClientAdapter.doesIlmPolicyExist('foo'); - expect(clusterClient.callAsInternalUser).toHaveBeenCalledWith('transport.request', { + expect(clusterClient.transport.request).toHaveBeenCalledWith({ method: 'GET', path: '/_ilm/policy/foo', }); }); test('should return false when 404 error is returned by Elasticsearch', async () => { - clusterClient.callAsInternalUser.mockRejectedValue(notFoundError); + clusterClient.transport.request.mockRejectedValue(notFoundError); await expect(clusterClientAdapter.doesIlmPolicyExist('foo')).resolves.toEqual(false); }); test('should throw error when error is not 404', async () => { - clusterClient.callAsInternalUser.mockRejectedValue(new Error('Fail')); + clusterClient.transport.request.mockRejectedValue(new Error('Fail')); await expect( clusterClientAdapter.doesIlmPolicyExist('foo') ).rejects.toThrowErrorMatchingInlineSnapshot(`"error checking existance of ilm policy: Fail"`); @@ -189,9 +190,9 @@ describe('doesIlmPolicyExist', () => { describe('createIlmPolicy', () => { test('should call cluster client with given policy', async () => { - clusterClient.callAsInternalUser.mockResolvedValue({ success: true }); + clusterClient.transport.request.mockResolvedValue(asApiResponse({ success: true })); await clusterClientAdapter.createIlmPolicy('foo', { args: true }); - expect(clusterClient.callAsInternalUser).toHaveBeenCalledWith('transport.request', { + expect(clusterClient.transport.request).toHaveBeenCalledWith({ method: 'PUT', path: '/_ilm/policy/foo', body: { args: true }, @@ -199,7 +200,7 @@ describe('createIlmPolicy', () => { }); test('should throw error when call cluster client throws', async () => { - clusterClient.callAsInternalUser.mockRejectedValue(new Error('Fail')); + clusterClient.transport.request.mockRejectedValue(new Error('Fail')); await expect( clusterClientAdapter.createIlmPolicy('foo', { args: true }) ).rejects.toThrowErrorMatchingInlineSnapshot(`"error creating ilm policy: Fail"`); @@ -209,23 +210,23 @@ describe('createIlmPolicy', () => { describe('doesIndexTemplateExist', () => { test('should call cluster with proper arguments', async () => { await clusterClientAdapter.doesIndexTemplateExist('foo'); - expect(clusterClient.callAsInternalUser).toHaveBeenCalledWith('indices.existsTemplate', { + expect(clusterClient.indices.existsTemplate).toHaveBeenCalledWith({ name: 'foo', }); }); test('should return true when call cluster returns true', async () => { - clusterClient.callAsInternalUser.mockResolvedValue(true); + clusterClient.indices.existsTemplate.mockResolvedValue(asApiResponse(true)); await expect(clusterClientAdapter.doesIndexTemplateExist('foo')).resolves.toEqual(true); }); test('should return false when call cluster returns false', async () => { - clusterClient.callAsInternalUser.mockResolvedValue(false); + clusterClient.indices.existsTemplate.mockResolvedValue(asApiResponse(false)); await expect(clusterClientAdapter.doesIndexTemplateExist('foo')).resolves.toEqual(false); }); test('should throw error when call cluster throws an error', async () => { - clusterClient.callAsInternalUser.mockRejectedValue(new Error('Fail')); + clusterClient.indices.existsTemplate.mockRejectedValue(new Error('Fail')); await expect( clusterClientAdapter.doesIndexTemplateExist('foo') ).rejects.toThrowErrorMatchingInlineSnapshot( @@ -237,7 +238,7 @@ describe('doesIndexTemplateExist', () => { describe('createIndexTemplate', () => { test('should call cluster with given template', async () => { await clusterClientAdapter.createIndexTemplate('foo', { args: true }); - expect(clusterClient.callAsInternalUser).toHaveBeenCalledWith('indices.putTemplate', { + expect(clusterClient.indices.putTemplate).toHaveBeenCalledWith({ name: 'foo', create: true, body: { args: true }, @@ -245,16 +246,16 @@ describe('createIndexTemplate', () => { }); test(`should throw error if index template still doesn't exist after error is thrown`, async () => { - clusterClient.callAsInternalUser.mockRejectedValueOnce(new Error('Fail')); - clusterClient.callAsInternalUser.mockResolvedValueOnce(false); + clusterClient.indices.putTemplate.mockRejectedValueOnce(new Error('Fail')); + clusterClient.indices.existsTemplate.mockResolvedValueOnce(asApiResponse(false)); await expect( clusterClientAdapter.createIndexTemplate('foo', { args: true }) ).rejects.toThrowErrorMatchingInlineSnapshot(`"error creating index template: Fail"`); }); test('should not throw error if index template exists after error is thrown', async () => { - clusterClient.callAsInternalUser.mockRejectedValueOnce(new Error('Fail')); - clusterClient.callAsInternalUser.mockResolvedValueOnce(true); + clusterClient.indices.putTemplate.mockRejectedValueOnce(new Error('Fail')); + clusterClient.indices.existsTemplate.mockResolvedValueOnce(asApiResponse(true)); await clusterClientAdapter.createIndexTemplate('foo', { args: true }); }); }); @@ -262,23 +263,23 @@ describe('createIndexTemplate', () => { describe('doesAliasExist', () => { test('should call cluster with proper arguments', async () => { await clusterClientAdapter.doesAliasExist('foo'); - expect(clusterClient.callAsInternalUser).toHaveBeenCalledWith('indices.existsAlias', { + expect(clusterClient.indices.existsAlias).toHaveBeenCalledWith({ name: 'foo', }); }); test('should return true when call cluster returns true', async () => { - clusterClient.callAsInternalUser.mockResolvedValueOnce(true); + clusterClient.indices.existsAlias.mockResolvedValueOnce(asApiResponse(true)); await expect(clusterClientAdapter.doesAliasExist('foo')).resolves.toEqual(true); }); test('should return false when call cluster returns false', async () => { - clusterClient.callAsInternalUser.mockResolvedValueOnce(false); + clusterClient.indices.existsAlias.mockResolvedValueOnce(asApiResponse(false)); await expect(clusterClientAdapter.doesAliasExist('foo')).resolves.toEqual(false); }); test('should throw error when call cluster throws an error', async () => { - clusterClient.callAsInternalUser.mockRejectedValue(new Error('Fail')); + clusterClient.indices.existsAlias.mockRejectedValue(new Error('Fail')); await expect( clusterClientAdapter.doesAliasExist('foo') ).rejects.toThrowErrorMatchingInlineSnapshot( @@ -290,14 +291,14 @@ describe('doesAliasExist', () => { describe('createIndex', () => { test('should call cluster with proper arguments', async () => { await clusterClientAdapter.createIndex('foo'); - expect(clusterClient.callAsInternalUser).toHaveBeenCalledWith('indices.create', { + expect(clusterClient.indices.create).toHaveBeenCalledWith({ index: 'foo', body: {}, }); }); test('should throw error when not getting an error of type resource_already_exists_exception', async () => { - clusterClient.callAsInternalUser.mockRejectedValue(new Error('Fail')); + clusterClient.indices.create.mockRejectedValue(new Error('Fail')); await expect( clusterClientAdapter.createIndex('foo') ).rejects.toThrowErrorMatchingInlineSnapshot(`"error creating initial index: Fail"`); @@ -312,7 +313,7 @@ describe('createIndex', () => { type: 'resource_already_exists_exception', }, }; - clusterClient.callAsInternalUser.mockRejectedValue(err); + clusterClient.indices.create.mockRejectedValue(err); await clusterClientAdapter.createIndex('foo'); }); }); @@ -321,12 +322,14 @@ describe('queryEventsBySavedObject', () => { const DEFAULT_OPTIONS = findOptionsSchema.validate({}); test('should call cluster with proper arguments with non-default namespace', async () => { - clusterClient.callAsInternalUser.mockResolvedValue({ - hits: { - hits: [], - total: { value: 0 }, - }, - }); + clusterClient.search.mockResolvedValue( + asApiResponse({ + hits: { + hits: [], + total: { value: 0 }, + }, + }) + ); await clusterClientAdapter.queryEventsBySavedObjects( 'index-name', 'namespace', @@ -335,14 +338,14 @@ describe('queryEventsBySavedObject', () => { DEFAULT_OPTIONS ); - const [method, query] = clusterClient.callAsInternalUser.mock.calls[0]; - expect(method).toEqual('search'); + const [query] = clusterClient.search.mock.calls[0]; expect(query).toMatchInlineSnapshot(` Object { "body": Object { "from": 0, "query": Object { "bool": Object { + "filter": Array [], "must": Array [ Object { "nested": Object { @@ -400,12 +403,14 @@ describe('queryEventsBySavedObject', () => { }); test('should call cluster with proper arguments with default namespace', async () => { - clusterClient.callAsInternalUser.mockResolvedValue({ - hits: { - hits: [], - total: { value: 0 }, - }, - }); + clusterClient.search.mockResolvedValue( + asApiResponse({ + hits: { + hits: [], + total: { value: 0 }, + }, + }) + ); await clusterClientAdapter.queryEventsBySavedObjects( 'index-name', undefined, @@ -414,14 +419,14 @@ describe('queryEventsBySavedObject', () => { DEFAULT_OPTIONS ); - const [method, query] = clusterClient.callAsInternalUser.mock.calls[0]; - expect(method).toEqual('search'); + const [query] = clusterClient.search.mock.calls[0]; expect(query).toMatchInlineSnapshot(` Object { "body": Object { "from": 0, "query": Object { "bool": Object { + "filter": Array [], "must": Array [ Object { "nested": Object { @@ -481,12 +486,14 @@ describe('queryEventsBySavedObject', () => { }); test('should call cluster with sort', async () => { - clusterClient.callAsInternalUser.mockResolvedValue({ - hits: { - hits: [], - total: { value: 0 }, - }, - }); + clusterClient.search.mockResolvedValue( + asApiResponse({ + hits: { + hits: [], + total: { value: 0 }, + }, + }) + ); await clusterClientAdapter.queryEventsBySavedObjects( 'index-name', 'namespace', @@ -495,8 +502,7 @@ describe('queryEventsBySavedObject', () => { { ...DEFAULT_OPTIONS, sort_field: 'event.end', sort_order: 'desc' } ); - const [method, query] = clusterClient.callAsInternalUser.mock.calls[0]; - expect(method).toEqual('search'); + const [query] = clusterClient.search.mock.calls[0]; expect(query).toMatchObject({ index: 'index-name', body: { @@ -506,12 +512,14 @@ describe('queryEventsBySavedObject', () => { }); test('supports open ended date', async () => { - clusterClient.callAsInternalUser.mockResolvedValue({ - hits: { - hits: [], - total: { value: 0 }, - }, - }); + clusterClient.search.mockResolvedValue( + asApiResponse({ + hits: { + hits: [], + total: { value: 0 }, + }, + }) + ); const start = '2020-07-08T00:52:28.350Z'; @@ -523,14 +531,14 @@ describe('queryEventsBySavedObject', () => { { ...DEFAULT_OPTIONS, start } ); - const [method, query] = clusterClient.callAsInternalUser.mock.calls[0]; - expect(method).toEqual('search'); + const [query] = clusterClient.search.mock.calls[0]; expect(query).toMatchInlineSnapshot(` Object { "body": Object { "from": 0, "query": Object { "bool": Object { + "filter": Array [], "must": Array [ Object { "nested": Object { @@ -595,12 +603,14 @@ describe('queryEventsBySavedObject', () => { }); test('supports optional date range', async () => { - clusterClient.callAsInternalUser.mockResolvedValue({ - hits: { - hits: [], - total: { value: 0 }, - }, - }); + clusterClient.search.mockResolvedValue( + asApiResponse({ + hits: { + hits: [], + total: { value: 0 }, + }, + }) + ); const start = '2020-07-08T00:52:28.350Z'; const end = '2020-07-08T00:00:00.000Z'; @@ -613,14 +623,14 @@ describe('queryEventsBySavedObject', () => { { ...DEFAULT_OPTIONS, start, end } ); - const [method, query] = clusterClient.callAsInternalUser.mock.calls[0]; - expect(method).toEqual('search'); + const [query] = clusterClient.search.mock.calls[0]; expect(query).toMatchInlineSnapshot(` Object { "body": Object { "from": 0, "query": Object { "bool": Object { + "filter": Array [], "must": Array [ Object { "nested": Object { @@ -697,6 +707,12 @@ type RetryableFunction = () => boolean; const RETRY_UNTIL_DEFAULT_COUNT = 20; const RETRY_UNTIL_DEFAULT_WAIT = 1000; // milliseconds +function asApiResponse(body: T): RequestEvent { + return { + body, + } as RequestEvent; +} + async function retryUntil( label: string, fn: RetryableFunction, diff --git a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts index 5d4c33f319fcc..4488dc74556ca 100644 --- a/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts +++ b/x-pack/plugins/event_log/server/es/cluster_client_adapter.ts @@ -5,20 +5,18 @@ */ import { Subject } from 'rxjs'; -import { bufferTime, filter, switchMap } from 'rxjs/operators'; +import { bufferTime, filter as rxFilter, switchMap } from 'rxjs/operators'; import { reject, isUndefined } from 'lodash'; -import { Client } from 'elasticsearch'; import type { PublicMethodsOf } from '@kbn/utility-types'; -import { Logger, LegacyClusterClient } from 'src/core/server'; -import { ESSearchResponse } from '../../../../typings/elasticsearch'; +import { Logger, ElasticsearchClient } from 'src/core/server'; import { EsContext } from '.'; import { IEvent, IValidatedEvent, SAVED_OBJECT_REL_PRIMARY } from '../types'; import { FindOptionsType } from '../event_log_client'; +import { esKuery } from '../../../../../src/plugins/data/server'; export const EVENT_BUFFER_TIME = 1000; // milliseconds export const EVENT_BUFFER_LENGTH = 100; -export type EsClusterClient = Pick; export type IClusterClientAdapter = PublicMethodsOf; export interface Doc { @@ -28,7 +26,7 @@ export interface Doc { export interface ConstructorOpts { logger: Logger; - clusterClientPromise: Promise; + elasticsearchClientPromise: Promise; context: EsContext; } @@ -41,14 +39,14 @@ export interface QueryEventsBySavedObjectResult { export class ClusterClientAdapter { private readonly logger: Logger; - private readonly clusterClientPromise: Promise; + private readonly elasticsearchClientPromise: Promise; private readonly docBuffer$: Subject; private readonly context: EsContext; private readonly docsBufferedFlushed: Promise; constructor(opts: ConstructorOpts) { this.logger = opts.logger; - this.clusterClientPromise = opts.clusterClientPromise; + this.elasticsearchClientPromise = opts.elasticsearchClientPromise; this.context = opts.context; this.docBuffer$ = new Subject(); @@ -58,7 +56,7 @@ export class ClusterClientAdapter { this.docsBufferedFlushed = this.docBuffer$ .pipe( bufferTime(EVENT_BUFFER_TIME, null, EVENT_BUFFER_LENGTH), - filter((docs) => docs.length > 0), + rxFilter((docs) => docs.length > 0), switchMap(async (docs) => await this.indexDocuments(docs)) ) .toPromise(); @@ -97,7 +95,8 @@ export class ClusterClientAdapter { } try { - await this.callEs>('bulk', { body: bulkBody }); + const esClient = await this.elasticsearchClientPromise; + await esClient.bulk({ body: bulkBody }); } catch (err) { this.logger.error( `error writing bulk events: "${err.message}"; docs: ${JSON.stringify(bulkBody)}` @@ -111,7 +110,8 @@ export class ClusterClientAdapter { path: `/_ilm/policy/${policyName}`, }; try { - await this.callEs('transport.request', request); + const esClient = await this.elasticsearchClientPromise; + await esClient.transport.request(request); } catch (err) { if (err.statusCode === 404) return false; throw new Error(`error checking existance of ilm policy: ${err.message}`); @@ -119,14 +119,15 @@ export class ClusterClientAdapter { return true; } - public async createIlmPolicy(policyName: string, policy: unknown): Promise { + public async createIlmPolicy(policyName: string, policy: Record): Promise { const request = { method: 'PUT', path: `/_ilm/policy/${policyName}`, body: policy, }; try { - await this.callEs('transport.request', request); + const esClient = await this.elasticsearchClientPromise; + await esClient.transport.request(request); } catch (err) { throw new Error(`error creating ilm policy: ${err.message}`); } @@ -135,27 +136,18 @@ export class ClusterClientAdapter { public async doesIndexTemplateExist(name: string): Promise { let result; try { - result = await this.callEs>( - 'indices.existsTemplate', - { name } - ); + const esClient = await this.elasticsearchClientPromise; + result = (await esClient.indices.existsTemplate({ name })).body; } catch (err) { throw new Error(`error checking existance of index template: ${err.message}`); } return result as boolean; } - public async createIndexTemplate(name: string, template: unknown): Promise { - const addTemplateParams = { - name, - create: true, - body: template, - }; + public async createIndexTemplate(name: string, template: Record): Promise { try { - await this.callEs>( - 'indices.putTemplate', - addTemplateParams - ); + const esClient = await this.elasticsearchClientPromise; + await esClient.indices.putTemplate({ name, body: template, create: true }); } catch (err) { // The error message doesn't have a type attribute we can look to guarantee it's due // to the template already existing (only long message) so we'll check ourselves to see @@ -171,19 +163,21 @@ export class ClusterClientAdapter { public async doesAliasExist(name: string): Promise { let result; try { - result = await this.callEs>( - 'indices.existsAlias', - { name } - ); + const esClient = await this.elasticsearchClientPromise; + result = (await esClient.indices.existsAlias({ name })).body; } catch (err) { throw new Error(`error checking existance of initial index: ${err.message}`); } return result as boolean; } - public async createIndex(name: string, body: unknown = {}): Promise { + public async createIndex( + name: string, + body: string | Record = {} + ): Promise { try { - await this.callEs>('indices.create', { + const esClient = await this.elasticsearchClientPromise; + await esClient.indices.create({ index: name, body, }); @@ -200,7 +194,7 @@ export class ClusterClientAdapter { type: string, ids: string[], // eslint-disable-next-line @typescript-eslint/naming-convention - { page, per_page: perPage, start, end, sort_field, sort_order }: FindOptionsType + { page, per_page: perPage, start, end, sort_field, sort_order, filter }: FindOptionsType ): Promise { const defaultNamespaceQuery = { bool: { @@ -220,12 +214,26 @@ export class ClusterClientAdapter { }; const namespaceQuery = namespace === undefined ? defaultNamespaceQuery : namedNamespaceQuery; + const esClient = await this.elasticsearchClientPromise; + let dslFilterQuery; + try { + dslFilterQuery = filter + ? esKuery.toElasticsearchQuery(esKuery.fromKueryExpression(filter)) + : []; + } catch (err) { + this.debug(`Invalid kuery syntax for the filter (${filter}) error:`, { + message: err.message, + statusCode: err.statusCode, + }); + throw err; + } const body = { size: perPage, from: (page - 1) * perPage, sort: { [sort_field]: { order: sort_order } }, query: { bool: { + filter: dslFilterQuery, must: reject( [ { @@ -283,8 +291,10 @@ export class ClusterClientAdapter { try { const { - hits: { hits, total }, - }: ESSearchResponse = await this.callEs('search', { + body: { + hits: { hits, total }, + }, + } = await esClient.search({ index, track_total_hits: true, body, @@ -293,7 +303,7 @@ export class ClusterClientAdapter { page, per_page: perPage, total: total.value, - data: hits.map((hit) => hit._source) as IValidatedEvent[], + data: hits.map((hit: { _source: unknown }) => hit._source) as IValidatedEvent[], }; } catch (err) { throw new Error( @@ -302,24 +312,6 @@ export class ClusterClientAdapter { } } - // We have a common problem typing ES-DSL Queries - // eslint-disable-next-line @typescript-eslint/no-explicit-any - private async callEs(operation: string, body?: any) { - try { - this.debug(`callEs(${operation}) calls:`, body); - const clusterClient = await this.clusterClientPromise; - const result = await clusterClient.callAsInternalUser(operation, body); - this.debug(`callEs(${operation}) result:`, result); - return result as ESQueryResult; - } catch (err) { - this.debug(`callEs(${operation}) error:`, { - message: err.message, - statusCode: err.statusCode, - }); - throw err; - } - } - private debug(message: string, object?: unknown) { const objectString = object == null ? '' : JSON.stringify(object); this.logger.debug(`esContext: ${message} ${objectString}`); diff --git a/x-pack/plugins/event_log/server/es/context.test.ts b/x-pack/plugins/event_log/server/es/context.test.ts index 5f26399618e38..fc137b4e45b13 100644 --- a/x-pack/plugins/event_log/server/es/context.test.ts +++ b/x-pack/plugins/event_log/server/es/context.test.ts @@ -5,27 +5,28 @@ */ import { createEsContext } from './context'; -import { LegacyClusterClient, Logger } from '../../../../../src/core/server'; +import { ElasticsearchClient, Logger } from '../../../../../src/core/server'; import { elasticsearchServiceMock, loggingSystemMock } from '../../../../../src/core/server/mocks'; +import { DeeplyMockedKeys } from '@kbn/utility-types/jest'; +import { RequestEvent } from '@elastic/elasticsearch'; jest.mock('../lib/../../../../package.json', () => ({ version: '1.2.3' })); jest.mock('./init'); -type EsClusterClient = Pick, 'callAsInternalUser' | 'asScoped'>; let logger: Logger; -let clusterClient: EsClusterClient; +let elasticsearchClient: DeeplyMockedKeys; beforeEach(() => { logger = loggingSystemMock.createLogger(); - clusterClient = elasticsearchServiceMock.createLegacyClusterClient(); + elasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser; }); describe('createEsContext', () => { test('should return is ready state as falsy if not initialized', () => { const context = createEsContext({ logger, - clusterClientPromise: Promise.resolve(clusterClient), indexNameRoot: 'test0', kibanaVersion: '1.2.3', + elasticsearchClientPromise: Promise.resolve(elasticsearchClient), }); expect(context.initialized).toBeFalsy(); @@ -37,9 +38,9 @@ describe('createEsContext', () => { test('should return esNames', () => { const context = createEsContext({ logger, - clusterClientPromise: Promise.resolve(clusterClient), indexNameRoot: 'test-index', kibanaVersion: '1.2.3', + elasticsearchClientPromise: Promise.resolve(elasticsearchClient), }); const esNames = context.esNames; @@ -57,12 +58,12 @@ describe('createEsContext', () => { test('should return exist false for esAdapter ilm policy, index template and alias before initialize', async () => { const context = createEsContext({ logger, - clusterClientPromise: Promise.resolve(clusterClient), indexNameRoot: 'test1', kibanaVersion: '1.2.3', + elasticsearchClientPromise: Promise.resolve(elasticsearchClient), }); - clusterClient.callAsInternalUser.mockResolvedValue(false); - + elasticsearchClient.indices.existsTemplate.mockResolvedValue(asApiResponse(false)); + elasticsearchClient.indices.existsAlias.mockResolvedValue(asApiResponse(false)); const doesAliasExist = await context.esAdapter.doesAliasExist(context.esNames.alias); expect(doesAliasExist).toBeFalsy(); @@ -75,11 +76,11 @@ describe('createEsContext', () => { test('should return exist true for esAdapter ilm policy, index template and alias after initialize', async () => { const context = createEsContext({ logger, - clusterClientPromise: Promise.resolve(clusterClient), indexNameRoot: 'test2', kibanaVersion: '1.2.3', + elasticsearchClientPromise: Promise.resolve(elasticsearchClient), }); - clusterClient.callAsInternalUser.mockResolvedValue(true); + elasticsearchClient.indices.existsTemplate.mockResolvedValue(asApiResponse(true)); context.initialize(); const doesIlmPolicyExist = await context.esAdapter.doesIlmPolicyExist( @@ -100,12 +101,18 @@ describe('createEsContext', () => { jest.requireMock('./init').initializeEs.mockResolvedValue(false); const context = createEsContext({ logger, - clusterClientPromise: Promise.resolve(clusterClient), indexNameRoot: 'test2', kibanaVersion: '1.2.3', + elasticsearchClientPromise: Promise.resolve(elasticsearchClient), }); context.initialize(); const success = await context.waitTillReady(); expect(success).toBe(false); }); }); + +function asApiResponse(body: T): RequestEvent { + return { + body, + } as RequestEvent; +} diff --git a/x-pack/plugins/event_log/server/es/context.ts b/x-pack/plugins/event_log/server/es/context.ts index c1777d6979c5c..26f249d3b2c06 100644 --- a/x-pack/plugins/event_log/server/es/context.ts +++ b/x-pack/plugins/event_log/server/es/context.ts @@ -4,15 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Logger, LegacyClusterClient } from 'src/core/server'; +import { Logger, ElasticsearchClient } from 'src/core/server'; import { EsNames, getEsNames } from './names'; import { initializeEs } from './init'; import { ClusterClientAdapter, IClusterClientAdapter } from './cluster_client_adapter'; import { createReadySignal, ReadySignal } from '../lib/ready_signal'; -export type EsClusterClient = Pick; - export interface EsContext { logger: Logger; esNames: EsNames; @@ -34,9 +32,9 @@ export function createEsContext(params: EsContextCtorParams): EsContext { export interface EsContextCtorParams { logger: Logger; - clusterClientPromise: Promise; indexNameRoot: string; kibanaVersion: string; + elasticsearchClientPromise: Promise; } class EsContextImpl implements EsContext { @@ -53,7 +51,7 @@ class EsContextImpl implements EsContext { this.initialized = false; this.esAdapter = new ClusterClientAdapter({ logger: params.logger, - clusterClientPromise: params.clusterClientPromise, + elasticsearchClientPromise: params.elasticsearchClientPromise, context: this, }); } diff --git a/x-pack/plugins/event_log/server/es/index.ts b/x-pack/plugins/event_log/server/es/index.ts index ad1409e33589f..adc7ed011aa14 100644 --- a/x-pack/plugins/event_log/server/es/index.ts +++ b/x-pack/plugins/event_log/server/es/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { EsClusterClient, EsContext, createEsContext } from './context'; +export { EsContext, createEsContext } from './context'; diff --git a/x-pack/plugins/event_log/server/event_log_client.ts b/x-pack/plugins/event_log/server/event_log_client.ts index 63453c6327da2..091f997fe62ea 100644 --- a/x-pack/plugins/event_log/server/event_log_client.ts +++ b/x-pack/plugins/event_log/server/event_log_client.ts @@ -6,14 +6,14 @@ import { Observable } from 'rxjs'; import { schema, TypeOf } from '@kbn/config-schema'; -import { LegacyClusterClient, KibanaRequest } from 'src/core/server'; +import { IClusterClient, KibanaRequest } from 'src/core/server'; import { SpacesServiceStart } from '../../spaces/server'; import { EsContext } from './es'; import { IEventLogClient } from './types'; import { QueryEventsBySavedObjectResult } from './es/cluster_client_adapter'; import { SavedObjectBulkGetterResult } from './saved_object_provider_registry'; -export type PluginClusterClient = Pick; +export type PluginClusterClient = Pick; export type AdminClusterClient$ = Observable; const optionalDateFieldSchema = schema.maybe( @@ -48,12 +48,13 @@ export const findOptionsSchema = schema.object({ sort_order: schema.oneOf([schema.literal('asc'), schema.literal('desc')], { defaultValue: 'asc', }), + filter: schema.maybe(schema.string()), }); // page & perPage are required, other fields are optional // using schema.maybe allows us to set undefined, but not to make the field optional export type FindOptionsType = Pick< TypeOf, - 'page' | 'per_page' | 'sort_field' | 'sort_order' + 'page' | 'per_page' | 'sort_field' | 'sort_order' | 'filter' > & Partial>; diff --git a/x-pack/plugins/event_log/server/event_log_service.ts b/x-pack/plugins/event_log/server/event_log_service.ts index 9249288d33939..0bc675fee928d 100644 --- a/x-pack/plugins/event_log/server/event_log_service.ts +++ b/x-pack/plugins/event_log/server/event_log_service.ts @@ -5,14 +5,14 @@ */ import { Observable } from 'rxjs'; -import { LegacyClusterClient } from 'src/core/server'; +import { IClusterClient } from 'src/core/server'; import { Plugin } from './plugin'; import { EsContext } from './es'; import { IEvent, IEventLogger, IEventLogService, IEventLogConfig } from './types'; import { EventLogger } from './event_logger'; import { SavedObjectProvider, SavedObjectProviderRegistry } from './saved_object_provider_registry'; -export type PluginClusterClient = Pick; +export type PluginClusterClient = Pick; export type AdminClusterClient$ = Observable; type SystemLogger = Plugin['systemLogger']; diff --git a/x-pack/plugins/event_log/server/event_log_start_service.ts b/x-pack/plugins/event_log/server/event_log_start_service.ts index 51dd7d6e95d15..82b8f06c251a3 100644 --- a/x-pack/plugins/event_log/server/event_log_start_service.ts +++ b/x-pack/plugins/event_log/server/event_log_start_service.ts @@ -5,14 +5,14 @@ */ import { Observable } from 'rxjs'; -import { LegacyClusterClient, KibanaRequest } from 'src/core/server'; +import { IClusterClient, KibanaRequest } from 'src/core/server'; import { SpacesServiceStart } from '../../spaces/server'; import { EsContext } from './es'; import { IEventLogClientService } from './types'; import { EventLogClient } from './event_log_client'; import { SavedObjectProviderRegistry } from './saved_object_provider_registry'; -export type PluginClusterClient = Pick; +export type PluginClusterClient = Pick; export type AdminClusterClient$ = Observable; interface EventLogServiceCtorParams { diff --git a/x-pack/plugins/event_log/server/plugin.ts b/x-pack/plugins/event_log/server/plugin.ts index 3bf726de71856..e2e31864eb31f 100644 --- a/x-pack/plugins/event_log/server/plugin.ts +++ b/x-pack/plugins/event_log/server/plugin.ts @@ -12,7 +12,7 @@ import { Logger, Plugin as CorePlugin, PluginInitializerContext, - LegacyClusterClient, + IClusterClient, SharedGlobalConfig, IContextProvider, } from 'src/core/server'; @@ -33,7 +33,7 @@ import { EventLogClientService } from './event_log_start_service'; import { SavedObjectProviderRegistry } from './saved_object_provider_registry'; import { findByIdsRoute } from './routes/find_by_ids'; -export type PluginClusterClient = Pick; +export type PluginClusterClient = Pick; const PROVIDER = 'eventLog'; @@ -77,9 +77,9 @@ export class Plugin implements CorePlugin elasticsearch.legacy.client), + .then(([{ elasticsearch }]) => elasticsearch.client.asInternalUser), kibanaVersion: this.kibanaVersion, }); diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index e09fbfc80b196..d0df9b73dd88a 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -326,7 +326,6 @@ export interface IndexTemplate { template: { settings: any; mappings: any; - aliases: object; }; data_stream: { hidden?: boolean }; composed_of: string[]; diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index dcc686e565b8e..7b4149819dc78 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -6,7 +6,10 @@ import { SavedObjectsServiceSetup, SavedObjectsType } from 'kibana/server'; import { EncryptedSavedObjectsPluginSetup } from '../../../encrypted_saved_objects/server'; -import { migratePackagePolicyToV7110 } from '../../../security_solution/common'; +import { + migratePackagePolicyToV7110, + migratePackagePolicyToV7120, +} from '../../../security_solution/common'; import { OUTPUT_SAVED_OBJECT_TYPE, AGENT_POLICY_SAVED_OBJECT_TYPE, @@ -273,6 +276,7 @@ const getSavedObjectTypes = ( migrations: { '7.10.0': migratePackagePolicyToV7100, '7.11.0': migratePackagePolicyToV7110, + '7.12.0': migratePackagePolicyToV7120, }, }, [PACKAGES_SAVED_OBJECT_TYPE]: { diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap index 0333fb024a717..2d2478843c454 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/__snapshots__/template.test.ts.snap @@ -95,8 +95,7 @@ exports[`tests loading base.yml: base.yml 1`] = ` "managed_by": "ingest-manager", "managed": true } - }, - "aliases": {} + } }, "data_stream": {}, "composed_of": [], @@ -205,8 +204,7 @@ exports[`tests loading coredns.logs.yml: coredns.logs.yml 1`] = ` "managed_by": "ingest-manager", "managed": true } - }, - "aliases": {} + } }, "data_stream": {}, "composed_of": [], @@ -1699,8 +1697,7 @@ exports[`tests loading system.yml: system.yml 1`] = ` "managed_by": "ingest-manager", "managed": true } - }, - "aliases": {} + } }, "data_stream": {}, "composed_of": [], diff --git a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts index fd75139d4cd45..e1fa2a0b18b59 100644 --- a/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts +++ b/x-pack/plugins/fleet/server/services/epm/elasticsearch/template/template.ts @@ -335,8 +335,6 @@ function getBaseTemplate( properties: mappings.properties, _meta, }, - // To be filled with the aliases that we need - aliases: {}, }, data_stream: { hidden }, composed_of: composedOfTemplates, diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/app/app.test.ts b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/app/app.test.ts index 1ffbae39d3705..1b49416ebbbe9 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/app/app.test.ts +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/app/app.test.ts @@ -21,6 +21,9 @@ const PERCENT_SIGN_NAME = 'test%'; const PERCENT_SIGN_WITH_OTHER_CHARS_NAME = 'test%#'; const PERCENT_SIGN_25_SEQUENCE = 'test%25'; +const createPolicyTitle = 'Create Policy'; +const editPolicyTitle = 'Edit Policy'; + window.scrollTo = jest.fn(); jest.mock('@elastic/eui', () => { @@ -52,7 +55,7 @@ describe('', () => { await actions.clickCreatePolicyButton(); component.update(); - expect(testBed.find('policyTitle').text()).toBe(`Create an index lifecycle policy`); + expect(testBed.find('policyTitle').text()).toBe(createPolicyTitle); expect(testBed.find('policyNameField').props().value).toBe(''); }); @@ -68,7 +71,7 @@ describe('', () => { await actions.clickCreatePolicyButton(); component.update(); - expect(testBed.find('policyTitle').text()).toBe(`Create an index lifecycle policy`); + expect(testBed.find('policyTitle').text()).toBe(createPolicyTitle); expect(testBed.find('policyNameField').props().value).toBe(''); }); }); @@ -89,9 +92,7 @@ describe('', () => { await actions.clickPolicyNameLink(); component.update(); - expect(testBed.find('policyTitle').text()).toBe( - `Edit index lifecycle policy ${SPECIAL_CHARS_NAME}` - ); + expect(testBed.find('policyTitle').text()).toBe(`${editPolicyTitle} ${SPECIAL_CHARS_NAME}`); }); test('loading edit policy page url works', async () => { @@ -102,9 +103,7 @@ describe('', () => { const { component } = testBed; component.update(); - expect(testBed.find('policyTitle').text()).toBe( - `Edit index lifecycle policy ${SPECIAL_CHARS_NAME}` - ); + expect(testBed.find('policyTitle').text()).toBe(`${editPolicyTitle} ${SPECIAL_CHARS_NAME}`); }); // using double encoding to counteract react-router's v5 internal decodeURI call @@ -117,9 +116,7 @@ describe('', () => { const { component } = testBed; component.update(); - expect(testBed.find('policyTitle').text()).toBe( - `Edit index lifecycle policy ${SPECIAL_CHARS_NAME}` - ); + expect(testBed.find('policyTitle').text()).toBe(`${editPolicyTitle} ${SPECIAL_CHARS_NAME}`); }); }); @@ -136,9 +133,7 @@ describe('', () => { const { component } = testBed; component.update(); - expect(testBed.find('policyTitle').text()).toBe( - `Edit index lifecycle policy ${PERCENT_SIGN_NAME}` - ); + expect(testBed.find('policyTitle').text()).toBe(`${editPolicyTitle} ${PERCENT_SIGN_NAME}`); }); test('loading edit policy page url with double encoding works', async () => { @@ -149,9 +144,7 @@ describe('', () => { const { component } = testBed; component.update(); - expect(testBed.find('policyTitle').text()).toBe( - `Edit index lifecycle policy ${PERCENT_SIGN_NAME}` - ); + expect(testBed.find('policyTitle').text()).toBe(`${editPolicyTitle} ${PERCENT_SIGN_NAME}`); }); }); @@ -174,7 +167,7 @@ describe('', () => { component.update(); expect(testBed.find('policyTitle').text()).toBe( - `Edit index lifecycle policy ${PERCENT_SIGN_WITH_OTHER_CHARS_NAME}` + `${editPolicyTitle} ${PERCENT_SIGN_WITH_OTHER_CHARS_NAME}` ); }); @@ -188,7 +181,7 @@ describe('', () => { // known issue https://github.com/elastic/kibana/issues/82440 expect(testBed.find('policyTitle').text()).not.toBe( - `Edit index lifecycle policy ${PERCENT_SIGN_WITH_OTHER_CHARS_NAME}` + `${editPolicyTitle} ${PERCENT_SIGN_WITH_OTHER_CHARS_NAME}` ); }); @@ -203,7 +196,7 @@ describe('', () => { component.update(); expect(testBed.find('policyTitle').text()).toBe( - `Edit index lifecycle policy ${PERCENT_SIGN_WITH_OTHER_CHARS_NAME}` + `${editPolicyTitle} ${PERCENT_SIGN_WITH_OTHER_CHARS_NAME}` ); }); }); @@ -225,7 +218,7 @@ describe('', () => { component.update(); expect(testBed.find('policyTitle').text()).toBe( - `Edit index lifecycle policy ${PERCENT_SIGN_25_SEQUENCE}` + `${editPolicyTitle} ${PERCENT_SIGN_25_SEQUENCE}` ); }); @@ -239,7 +232,7 @@ describe('', () => { // known issue https://github.com/elastic/kibana/issues/82440 expect(testBed.find('policyTitle').text()).not.toBe( - `Edit index lifecycle policy ${PERCENT_SIGN_25_SEQUENCE}` + `${editPolicyTitle} ${PERCENT_SIGN_25_SEQUENCE}` ); }); @@ -254,7 +247,7 @@ describe('', () => { component.update(); expect(testBed.find('policyTitle').text()).toBe( - `Edit index lifecycle policy ${PERCENT_SIGN_25_SEQUENCE}` + `${editPolicyTitle} ${PERCENT_SIGN_25_SEQUENCE}` ); }); }); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx index 72a0372628a22..64b654b030236 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx +++ b/x-pack/plugins/index_lifecycle_management/__jest__/client_integration/edit_policy/edit_policy.helpers.tsx @@ -89,6 +89,13 @@ export const setup = async (arg?: { appServicesContext: Partial async (checked: boolean) => { + await act(async () => { + form.selectCheckBox(dataTestSubject, checked); + }); + component.update(); + }; + function createFormSetValueAction(dataTestSubject: string) { return async (value: V) => { await act(async () => { @@ -146,17 +153,21 @@ export const setup = async (arg?: { appServicesContext: Partial exists(toggleSelector), toggleForceMerge: createFormToggleAction(toggleSelector), setForcemergeSegmentsCount: createFormSetValueAction(`${phase}-selectedForceMergeSegments`), - setBestCompression: createFormToggleAction(`${phase}-bestCompression`), + setBestCompression: createFormCheckboxAction(`${phase}-bestCompression`), }; }; - const setIndexPriority = (phase: Phases) => - createFormSetValueAction(`${phase}-phaseIndexPriority`); + const createIndexPriorityActions = (phase: Phases) => { + const toggleSelector = `${phase}-indexPrioritySwitch`; + return { + indexPriorityExists: () => exists(toggleSelector), + toggleIndexPriority: createFormToggleAction(toggleSelector), + setIndexPriority: createFormSetValueAction(`${phase}-indexPriority`), + }; + }; const enable = (phase: Phases) => createFormToggleAction(`enablePhaseSwitch-${phase}`); - const warmPhaseOnRollover = createFormToggleAction(`warm-warmPhaseOnRollover`); - const setMinAgeValue = (phase: Phases) => createFormSetValueAction(`${phase}-selectedMinimumAge`); const setMinAgeUnits = (phase: Phases) => @@ -190,13 +201,15 @@ export const setup = async (arg?: { appServicesContext: Partial async (value: string) => { - await createFormToggleAction(`${phase}-shrinkSwitch`)(true); - await createFormSetValueAction(`${phase}-selectedPrimaryShardCount`)(value); + const createShrinkActions = (phase: Phases) => { + const toggleSelector = `${phase}-shrinkSwitch`; + return { + shrinkExists: () => exists(toggleSelector), + toggleShrink: createFormToggleAction(toggleSelector), + setShrink: createFormSetValueAction(`${phase}-primaryShardCount`), + }; }; - const shrinkExists = (phase: Phases) => () => exists(`${phase}-shrinkSwitch`); - const setFreeze = createFormToggleAction('freezeSwitch'); const freezeExists = () => exists('freezeSwitch'); @@ -250,25 +263,22 @@ export const setup = async (arg?: { appServicesContext: Partial', () => { max_size: '50gb', unknown_setting: 123, // Made up setting that should stay preserved }, - set_priority: { - priority: 100, - }, }, min_age: '0ms', }, @@ -126,8 +123,10 @@ describe('', () => { await actions.hot.toggleForceMerge(true); await actions.hot.setForcemergeSegmentsCount('123'); await actions.hot.setBestCompression(true); + await actions.hot.toggleShrink(true); await actions.hot.setShrink('2'); await actions.hot.setReadonly(true); + await actions.hot.toggleIndexPriority(true); await actions.hot.setIndexPriority('123'); await actions.savePolicy(); @@ -186,13 +185,7 @@ describe('', () => { const hotActions = policy.phases.hot.actions; const rolloverAction = hotActions.rollover; expect(rolloverAction).toBe(undefined); - expect(hotActions).toMatchInlineSnapshot(` - Object { - "set_priority": Object { - "priority": 100, - }, - } - `); + expect(hotActions).toMatchInlineSnapshot(`Object {}`); }); test('enabling searchable snapshot should hide force merge, freeze and shrink in subsequent phases', async () => { @@ -260,6 +253,7 @@ describe('', () => { "priority": 50, }, }, + "min_age": "0ms", } `); }); @@ -270,6 +264,7 @@ describe('', () => { await actions.warm.setDataAllocation('node_attrs'); await actions.warm.setSelectedNodeAttribute('test:123'); await actions.warm.setReplicas('123'); + await actions.warm.toggleShrink(true); await actions.warm.setShrink('123'); await actions.warm.toggleForceMerge(true); await actions.warm.setForcemergeSegmentsCount('123'); @@ -290,9 +285,6 @@ describe('', () => { "max_age": "30d", "max_size": "50gb", }, - "set_priority": Object { - "priority": 100, - }, }, "min_age": "0ms", }, @@ -316,24 +308,12 @@ describe('', () => { "number_of_shards": 123, }, }, + "min_age": "0ms", }, }, } `); }); - - test('setting warm phase on rollover to "false"', async () => { - const { actions } = testBed; - await actions.warm.enable(true); - await actions.warm.warmPhaseOnRollover(false); - await actions.warm.setMinAgeValue('123'); - await actions.warm.setMinAgeUnits('d'); - await actions.savePolicy(); - const latestRequest = server.requests[server.requests.length - 1]; - const warmPhaseMinAge = JSON.parse(JSON.parse(latestRequest.requestBody).body).phases.warm - .min_age; - expect(warmPhaseMinAge).toBe('123d'); - }); }); describe('policy with include and exclude', () => { @@ -458,9 +438,6 @@ describe('', () => { "max_age": "30d", "max_size": "50gb", }, - "set_priority": Object { - "priority": 100, - }, }, "min_age": "0ms", }, @@ -662,9 +639,6 @@ describe('', () => { "allocate": Object { "number_of_replicas": 123, }, - "set_priority": Object { - "priority": 50, - }, } `); }); diff --git a/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx b/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx index 6aa6c3177ca5d..d847c3a7f9766 100644 --- a/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx +++ b/x-pack/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.tsx @@ -157,7 +157,7 @@ const setPhaseIndexPriority = async ( phase: string, priority: string | number ) => { - const priorityInput = findTestSubject(rendered, `${phase}-phaseIndexPriority`); + const priorityInput = findTestSubject(rendered, `${phase}-indexPriority`); await act(async () => { priorityInput.simulate('change', { target: { value: priority } }); }); @@ -324,9 +324,6 @@ describe('edit policy', () => { max_age: '30d', max_size: '50gb', }, - set_priority: { - priority: 100, - }, }, min_age: '0ms', }, @@ -451,6 +448,7 @@ describe('edit policy', () => { const rendered = mountWithIntl(component); await noRollover(rendered); await setPolicyName(rendered, 'mypolicy'); + await setPhaseIndexPriority(rendered, 'hot', '-1'); waitForFormLibValidation(rendered); expectedErrorMessages(rendered, [i18nTexts.editPolicy.errors.nonNegativeNumberRequired]); @@ -512,7 +510,7 @@ describe('edit policy', () => { }); rendered.update(); await setPhaseAfter(rendered, 'warm', '1'); - const shrinkInput = findTestSubject(rendered, 'warm-selectedPrimaryShardCount'); + const shrinkInput = findTestSubject(rendered, 'warm-primaryShardCount'); await act(async () => { shrinkInput.simulate('change', { target: { value: '0' } }); }); @@ -529,7 +527,7 @@ describe('edit policy', () => { findTestSubject(rendered, 'warm-shrinkSwitch').simulate('click'); }); rendered.update(); - const shrinkInput = findTestSubject(rendered, 'warm-selectedPrimaryShardCount'); + const shrinkInput = findTestSubject(rendered, 'warm-primaryShardCount'); await act(async () => { shrinkInput.simulate('change', { target: { value: '-1' } }); }); @@ -845,7 +843,7 @@ describe('edit policy', () => { await activatePhase(rendered, 'warm'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); - // Assert that only the custom and off options exist + // Assert that default, custom and 'none' options exist findTestSubject(rendered, 'dataTierSelect').simulate('click'); expect(findTestSubject(rendered, 'defaultDataAllocationOption').exists()).toBeTruthy(); expect(findTestSubject(rendered, 'customDataAllocationOption').exists()).toBeTruthy(); @@ -885,7 +883,7 @@ describe('edit policy', () => { await activatePhase(rendered, 'warm'); expect(rendered.find('.euiLoadingSpinner').exists()).toBeFalsy(); - // Assert that only the custom and off options exist + // Assert that default, custom and 'none' options exist findTestSubject(rendered, 'dataTierSelect').simulate('click'); expect(findTestSubject(rendered, 'defaultDataAllocationOption').exists()).toBeFalsy(); expect(findTestSubject(rendered, 'customDataAllocationOption').exists()).toBeTruthy(); diff --git a/x-pack/plugins/index_lifecycle_management/public/application/constants/policy.ts b/x-pack/plugins/index_lifecycle_management/public/application/constants/policy.ts index a892a7a031a87..6eae59ec4e6ea 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/constants/policy.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/constants/policy.ts @@ -4,16 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - SerializedPhase, - DeletePhase, - SerializedPolicy, - RolloverAction, -} from '../../../common/types'; +import { SerializedPolicy, RolloverAction } from '../../../common/types'; -export const defaultSetPriority: string = '100'; - -export const defaultPhaseIndexPriority: string = '50'; +export const defaultIndexPriority = { + hot: '100', + warm: '50', + cold: '0', +}; export const defaultRolloverAction: RolloverAction = { max_age: '30d', @@ -30,15 +27,3 @@ export const defaultPolicy: SerializedPolicy = { }, }, }; - -export const defaultNewDeletePhase: DeletePhase = { - phaseEnabled: false, - selectedMinimumAge: '0', - selectedMinimumAgeUnits: 'd', - waitForSnapshotPolicy: '', -}; - -export const serializedPhaseInitialization: SerializedPhase = { - min_age: '0ms', - actions: {}, -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/active_highlight/active_highlight.scss b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/active_highlight/active_highlight.scss new file mode 100644 index 0000000000000..96ca0c3a61067 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/active_highlight/active_highlight.scss @@ -0,0 +1,16 @@ +.ilmActivePhaseHighlight { + border-left: $euiBorderWidthThin solid $euiColorLightShade; + height: 100%; + + &.hotPhase.active { + border-left-color: $euiColorVis9_behindText; + } + + &.warmPhase.active { + border-left-color: $euiColorVis5_behindText; + } + + &.coldPhase.active { + border-left-color: $euiColorVis1_behindText; + } +} diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/active_highlight/active_highlight.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/active_highlight/active_highlight.tsx new file mode 100644 index 0000000000000..64db9e1ec5481 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/active_highlight/active_highlight.tsx @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent } from 'react'; + +import './active_highlight.scss'; + +interface Props { + phase: 'hot' | 'warm' | 'cold'; + enabled: boolean; +} +export const ActiveHighlight: FunctionComponent = ({ phase, enabled }) => { + return
; +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_input_field/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/active_highlight/index.ts similarity index 80% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_input_field/index.ts rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/active_highlight/index.ts index 0228a524f8129..a1db0c3997edb 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_input_field/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/active_highlight/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { MinAgeInputField } from './min_age_input_field'; +export { ActiveHighlight } from './active_highlight'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts index d22206d7ae4de..960b632d70bd4 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/index.ts @@ -11,6 +11,7 @@ export { OptionalLabel } from './optional_label'; export { PolicyJsonFlyout } from './policy_json_flyout'; export { DescribedFormRow, ToggleFieldWithDescribedFormRow } from './described_form_row'; export { FieldLoadingError } from './field_loading_error'; +export { ActiveHighlight } from './active_highlight'; export { Timeline } from './timeline'; export * from './phases'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx index 4e1ec76c52a77..976f584ef4d3a 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/cold_phase/cold_phase.tsx @@ -9,28 +9,21 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { get } from 'lodash'; -import { EuiDescribedFormGroup, EuiTextColor, EuiAccordion } from '@elastic/eui'; +import { EuiTextColor } from '@elastic/eui'; -import { Phases } from '../../../../../../../common/types'; +import { useFormData } from '../../../../../../shared_imports'; -import { useFormData, UseField, ToggleField, NumericField } from '../../../../../../shared_imports'; - -import { useEditPolicyContext } from '../../../edit_policy_context'; import { useConfigurationIssues } from '../../../form'; -import { - LearnMoreLink, - ActiveBadge, - DescribedFormRow, - ToggleFieldWithDescribedFormRow, -} from '../../'; +import { LearnMoreLink, ToggleFieldWithDescribedFormRow } from '../../'; import { - MinAgeInputField, DataTierAllocationField, - SetPriorityInputField, SearchableSnapshotField, + IndexPriorityField, + ReplicasField, } from '../shared_fields'; +import { Phase } from '../phase'; const i18nTexts = { dataTierAllocation: { @@ -41,166 +34,64 @@ const i18nTexts = { }, }; -const coldProperty: keyof Phases = 'cold'; - const formFieldPaths = { enabled: '_meta.cold.enabled', searchableSnapshot: 'phases.cold.actions.searchable_snapshot.snapshot_repository', }; export const ColdPhase: FunctionComponent = () => { - const { policy } = useEditPolicyContext(); const { isUsingSearchableSnapshotInHotPhase } = useConfigurationIssues(); const [formData] = useFormData({ - watch: [formFieldPaths.enabled, formFieldPaths.searchableSnapshot], + watch: [formFieldPaths.searchableSnapshot], }); - const enabled = get(formData, formFieldPaths.enabled); const showReplicasField = get(formData, formFieldPaths.searchableSnapshot) == null; return ( -
- <> - {/* Section title group; containing min age */} - + + + {showReplicasField && } + + {/* Freeze section */} + {!isUsingSearchableSnapshotInHotPhase && ( + -

- -

{' '} - {enabled && } -
+

+ +

} - titleSize="s" description={ - <> -

- -

- - + + {' '} + + } fullWidth + titleSize="xs" + switchProps={{ + 'data-test-subj': 'freezeSwitch', + path: '_meta.cold.freezeEnabled', + }} > - {enabled && } - - {enabled && ( - <> - - - - { - /* Replicas section */ - showReplicasField && ( - - {i18n.translate('xpack.indexLifecycleMgmt.coldPhase.replicasTitle', { - defaultMessage: 'Replicas', - })} - - } - description={i18n.translate( - 'xpack.indexLifecycleMgmt.coldPhase.numberOfReplicasDescription', - { - defaultMessage: - 'Set the number of replicas. Remains the same as the previous phase by default.', - } - )} - switchProps={{ - 'data-test-subj': 'cold-setReplicasSwitch', - label: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.coldPhase.numberOfReplicas.switchLabel', - { defaultMessage: 'Set replicas' } - ), - initialValue: - policy.phases.cold?.actions?.allocate?.number_of_replicas != null, - }} - fullWidth - > - - - ) - } - - {/* Freeze section */} - {!isUsingSearchableSnapshotInHotPhase && ( - - - - } - description={ - - {' '} - - - } - fullWidth - titleSize="xs" - switchProps={{ - 'data-test-subj': 'freezeSwitch', - path: '_meta.cold.freezeEnabled', - }} - > -
- - )} - {/* Data tier allocation section */} - - - - - )} - -
+
+ + )} + + {/* Data tier allocation section */} + + + + ); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/delete_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/delete_phase.tsx index 37323b97edc92..5c43bb413eb5e 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/delete_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/delete_phase/delete_phase.tsx @@ -13,7 +13,7 @@ import { useFormData, UseField, ToggleField } from '../../../../../../shared_imp import { ActiveBadge, LearnMoreLink, OptionalLabel } from '../../index'; -import { MinAgeInputField, SnapshotPoliciesField } from '../shared_fields'; +import { MinAgeField, SnapshotPoliciesField } from '../shared_fields'; const formFieldPaths = { enabled: '_meta.delete.enabled', @@ -63,7 +63,7 @@ export const DeletePhase: FunctionComponent = () => { } fullWidth > - {enabled && } + {enabled && } {enabled ? ( { const { license } = useEditPolicyContext(); const [formData] = useFormData({ @@ -56,245 +51,210 @@ export const HotPhase: FunctionComponent = () => { const [showEmptyRolloverFieldsError, setShowEmptyRolloverFieldsError] = useState(false); return ( - <> - + -

- -

{' '} - -
+

+ {i18n.translate('xpack.indexLifecycleMgmt.hotPhase.rolloverFieldTitle', { + defaultMessage: 'Rollover', + })} +

} description={ -

- -

+ <> + +

+ {' '} + + } + docPath="ilm-rollover.html" + /> +

+
+ + path={isUsingDefaultRolloverPath}> + {(field) => ( + <> + field.setValue(e.target.checked)} + data-test-subj="useDefaultRolloverSwitch" + /> +   + + } + /> + + )} +
+ } + fullWidth > -
- - - - - {i18n.translate('xpack.indexLifecycleMgmt.hotPhase.rolloverFieldTitle', { - defaultMessage: 'Rollover', - })} - - } - description={ - <> - -

- {' '} - + path="_meta.hot.customRollover.enabled"> + {(field) => ( + <> + field.setValue(e.target.checked)} + data-test-subj="rolloverSwitch" + /> +   + } - docPath="ilm-rollover.html" /> -

-
- - path={isUsingDefaultRolloverPath}> - {(field) => ( + + )} + + {isUsingRollover && ( + <> + + {showEmptyRolloverFieldsError && ( <> - field.setValue(e.target.checked)} - data-test-subj="useDefaultRolloverSwitch" - /> -   - - } - /> + +
{i18nTexts.editPolicy.errors.rollOverConfigurationCallout.body}
+
+ )} - - - } - fullWidth - > - {isUsingDefaultRollover === false ? ( -
- path="_meta.hot.customRollover.enabled"> - {(field) => ( - <> - field.setValue(e.target.checked)} - data-test-subj="rolloverSwitch" + + + + {(field) => { + const showErrorCallout = field.errors.some( + (e) => e.code === ROLLOVER_EMPTY_VALIDATION + ); + if (showErrorCallout !== showEmptyRolloverFieldsError) { + setShowEmptyRolloverFieldsError(showErrorCallout); + } + return ( + + ); + }} + + + + -   - - } + + + + + + - - )} - - {isUsingRollover && ( - <> - - {showEmptyRolloverFieldsError && ( - <> - -
{i18nTexts.editPolicy.errors.rollOverConfigurationCallout.body}
-
- - - )} - - - - {(field) => { - const showErrorCallout = field.errors.some( - (e) => e.code === ROLLOVER_EMPTY_VALIDATION - ); - if (showErrorCallout !== showEmptyRolloverFieldsError) { - setShowEmptyRolloverFieldsError(showErrorCallout); - } - return ( - - ); - }} - - - - - - - - - - - - - - - - - - - - - - - )} -
- ) : ( -
- )} - - {isUsingRollover && ( - <> - {} - - {license.canUseSearchableSnapshot() && } - - + + + + + + + + + + + + + )} +
+ ) : ( +
)} - - - + + {isUsingRollover && ( + <> + {} + + {license.canUseSearchableSnapshot() && } + + + )} + + ); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/index.ts index 076c16e87e8d6..c2c7e6a769071 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/index.ts @@ -11,3 +11,5 @@ export { WarmPhase } from './warm_phase'; export { ColdPhase } from './cold_phase'; export { DeletePhase } from './delete_phase'; + +export { Phase } from './phase'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/phase.tsx new file mode 100644 index 0000000000000..6de18f1c1d3cb --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/phase.tsx @@ -0,0 +1,119 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent, useState } from 'react'; + +import { + EuiFlexGroup, + EuiFlexItem, + EuiPanel, + EuiTitle, + EuiSpacer, + EuiText, + EuiButtonEmpty, +} from '@elastic/eui'; +import { get } from 'lodash'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { ToggleField, UseField, useFormData } from '../../../../../shared_imports'; +import { i18nTexts } from '../../i18n_texts'; + +import { ActiveHighlight } from '../active_highlight'; +import { MinAgeField } from './shared_fields'; + +interface Props { + phase: 'hot' | 'warm' | 'cold'; +} + +export const Phase: FunctionComponent = ({ children, phase }) => { + const enabledPath = `_meta.${phase}.enabled`; + const [formData] = useFormData({ + watch: [enabledPath], + }); + + // hot phase is always enabled + const enabled = get(formData, enabledPath) || phase === 'hot'; + + const [isShowingSettings, setShowingSettings] = useState(false); + return ( + + + + + + + + + + {phase !== 'hot' && ( + + + + )} + + +

{i18nTexts.editPolicy.titles[phase]}

+
+
+
+
+ {enabled && ( + + + + {phase !== 'hot' && } + + + { + setShowingSettings(!isShowingSettings); + }} + size="xs" + iconType="controlsVertical" + iconSide="left" + aria-controls={`${phase}-phaseContent`} + > + + + + + + )} +
+ + + {i18nTexts.editPolicy.descriptions[phase]} + + + {enabled && ( +
+ + {children} +
+ )} +
+
+
+ ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/forcemerge_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/forcemerge_field.tsx index 8776dbbbc7553..8d6807c90dae8 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/forcemerge_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/forcemerge_field.tsx @@ -6,9 +6,8 @@ import React, { useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiSpacer, EuiTextColor } from '@elastic/eui'; -import { UseField, ToggleField, NumericField } from '../../../../../../shared_imports'; +import { UseField, CheckBoxField, NumericField } from '../../../../../../shared_imports'; import { i18nTexts } from '../../../i18n_texts'; @@ -38,50 +37,43 @@ export const ForcemergeField: React.FunctionComponent = ({ phase }) => { } description={ - + <> {' '} - + } titleSize="xs" fullWidth switchProps={{ - 'aria-label': i18nTexts.editPolicy.forceMergeEnabledFieldLabel, - 'data-test-subj': `${phase}-forceMergeSwitch`, - 'aria-controls': 'forcemergeContent', label: i18nTexts.editPolicy.forceMergeEnabledFieldLabel, + 'data-test-subj': `${phase}-forceMergeSwitch`, initialValue: initialToggleValue, }} > - -
- - -
+ + ); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/index.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/index.ts index 15167672265fd..710df7e95f8fc 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/index.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/index.ts @@ -8,9 +8,7 @@ export { DataTierAllocationField } from './data_tier_allocation_field'; export { ForcemergeField } from './forcemerge_field'; -export { SetPriorityInputField } from './set_priority_input_field'; - -export { MinAgeInputField } from './min_age_input_field'; +export { MinAgeField } from './min_age_field'; export { SnapshotPoliciesField } from './snapshot_policies_field'; @@ -19,3 +17,7 @@ export { ShrinkField } from './shrink_field'; export { SearchableSnapshotField } from './searchable_snapshot_field'; export { ReadonlyField } from './readonly_field'; + +export { ReplicasField } from './replicas_field'; + +export { IndexPriorityField } from './index_priority_field'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/set_priority_input_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/index_priority_field.tsx similarity index 50% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/set_priority_input_field.tsx rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/index_priority_field.tsx index 328587a379b76..570033812c247 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/set_priority_input_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/index_priority_field.tsx @@ -4,23 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FunctionComponent } from 'react'; +import React, { FunctionComponent, useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiTextColor, EuiDescribedFormGroup } from '@elastic/eui'; - -import { Phases } from '../../../../../../../common/types'; +import { i18n } from '@kbn/i18n'; +import { EuiSpacer, EuiTextColor } from '@elastic/eui'; import { UseField, NumericField } from '../../../../../../shared_imports'; - -import { LearnMoreLink } from '../..'; +import { LearnMoreLink, DescribedFormRow } from '../..'; +import { useEditPolicyContext } from '../../../edit_policy_context'; interface Props { - phase: keyof Phases & string; + phase: 'hot' | 'warm' | 'cold'; } -export const SetPriorityInputField: FunctionComponent = ({ phase }) => { +export const IndexPriorityField: FunctionComponent = ({ phase }) => { + const { policy, isNewPolicy } = useEditPolicyContext(); + + const initialToggleValue = useMemo(() => { + return ( + isNewPolicy || // enable index priority for new policies + !policy.phases[phase]?.actions || // enable index priority for new phases + policy.phases[phase]?.actions?.set_priority != null // enable index priority if it's set + ); + }, [isNewPolicy, policy.phases, phase]); + return ( - = ({ phase }) => { } titleSize="xs" fullWidth + switchProps={{ + label: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.indexPriority.indexPriorityEnabledFieldLabel', + { + defaultMessage: 'Set index priority', + } + ), + 'data-test-subj': `${phase}-indexPrioritySwitch`, + initialValue: initialToggleValue, + }} > + - + ); }; diff --git a/x-pack/test/upgrade_assistant_integration/services/index.js b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_field/index.ts similarity index 83% rename from x-pack/test/upgrade_assistant_integration/services/index.js rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_field/index.ts index 83424ad1eb50c..43ef0cf5d9b71 100644 --- a/x-pack/test/upgrade_assistant_integration/services/index.js +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_field/index.ts @@ -4,4 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { LegacyEsProvider } from './legacy_es'; +export { MinAgeField } from './min_age_field'; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_field/min_age_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_field/min_age_field.tsx new file mode 100644 index 0000000000000..8a84b7fa0e762 --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_field/min_age_field.tsx @@ -0,0 +1,158 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { FunctionComponent } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { + EuiFieldNumber, + EuiFieldNumberProps, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiSelect, + EuiText, +} from '@elastic/eui'; + +import { UseField, getFieldValidityAndErrorMessage } from '../../../../../../../shared_imports'; + +import { getUnitsAriaLabelForPhase, getTimingLabelForPhase } from './util'; + +type PhaseWithMinAgeAction = 'warm' | 'cold' | 'delete'; + +const i18nTexts = { + daysOptionLabel: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.daysOptionLabel', { + defaultMessage: 'days', + }), + + hoursOptionLabel: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.hoursOptionLabel', { + defaultMessage: 'hours', + }), + minutesOptionLabel: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.minutesOptionLabel', { + defaultMessage: 'minutes', + }), + + secondsOptionLabel: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.secondsOptionLabel', { + defaultMessage: 'seconds', + }), + millisecondsOptionLabel: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.milliSecondsOptionLabel', + { + defaultMessage: 'milliseconds', + } + ), + + microsecondsOptionLabel: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.microSecondsOptionLabel', + { + defaultMessage: 'microseconds', + } + ), + + nanosecondsOptionLabel: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.nanoSecondsOptionLabel', + { + defaultMessage: 'nanoseconds', + } + ), +}; + +interface Props { + phase: PhaseWithMinAgeAction; +} + +export const MinAgeField: FunctionComponent = ({ phase }): React.ReactElement => { + return ( + + {(field) => { + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); + return ( + + + + + + + + + + + + + + + {(unitField) => { + const { isInvalid: isUnitFieldInvalid } = getFieldValidityAndErrorMessage( + unitField + ); + return ( + { + unitField.setValue(e.target.value); + }} + isInvalid={isUnitFieldInvalid} + append={'old'} + data-test-subj={`${phase}-selectedMinimumAgeUnits`} + aria-label={getUnitsAriaLabelForPhase(phase)} + options={[ + { + value: 'd', + text: i18nTexts.daysOptionLabel, + }, + { + value: 'h', + text: i18nTexts.hoursOptionLabel, + }, + { + value: 'm', + text: i18nTexts.minutesOptionLabel, + }, + { + value: 's', + text: i18nTexts.secondsOptionLabel, + }, + { + value: 'ms', + text: i18nTexts.millisecondsOptionLabel, + }, + { + value: 'micros', + text: i18nTexts.microsecondsOptionLabel, + }, + { + value: 'nanos', + text: i18nTexts.nanosecondsOptionLabel, + }, + ]} + /> + ); + }} + + + + + + + ); + }} + + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_input_field/util.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_field/util.ts similarity index 100% rename from x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_input_field/util.ts rename to x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_field/util.ts diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_input_field/min_age_input_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_input_field/min_age_input_field.tsx deleted file mode 100644 index 59086ce572252..0000000000000 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/min_age_input_field/min_age_input_field.tsx +++ /dev/null @@ -1,205 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import React, { FunctionComponent } from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; - -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; - -import { UseField, NumericField, SelectField } from '../../../../../../../shared_imports'; - -import { LearnMoreLink } from '../../../learn_more_link'; -import { useConfigurationIssues } from '../../../../form'; - -import { getUnitsAriaLabelForPhase, getTimingLabelForPhase } from './util'; - -type PhaseWithMinAgeAction = 'warm' | 'cold' | 'delete'; - -interface Props { - phase: PhaseWithMinAgeAction; -} - -export const MinAgeInputField: FunctionComponent = ({ phase }): React.ReactElement => { - const { isUsingRollover: rolloverEnabled } = useConfigurationIssues(); - - let daysOptionLabel; - let hoursOptionLabel; - let minutesOptionLabel; - let secondsOptionLabel; - let millisecondsOptionLabel; - let microsecondsOptionLabel; - let nanosecondsOptionLabel; - - if (rolloverEnabled) { - daysOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.rolloverDaysOptionLabel', - { - defaultMessage: 'days from rollover', - } - ); - - hoursOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.rolloverHoursOptionLabel', - { - defaultMessage: 'hours from rollover', - } - ); - minutesOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.rolloverMinutesOptionLabel', - { - defaultMessage: 'minutes from rollover', - } - ); - - secondsOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.rolloverSecondsOptionLabel', - { - defaultMessage: 'seconds from rollover', - } - ); - millisecondsOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.rolloverMilliSecondsOptionLabel', - { - defaultMessage: 'milliseconds from rollover', - } - ); - - microsecondsOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.rolloverMicroSecondsOptionLabel', - { - defaultMessage: 'microseconds from rollover', - } - ); - - nanosecondsOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.rolloverNanoSecondsOptionLabel', - { - defaultMessage: 'nanoseconds from rollover', - } - ); - } else { - daysOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.creationDaysOptionLabel', - { - defaultMessage: 'days from index creation', - } - ); - - hoursOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.creationHoursOptionLabel', - { - defaultMessage: 'hours from index creation', - } - ); - - minutesOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.creationMinutesOptionLabel', - { - defaultMessage: 'minutes from index creation', - } - ); - - secondsOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.creationSecondsOptionLabel', - { - defaultMessage: 'seconds from index creation', - } - ); - - millisecondsOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.creationMilliSecondsOptionLabel', - { - defaultMessage: 'milliseconds from index creation', - } - ); - - microsecondsOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.creationMicroSecondsOptionLabel', - { - defaultMessage: 'microseconds from index creation', - } - ); - - nanosecondsOptionLabel = i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.creationNanoSecondsOptionLabel', - { - defaultMessage: 'nanoseconds from index creation', - } - ); - } - - return ( - - - - } - /> - ), - euiFieldProps: { - 'data-test-subj': `${phase}-selectedMinimumAge`, - min: 0, - }, - }} - /> - - - - - - ); -}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/replicas_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/replicas_field.tsx new file mode 100644 index 0000000000000..6d8e019ff8a0c --- /dev/null +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/replicas_field.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FunctionComponent } from 'react'; +import { i18n } from '@kbn/i18n'; + +import { UseField, NumericField } from '../../../../../../shared_imports'; +import { useEditPolicyContext } from '../../../edit_policy_context'; +import { DescribedFormRow } from '../../described_form_row'; + +interface Props { + phase: 'warm' | 'cold'; +} + +export const ReplicasField: FunctionComponent = ({ phase }) => { + const { policy } = useEditPolicyContext(); + const initialValue = policy.phases[phase]?.actions?.allocate?.number_of_replicas != null; + return ( + + {i18n.translate('xpack.indexLifecycleMgmt.numberOfReplicas.formRowTitle', { + defaultMessage: 'Replicas', + })} + + } + description={i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.numberOfReplicas.formRowDescription', + { + defaultMessage: + 'Set the number of replicas. Remains the same as the previous phase by default.', + } + )} + switchProps={{ + 'data-test-subj': `${phase}-setReplicasSwitch`, + label: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.numberOfReplicas.switchLabel', { + defaultMessage: 'Set replicas', + }), + initialValue, + }} + fullWidth + > + + + ); +}; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/shrink_field.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/shrink_field.tsx index c5fc31d9839bd..da200e9e68d17 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/shrink_field.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/shared_fields/shrink_field.tsx @@ -42,32 +42,28 @@ export const ShrinkField: FunctionComponent = ({ phase }) => { } titleSize="xs" switchProps={{ - 'aria-controls': 'shrinkContent', 'data-test-subj': `${phase}-shrinkSwitch`, label: i18nTexts.editPolicy.shrinkLabel, - 'aria-label': i18nTexts.editPolicy.shrinkLabel, initialValue: Boolean(policy.phases[phase]?.actions?.shrink), }} fullWidth > -
- - - - - - -
+ + + + + + ); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx index 77078e94d7e98..47255da08df72 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/components/phases/warm_phase/warm_phase.tsx @@ -5,30 +5,21 @@ */ import React, { FunctionComponent } from 'react'; -import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { get } from 'lodash'; -import { EuiSpacer, EuiDescribedFormGroup, EuiAccordion } from '@elastic/eui'; - -import { useFormData, UseField, ToggleField, NumericField } from '../../../../../../shared_imports'; - -import { Phases } from '../../../../../../../common/types'; - -import { useEditPolicyContext } from '../../../edit_policy_context'; import { useConfigurationIssues } from '../../../form'; -import { ActiveBadge, DescribedFormRow } from '../../'; - import { - MinAgeInputField, ForcemergeField, - SetPriorityInputField, + IndexPriorityField, DataTierAllocationField, ShrinkField, ReadonlyField, + ReplicasField, } from '../shared_fields'; +import { Phase } from '../phase'; + const i18nTexts = { dataTierAllocation: { description: i18n.translate('xpack.indexLifecycleMgmt.warmPhase.dataTier.description', { @@ -37,153 +28,26 @@ const i18nTexts = { }, }; -const warmProperty: keyof Phases = 'warm'; - -const formFieldPaths = { - enabled: '_meta.warm.enabled', - warmPhaseOnRollover: '_meta.warm.warmPhaseOnRollover', -}; - export const WarmPhase: FunctionComponent = () => { - const { policy } = useEditPolicyContext(); - const { isUsingSearchableSnapshotInHotPhase, isUsingRollover } = useConfigurationIssues(); - const [formData] = useFormData({ - watch: [formFieldPaths.enabled, formFieldPaths.warmPhaseOnRollover], - }); - - const enabled = get(formData, formFieldPaths.enabled); - const warmPhaseOnRollover = get(formData, formFieldPaths.warmPhaseOnRollover); + const { isUsingSearchableSnapshotInHotPhase } = useConfigurationIssues(); return ( -
- <> - -

- -

{' '} - {enabled && } -
- } - titleSize="s" - description={ - <> -

- -

- - - } - fullWidth - > - <> - {enabled && ( - <> - {isUsingRollover && ( - - )} - {(!warmPhaseOnRollover || !isUsingRollover) && ( - <> - - - - )} - - )} - - + + - {enabled && ( - - - {i18n.translate('xpack.indexLifecycleMgmt.warmPhase.replicasTitle', { - defaultMessage: 'Replicas', - })} - - } - description={i18n.translate( - 'xpack.indexLifecycleMgmt.warmPhase.numberOfReplicasDescription', - { - defaultMessage: - 'Set the number of replicas. Remains the same as the previous phase by default.', - } - )} - switchProps={{ - 'data-test-subj': 'warm-setReplicasSwitch', - label: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.warmPhase.numberOfReplicas.switchLabel', - { defaultMessage: 'Set replicas' } - ), - initialValue: policy.phases.warm?.actions?.allocate?.number_of_replicas != null, - }} - fullWidth - > - - + {!isUsingSearchableSnapshotInHotPhase && } - {!isUsingSearchableSnapshotInHotPhase && } + {!isUsingSearchableSnapshotInHotPhase && } - {!isUsingSearchableSnapshotInHotPhase && } + - + {/* Data tier allocation section */} + - {/* Data tier allocation section */} - - - - )} - -
+ + ); }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx index 228a0f9fdb942..b1cf41773de3c 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/edit_policy.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, useEffect, useState, useMemo } from 'react'; +import React, { Fragment, useEffect, useMemo, useState } from 'react'; import { get } from 'lodash'; import { RouteComponentProps } from 'react-router-dom'; @@ -16,7 +16,6 @@ import { i18n } from '@kbn/i18n'; import { EuiButton, EuiButtonEmpty, - EuiDescribedFormGroup, EuiFlexGroup, EuiFlexItem, EuiFormRow, @@ -24,33 +23,35 @@ import { EuiPage, EuiPageBody, EuiPageContent, + EuiPageContentHeader, + EuiPageContentHeaderSection, EuiSpacer, EuiSwitch, EuiText, EuiTitle, } from '@elastic/eui'; -import { useForm, UseField, TextField, useFormData } from '../../../shared_imports'; +import { TextField, UseField, useForm, useFormData } from '../../../shared_imports'; import { toasts } from '../../services/notification'; import { savePolicy } from './save_policy'; import { - LearnMoreLink, - PolicyJsonFlyout, ColdPhase, DeletePhase, HotPhase, + PolicyJsonFlyout, WarmPhase, Timeline, } from './components'; -import { schema, deserializer, createSerializer, createPolicyNameValidations, Form } from './form'; +import { createPolicyNameValidations, createSerializer, deserializer, Form, schema } from './form'; import { useEditPolicyContext } from './edit_policy_context'; import { FormInternal } from './types'; +import { createDocLink } from '../../services/documentation'; export interface Props { history: RouteComponentProps['history']; @@ -75,7 +76,7 @@ export const EditPolicy: React.FunctionComponent = ({ history }) => { return createSerializer(isNewPolicy ? undefined : currentPolicy); }, [isNewPolicy, currentPolicy]); - const [saveAsNew, setSaveAsNew] = useState(isNewPolicy); + const [saveAsNew, setSaveAsNew] = useState(false); const originalPolicyName: string = isNewPolicy ? '' : policyName!; const { form } = useForm({ @@ -116,7 +117,7 @@ export const EditPolicy: React.FunctionComponent = ({ history }) => { ); } else { const success = await savePolicy( - { ...policy, name: saveAsNew ? currentPolicyName : originalPolicyName }, + { ...policy, name: saveAsNew || isNewPolicy ? currentPolicyName : originalPolicyName }, isNewPolicy || saveAsNew ); if (success) { @@ -132,216 +133,187 @@ export const EditPolicy: React.FunctionComponent = ({ history }) => { return ( - - -

- {isNewPolicy - ? i18n.translate('xpack.indexLifecycleMgmt.editPolicy.createPolicyMessage', { - defaultMessage: 'Create an index lifecycle policy', - }) - : i18n.translate('xpack.indexLifecycleMgmt.editPolicy.editPolicyMessage', { - defaultMessage: 'Edit index lifecycle policy {originalPolicyName}', - values: { originalPolicyName }, - })} -

-
- -
-
- - -

- {' '} - + + + +

+ {isNewPolicy + ? i18n.translate('xpack.indexLifecycleMgmt.editPolicy.createPolicyMessage', { + defaultMessage: 'Create Policy', + }) + : i18n.translate('xpack.indexLifecycleMgmt.editPolicy.editPolicyMessage', { + defaultMessage: 'Edit Policy {originalPolicyName}', + values: { originalPolicyName }, + })} +

+ + + + + + + + + + {isNewPolicy ? null : ( + + +

+ - } - /> -

-
- - - - {isNewPolicy ? null : ( - - -

- - - - .{' '} - -

-
- - - - { - setSaveAsNew(e.target.checked); - }} - label={ - - - - } /> - -
- )} - - {saveAsNew || isNewPolicy ? ( - - +

+
+ + + + { + setSaveAsNew(e.target.checked); + }} + label={ + -
- } - titleSize="s" - fullWidth - > - - path={policyNamePath} - config={{ - label: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.policyNameLabel', { - defaultMessage: 'Policy name', - }), - helpText: i18n.translate( - 'xpack.indexLifecycleMgmt.editPolicy.validPolicyNameMessage', - { - defaultMessage: - 'A policy name cannot start with an underscore and cannot contain a comma or a space.', - } - ), - validations: policyNameValidations, - }} - component={TextField} - componentProps={{ - fullWidth: false, - euiFieldProps: { - 'data-test-subj': 'policyNameField', - }, - }} + } /> - - ) : null} - - + + + )} + + {saveAsNew || isNewPolicy ? ( + + path={policyNamePath} + config={{ + label: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.policyNameLabel', { + defaultMessage: 'Policy name', + }), + helpText: i18n.translate( + 'xpack.indexLifecycleMgmt.editPolicy.validPolicyNameMessage', + { + defaultMessage: + 'A policy name cannot start with an underscore and cannot contain a comma or a space.', + } + ), + validations: policyNameValidations, + }} + component={TextField} + componentProps={{ + fullWidth: false, + euiFieldProps: { + 'data-test-subj': 'policyNameField', + }, + }} + /> + ) : null} - + - + - + - + - + - + - + - + - + - + - - - - - - {saveAsNew ? ( - - ) : ( - - )} - - + - - - - - - - - - - - {isShowingPolicyJsonFlyout ? ( - - ) : ( + + + + {isShowingPolicyJsonFlyout ? ( + + ) : ( + + )} + + + + + + + - )} - - - - - {isShowingPolicyJsonFlyout ? ( - setIsShowingPolicyJsonFlyout(false)} - /> - ) : null} - -
+ + + + + + {saveAsNew ? ( + + ) : ( + + )} + + + + + + + {isShowingPolicyJsonFlyout ? ( + setIsShowingPolicyJsonFlyout(false)} + /> + ) : null} + diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer_and_serializer.test.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer_and_serializer.test.ts index b5abf51c29028..44512c2a511ec 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer_and_serializer.test.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/deserializer_and_serializer.test.ts @@ -260,15 +260,6 @@ describe('deserializer and serializer', () => { expect(result.phases.hot!.actions.readonly).toBeUndefined(); }); - it('removes min_age from warm when rollover is enabled', () => { - formInternal._meta.hot.customRollover.enabled = true; - formInternal._meta.warm.warmPhaseOnRollover = true; - - const result = serializer(formInternal); - - expect(result.phases.warm!.min_age).toBeUndefined(); - }); - it('adds default rollover configuration when enabled, but previously not configured', () => { delete formInternal.phases.hot!.actions.rollover; formInternal._meta.hot.isUsingDefaultRollover = true; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts index 4bdf902d27b6d..9883c2de0685d 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/schema.ts @@ -7,13 +7,13 @@ import { i18n } from '@kbn/i18n'; import { FormSchema, fieldValidators } from '../../../../shared_imports'; -import { defaultSetPriority, defaultPhaseIndexPriority } from '../../../constants'; +import { defaultIndexPriority } from '../../../constants'; import { ROLLOVER_FORM_PATHS } from '../constants'; -const rolloverFormPaths = Object.values(ROLLOVER_FORM_PATHS); - import { FormInternal } from '../types'; +const rolloverFormPaths = Object.values(ROLLOVER_FORM_PATHS); + import { ifExistsNumberGreaterThanZero, ifExistsNumberNonNegative, @@ -54,7 +54,6 @@ export const schema: FormSchema = { }, bestCompression: { label: i18nTexts.editPolicy.bestCompressionFieldLabel, - helpText: i18nTexts.editPolicy.bestCompressionFieldHelpText, }, readonlyEnabled: { defaultValue: false, @@ -69,18 +68,11 @@ export const schema: FormSchema = { { defaultMessage: 'Activate warm phase' } ), }, - warmPhaseOnRollover: { - defaultValue: true, - label: i18n.translate('xpack.indexLifecycleMgmt.warmPhase.moveToWarmPhaseOnRolloverLabel', { - defaultMessage: 'Move to warm phase on rollover', - }), - }, minAgeUnit: { defaultValue: 'ms', }, bestCompression: { label: i18nTexts.editPolicy.bestCompressionFieldLabel, - helpText: i18nTexts.editPolicy.bestCompressionFieldHelpText, }, dataTierAllocationType: { label: i18nTexts.editPolicy.allocationTypeOptionsFieldLabel, @@ -218,9 +210,14 @@ export const schema: FormSchema = { }, set_priority: { priority: { - defaultValue: defaultSetPriority as any, - label: i18nTexts.editPolicy.setPriorityFieldLabel, - validations: [{ validator: ifExistsNumberNonNegative }], + defaultValue: defaultIndexPriority.hot as any, + label: i18nTexts.editPolicy.indexPriorityFieldLabel, + validations: [ + { + validator: emptyField(i18nTexts.editPolicy.errors.numberRequired), + }, + { validator: ifExistsNumberNonNegative }, + ], serializer: serializers.stringToNumber, }, }, @@ -239,9 +236,12 @@ export const schema: FormSchema = { allocate: { number_of_replicas: { label: i18n.translate('xpack.indexLifecycleMgmt.warmPhase.numberOfReplicasLabel', { - defaultMessage: 'Number of replicas (optional)', + defaultMessage: 'Number of replicas', }), validations: [ + { + validator: emptyField(i18nTexts.editPolicy.errors.numberRequired), + }, { validator: ifExistsNumberNonNegative, }, @@ -289,9 +289,14 @@ export const schema: FormSchema = { }, set_priority: { priority: { - defaultValue: defaultPhaseIndexPriority as any, - label: i18nTexts.editPolicy.setPriorityFieldLabel, - validations: [{ validator: ifExistsNumberNonNegative }], + defaultValue: defaultIndexPriority.warm as any, + label: i18nTexts.editPolicy.indexPriorityFieldLabel, + validations: [ + { + validator: emptyField(i18nTexts.editPolicy.errors.numberRequired), + }, + { validator: ifExistsNumberNonNegative }, + ], serializer: serializers.stringToNumber, }, }, @@ -310,9 +315,12 @@ export const schema: FormSchema = { allocate: { number_of_replicas: { label: i18n.translate('xpack.indexLifecycleMgmt.coldPhase.numberOfReplicasLabel', { - defaultMessage: 'Number of replicas (optional)', + defaultMessage: 'Number of replicas', }), validations: [ + { + validator: emptyField(i18nTexts.editPolicy.errors.numberRequired), + }, { validator: ifExistsNumberNonNegative, }, @@ -322,9 +330,14 @@ export const schema: FormSchema = { }, set_priority: { priority: { - defaultValue: '0' as any, - label: i18nTexts.editPolicy.setPriorityFieldLabel, - validations: [{ validator: ifExistsNumberNonNegative }], + defaultValue: defaultIndexPriority.cold as any, + label: i18nTexts.editPolicy.indexPriorityFieldLabel, + validations: [ + { + validator: emptyField(i18nTexts.editPolicy.errors.numberRequired), + }, + { validator: ifExistsNumberNonNegative }, + ], serializer: serializers.stringToNumber, }, }, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serializer.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serializer.ts index f718073afa352..f7cdecdc352fb 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serializer.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/form/serializer/serializer.ts @@ -20,9 +20,7 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => ( ): SerializedPolicy => { const { _meta, ...updatedPolicy } = data; - if (!updatedPolicy.phases || !updatedPolicy.phases.hot) { - updatedPolicy.phases = { hot: { actions: {} } }; - } + updatedPolicy.phases = { hot: { actions: {} }, ...updatedPolicy.phases }; return produce(originalPolicy ?? defaultPolicy, (draft) => { // Copy over all updated fields @@ -32,7 +30,7 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => ( * Important shared values for serialization */ const isUsingRollover = Boolean( - _meta.hot.isUsingDefaultRollover || _meta.hot.customRollover.enabled + _meta.hot?.isUsingDefaultRollover || _meta.hot?.customRollover.enabled ); // Next copy over all meta fields and delete any fields that have been removed @@ -53,7 +51,7 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => ( * HOT PHASE ROLLOVER */ if (isUsingRollover) { - if (_meta.hot.isUsingDefaultRollover) { + if (_meta.hot?.isUsingDefaultRollover) { hotPhaseActions.rollover = cloneDeep(defaultRolloverAction); } else { // Rollover may not exist if editing an existing policy with initially no rollover configured @@ -63,7 +61,7 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => ( // We are using user-defined, custom rollover settings. if (updatedPolicy.phases.hot!.actions.rollover?.max_age) { - hotPhaseActions.rollover.max_age = `${hotPhaseActions.rollover.max_age}${_meta.hot.customRollover.maxAgeUnit}`; + hotPhaseActions.rollover.max_age = `${hotPhaseActions.rollover.max_age}${_meta.hot?.customRollover.maxAgeUnit}`; } else { delete hotPhaseActions.rollover.max_age; } @@ -73,7 +71,7 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => ( } if (updatedPolicy.phases.hot!.actions.rollover?.max_size) { - hotPhaseActions.rollover.max_size = `${hotPhaseActions.rollover.max_size}${_meta.hot.customRollover.maxStorageSizeUnit}`; + hotPhaseActions.rollover.max_size = `${hotPhaseActions.rollover.max_size}${_meta.hot?.customRollover.maxStorageSizeUnit}`; } else { delete hotPhaseActions.rollover.max_size; } @@ -84,20 +82,20 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => ( */ if (!updatedPolicy.phases.hot!.actions?.forcemerge) { delete hotPhaseActions.forcemerge; - } else if (_meta.hot.bestCompression) { + } else if (_meta.hot?.bestCompression) { hotPhaseActions.forcemerge!.index_codec = 'best_compression'; } else { delete hotPhaseActions.forcemerge!.index_codec; } - if (_meta.hot.bestCompression && hotPhaseActions.forcemerge) { + if (_meta.hot?.bestCompression && hotPhaseActions.forcemerge) { hotPhaseActions.forcemerge.index_codec = 'best_compression'; } /** * HOT PHASE READ-ONLY */ - if (_meta.hot.readonlyEnabled) { + if (_meta.hot?.readonlyEnabled) { hotPhaseActions.readonly = hotPhaseActions.readonly ?? {}; } else { delete hotPhaseActions.readonly; @@ -140,17 +138,9 @@ export const createSerializer = (originalPolicy?: SerializedPolicy) => ( /** * WARM PHASE MIN AGE * - * If warm phase on rollover is enabled, delete min age field - * An index lifecycle switches to warm phase when rollover occurs, so you cannot specify a warm phase time - * They are mutually exclusive */ - if ( - (!isUsingRollover || !_meta.warm.warmPhaseOnRollover) && - updatedPolicy.phases.warm?.min_age - ) { + if (updatedPolicy.phases.warm?.min_age) { warmPhase.min_age = `${updatedPolicy.phases.warm!.min_age}${_meta.warm.minAgeUnit}`; - } else { - delete warmPhase.min_age; } /** diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts index f30a40fdd2bb9..71085a6d7a2b8 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/i18n_texts.ts @@ -40,10 +40,10 @@ export const i18nTexts = { defaultMessage: 'Number of segments', } ), - setPriorityFieldLabel: i18n.translate( + indexPriorityFieldLabel: i18n.translate( 'xpack.indexLifecycleMgmt.editPolicy.indexPriorityLabel', { - defaultMessage: 'Index priority (optional)', + defaultMessage: 'Index priority', } ), bestCompressionFieldLabel: i18n.translate( @@ -170,5 +170,30 @@ export const i18nTexts = { } ), }, + titles: { + hot: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.hotPhase.hotPhaseTitle', { + defaultMessage: 'Hot phase', + }), + warm: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.warmPhase.warmPhaseTitle', { + defaultMessage: 'Warm phase', + }), + cold: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.coldPhase.coldPhaseTitle', { + defaultMessage: 'Cold phase', + }), + }, + descriptions: { + hot: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.hotPhase.hotPhaseDescription', { + defaultMessage: + 'This phase is required. You are actively querying and writing to your index. For faster updates, you can roll over the index when it gets too big or too old.', + }), + warm: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.warmPhase.warmPhaseDescription', { + defaultMessage: + 'You are still querying your index, but it is read-only. You can allocate shards to less performant hardware. For faster searches, you can reduce the number of shards and force merge segments.', + }), + cold: i18n.translate('xpack.indexLifecycleMgmt.editPolicy.coldPhase.coldPhaseDescription', { + defaultMessage: + 'You are querying your index less frequently, so you can allocate shards on significantly less performant hardware. Because your queries are slower, you can reduce the number of replicas.', + }), + }, }, }; diff --git a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/lib/absolute_timing_to_relative_timing.ts b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/lib/absolute_timing_to_relative_timing.ts index c77b171a56bed..2f37608b2d7ae 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/lib/absolute_timing_to_relative_timing.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/sections/edit_policy/lib/absolute_timing_to_relative_timing.ts @@ -70,9 +70,10 @@ export interface PhaseAgeInMilliseconds { const phaseOrder: Phase[] = ['hot', 'warm', 'cold', 'delete']; const getMinAge = (phase: MinAgePhase, formData: FormInternal) => ({ - min_age: formData.phases[phase]?.min_age - ? formData.phases[phase]!.min_age! + formData._meta[phase].minAgeUnit - : '0ms', + min_age: + formData.phases && formData.phases[phase]?.min_age + ? formData.phases[phase]!.min_age! + formData._meta[phase].minAgeUnit + : '0ms', }); const formDataToAbsoluteTimings = (formData: FormInternal): AbsoluteTimings => { diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.test.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.test.ts index 2f1c7798e7a4d..4b22d1c8448b5 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.test.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.test.ts @@ -9,7 +9,7 @@ import { UIM_CONFIG_WARM_PHASE, UIM_CONFIG_SET_PRIORITY, UIM_CONFIG_FREEZE_INDEX, - defaultPhaseIndexPriority, + defaultIndexPriority, } from '../constants/'; import { getUiMetricsForPhases } from './ui_metric'; @@ -22,7 +22,7 @@ describe('getUiMetricsForPhases', () => { min_age: '0ms', actions: { set_priority: { - priority: parseInt(defaultPhaseIndexPriority, 10), + priority: parseInt(defaultIndexPriority.cold, 10), }, }, }, @@ -37,7 +37,7 @@ describe('getUiMetricsForPhases', () => { min_age: '0ms', actions: { set_priority: { - priority: parseInt(defaultPhaseIndexPriority, 10), + priority: parseInt(defaultIndexPriority.warm, 10), }, }, }, @@ -52,7 +52,7 @@ describe('getUiMetricsForPhases', () => { min_age: '0ms', actions: { set_priority: { - priority: parseInt(defaultPhaseIndexPriority, 10) + 1, + priority: parseInt(defaultIndexPriority.warm, 10) + 1, }, }, }, @@ -68,7 +68,7 @@ describe('getUiMetricsForPhases', () => { actions: { freeze: {}, set_priority: { - priority: parseInt(defaultPhaseIndexPriority, 10), + priority: parseInt(defaultIndexPriority.cold, 10), }, }, }, diff --git a/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts b/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts index bcf4b6cf1da0d..ffdcf927a5b67 100644 --- a/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts +++ b/x-pack/plugins/index_lifecycle_management/public/application/services/ui_metric.ts @@ -19,8 +19,7 @@ import { UIM_CONFIG_FREEZE_INDEX, UIM_CONFIG_SET_PRIORITY, UIM_CONFIG_WARM_PHASE, - defaultSetPriority, - defaultPhaseIndexPriority, + defaultIndexPriority, } from '../constants'; import { Phases } from '../../../common/types'; @@ -50,17 +49,17 @@ export function getUiMetricsForPhases(phases: Phases): string[] { const isHotPhasePriorityChanged = phases.hot && phases.hot.actions.set_priority && - phases.hot.actions.set_priority.priority !== parseInt(defaultSetPriority, 10); + phases.hot.actions.set_priority.priority !== parseInt(defaultIndexPriority.hot, 10); const isWarmPhasePriorityChanged = phases.warm && phases.warm.actions.set_priority && - phases.warm.actions.set_priority.priority !== parseInt(defaultPhaseIndexPriority, 10); + phases.warm.actions.set_priority.priority !== parseInt(defaultIndexPriority.warm, 10); const isColdPhasePriorityChanged = phases.cold && phases.cold.actions.set_priority && - phases.cold.actions.set_priority.priority !== parseInt(defaultPhaseIndexPriority, 10); + phases.cold.actions.set_priority.priority !== parseInt(defaultIndexPriority.cold, 10); // If the priority is different than the default, we'll consider it a user interaction, // even if the user has set it to undefined. return ( diff --git a/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts b/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts index 4cb5d95239408..fdb25dec6f1fd 100644 --- a/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts +++ b/x-pack/plugins/index_lifecycle_management/public/shared_imports.ts @@ -31,6 +31,7 @@ export { SuperSelectField, ComboBoxField, TextField, + CheckBoxField, } from '../../../../src/plugins/es_ui_shared/static/forms/components'; export { attemptToURIDecode } from '../../../../src/plugins/es_ui_shared/public'; diff --git a/x-pack/plugins/infra/common/http_api/log_analysis/results/log_entry_anomalies.ts b/x-pack/plugins/infra/common/http_api/log_analysis/results/log_entry_anomalies.ts index 62b76a0ae475e..614684d29ae76 100644 --- a/x-pack/plugins/infra/common/http_api/log_analysis/results/log_entry_anomalies.ts +++ b/x-pack/plugins/infra/common/http_api/log_analysis/results/log_entry_anomalies.ts @@ -7,48 +7,17 @@ import * as rt from 'io-ts'; import { timeRangeRT, routeTimingMetadataRT } from '../../shared'; +import { + logEntryAnomalyRT, + logEntryAnomalyDatasetsRT, + anomaliesSortRT, + paginationRT, + paginationCursorRT, +} from '../../../log_analysis'; export const LOG_ANALYSIS_GET_LOG_ENTRY_ANOMALIES_PATH = '/api/infra/log_analysis/results/log_entry_anomalies'; -// [Sort field value, tiebreaker value] -const paginationCursorRT = rt.tuple([ - rt.union([rt.string, rt.number]), - rt.union([rt.string, rt.number]), -]); - -export type PaginationCursor = rt.TypeOf; - -export const anomalyTypeRT = rt.keyof({ - logRate: null, - logCategory: null, -}); - -export type AnomalyType = rt.TypeOf; - -const logEntryAnomalyCommonFieldsRT = rt.type({ - id: rt.string, - anomalyScore: rt.number, - dataset: rt.string, - typical: rt.number, - actual: rt.number, - type: anomalyTypeRT, - duration: rt.number, - startTime: rt.number, - jobId: rt.string, -}); -const logEntrylogRateAnomalyRT = logEntryAnomalyCommonFieldsRT; -const logEntrylogCategoryAnomalyRT = rt.partial({ - categoryId: rt.string, -}); -const logEntryAnomalyRT = rt.intersection([ - logEntryAnomalyCommonFieldsRT, - logEntrylogRateAnomalyRT, - logEntrylogCategoryAnomalyRT, -]); - -export type LogEntryAnomaly = rt.TypeOf; - export const getLogEntryAnomaliesSuccessReponsePayloadRT = rt.intersection([ rt.type({ data: rt.intersection([ @@ -78,43 +47,6 @@ export type GetLogEntryAnomaliesSuccessResponsePayload = rt.TypeOf< typeof getLogEntryAnomaliesSuccessReponsePayloadRT >; -const sortOptionsRT = rt.keyof({ - anomalyScore: null, - dataset: null, - startTime: null, -}); - -const sortDirectionsRT = rt.keyof({ - asc: null, - desc: null, -}); - -const paginationPreviousPageCursorRT = rt.type({ - searchBefore: paginationCursorRT, -}); - -const paginationNextPageCursorRT = rt.type({ - searchAfter: paginationCursorRT, -}); - -const paginationRT = rt.intersection([ - rt.type({ - pageSize: rt.number, - }), - rt.partial({ - cursor: rt.union([paginationPreviousPageCursorRT, paginationNextPageCursorRT]), - }), -]); - -export type Pagination = rt.TypeOf; - -const sortRT = rt.type({ - field: sortOptionsRT, - direction: sortDirectionsRT, -}); - -export type Sort = rt.TypeOf; - export const getLogEntryAnomaliesRequestPayloadRT = rt.type({ data: rt.intersection([ rt.type({ @@ -127,9 +59,9 @@ export const getLogEntryAnomaliesRequestPayloadRT = rt.type({ // Pagination properties pagination: paginationRT, // Sort properties - sort: sortRT, + sort: anomaliesSortRT, // Dataset filters - datasets: rt.array(rt.string), + datasets: logEntryAnomalyDatasetsRT, }), ]), }); diff --git a/x-pack/plugins/infra/common/http_api/log_analysis/results/log_entry_categories.ts b/x-pack/plugins/infra/common/http_api/log_analysis/results/log_entry_categories.ts index 0554192398fc5..019ae01c1437c 100644 --- a/x-pack/plugins/infra/common/http_api/log_analysis/results/log_entry_categories.ts +++ b/x-pack/plugins/infra/common/http_api/log_analysis/results/log_entry_categories.ts @@ -13,6 +13,8 @@ import { routeTimingMetadataRT, } from '../../shared'; +import { logEntryCategoryRT, categoriesSortRT } from '../../../log_analysis'; + export const LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORIES_PATH = '/api/infra/log_analysis/results/log_entry_categories'; @@ -30,23 +32,6 @@ export type LogEntryCategoriesHistogramParameters = rt.TypeOf< typeof logEntryCategoriesHistogramParametersRT >; -const sortOptionsRT = rt.keyof({ - maximumAnomalyScore: null, - logEntryCount: null, -}); - -const sortDirectionsRT = rt.keyof({ - asc: null, - desc: null, -}); - -const categorySortRT = rt.type({ - field: sortOptionsRT, - direction: sortDirectionsRT, -}); - -export type CategorySort = rt.TypeOf; - export const getLogEntryCategoriesRequestPayloadRT = rt.type({ data: rt.intersection([ rt.type({ @@ -59,7 +44,7 @@ export const getLogEntryCategoriesRequestPayloadRT = rt.type({ // a list of histograms to create histograms: rt.array(logEntryCategoriesHistogramParametersRT), // the criteria to the categories by - sort: categorySortRT, + sort: categoriesSortRT, }), rt.partial({ // the datasets to filter for (optional, unfiltered if not present) @@ -76,39 +61,6 @@ export type GetLogEntryCategoriesRequestPayload = rt.TypeOf< * response */ -export const logEntryCategoryHistogramBucketRT = rt.type({ - startTime: rt.number, - bucketDuration: rt.number, - logEntryCount: rt.number, -}); - -export type LogEntryCategoryHistogramBucket = rt.TypeOf; - -export const logEntryCategoryHistogramRT = rt.type({ - histogramId: rt.string, - buckets: rt.array(logEntryCategoryHistogramBucketRT), -}); - -export type LogEntryCategoryHistogram = rt.TypeOf; - -export const logEntryCategoryDatasetRT = rt.type({ - name: rt.string, - maximumAnomalyScore: rt.number, -}); - -export type LogEntryCategoryDataset = rt.TypeOf; - -export const logEntryCategoryRT = rt.type({ - categoryId: rt.number, - datasets: rt.array(logEntryCategoryDatasetRT), - histograms: rt.array(logEntryCategoryHistogramRT), - logEntryCount: rt.number, - maximumAnomalyScore: rt.number, - regularExpression: rt.string, -}); - -export type LogEntryCategory = rt.TypeOf; - export const getLogEntryCategoriesSuccessReponsePayloadRT = rt.intersection([ rt.type({ data: rt.type({ diff --git a/x-pack/plugins/infra/common/http_api/log_analysis/results/log_entry_category_examples.ts b/x-pack/plugins/infra/common/http_api/log_analysis/results/log_entry_category_examples.ts index e9e3c6e0ca3f9..3166d40d70392 100644 --- a/x-pack/plugins/infra/common/http_api/log_analysis/results/log_entry_category_examples.ts +++ b/x-pack/plugins/infra/common/http_api/log_analysis/results/log_entry_category_examples.ts @@ -12,7 +12,7 @@ import { timeRangeRT, routeTimingMetadataRT, } from '../../shared'; -import { logEntryContextRT } from '../../log_entries'; +import { logEntryContextRT } from '../../../log_entry'; export const LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORY_EXAMPLES_PATH = '/api/infra/log_analysis/results/log_entry_category_examples'; diff --git a/x-pack/plugins/infra/common/http_api/log_analysis/results/log_entry_examples.ts b/x-pack/plugins/infra/common/http_api/log_analysis/results/log_entry_examples.ts index 1eed29cd37560..c061545ec09ed 100644 --- a/x-pack/plugins/infra/common/http_api/log_analysis/results/log_entry_examples.ts +++ b/x-pack/plugins/infra/common/http_api/log_analysis/results/log_entry_examples.ts @@ -5,7 +5,7 @@ */ import * as rt from 'io-ts'; - +import { logEntryExampleRT } from '../../../log_analysis'; import { badRequestErrorRT, forbiddenErrorRT, @@ -46,16 +46,6 @@ export type GetLogEntryExamplesRequestPayload = rt.TypeOf< * response */ -const logEntryExampleRT = rt.type({ - id: rt.string, - dataset: rt.string, - message: rt.string, - timestamp: rt.number, - tiebreaker: rt.number, -}); - -export type LogEntryExample = rt.TypeOf; - export const getLogEntryExamplesSuccessReponsePayloadRT = rt.intersection([ rt.type({ data: rt.type({ diff --git a/x-pack/plugins/infra/common/http_api/log_entries/entries.ts b/x-pack/plugins/infra/common/http_api/log_entries/entries.ts index 31bc62f48791a..b4d9a5744d5ac 100644 --- a/x-pack/plugins/infra/common/http_api/log_entries/entries.ts +++ b/x-pack/plugins/infra/common/http_api/log_entries/entries.ts @@ -5,8 +5,7 @@ */ import * as rt from 'io-ts'; -import { logEntryCursorRT } from '../../log_entry'; -import { jsonArrayRT } from '../../typed_json'; +import { logEntryCursorRT, logEntryRT } from '../../log_entry'; import { logSourceColumnConfigurationRT } from '../log_sources'; export const LOG_ENTRIES_PATH = '/api/log_entries/entries'; @@ -52,54 +51,6 @@ export type LogEntriesAfterRequest = rt.TypeOf; export type LogEntriesCenteredRequest = rt.TypeOf; export type LogEntriesRequest = rt.TypeOf; -export const logMessageConstantPartRT = rt.type({ - constant: rt.string, -}); -export const logMessageFieldPartRT = rt.type({ - field: rt.string, - value: jsonArrayRT, - highlights: rt.array(rt.string), -}); - -export const logMessagePartRT = rt.union([logMessageConstantPartRT, logMessageFieldPartRT]); - -export const logTimestampColumnRT = rt.type({ columnId: rt.string, timestamp: rt.number }); -export const logFieldColumnRT = rt.type({ - columnId: rt.string, - field: rt.string, - value: jsonArrayRT, - highlights: rt.array(rt.string), -}); -export const logMessageColumnRT = rt.type({ - columnId: rt.string, - message: rt.array(logMessagePartRT), -}); - -export const logColumnRT = rt.union([logTimestampColumnRT, logFieldColumnRT, logMessageColumnRT]); - -export const logEntryContextRT = rt.union([ - rt.type({}), - rt.type({ 'container.id': rt.string }), - rt.type({ 'host.name': rt.string, 'log.file.path': rt.string }), -]); - -export const logEntryRT = rt.type({ - id: rt.string, - cursor: logEntryCursorRT, - columns: rt.array(logColumnRT), - context: logEntryContextRT, -}); - -export type LogMessageConstantPart = rt.TypeOf; -export type LogMessageFieldPart = rt.TypeOf; -export type LogMessagePart = rt.TypeOf; -export type LogTimestampColumn = rt.TypeOf; -export type LogFieldColumn = rt.TypeOf; -export type LogMessageColumn = rt.TypeOf; -export type LogColumn = rt.TypeOf; -export type LogEntryContext = rt.TypeOf; -export type LogEntry = rt.TypeOf; - export const logEntriesResponseRT = rt.type({ data: rt.intersection([ rt.type({ diff --git a/x-pack/plugins/infra/common/http_api/log_entries/highlights.ts b/x-pack/plugins/infra/common/http_api/log_entries/highlights.ts index 648da43134a27..96bf8beb29021 100644 --- a/x-pack/plugins/infra/common/http_api/log_entries/highlights.ts +++ b/x-pack/plugins/infra/common/http_api/log_entries/highlights.ts @@ -5,13 +5,12 @@ */ import * as rt from 'io-ts'; -import { logEntryCursorRT } from '../../log_entry'; +import { logEntryCursorRT, logEntryRT } from '../../log_entry'; import { logEntriesBaseRequestRT, logEntriesBeforeRequestRT, logEntriesAfterRequestRT, logEntriesCenteredRequestRT, - logEntryRT, } from './entries'; export const LOG_ENTRIES_HIGHLIGHTS_PATH = '/api/log_entries/highlights'; diff --git a/x-pack/plugins/infra/common/http_api/shared/time_range.ts b/x-pack/plugins/infra/common/http_api/shared/time_range.ts index efda07423748b..07317092cdedb 100644 --- a/x-pack/plugins/infra/common/http_api/shared/time_range.ts +++ b/x-pack/plugins/infra/common/http_api/shared/time_range.ts @@ -4,11 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as rt from 'io-ts'; - -export const timeRangeRT = rt.type({ - startTime: rt.number, - endTime: rt.number, -}); - -export type TimeRange = rt.TypeOf; +export * from '../../time/time_range'; diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_docker_overview.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_docker_overview.ts index d7026d7648d37..b8cf094d7bd4c 100644 --- a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_docker_overview.ts +++ b/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_docker_overview.ts @@ -15,7 +15,7 @@ export const hostDockerOverview: TSVBMetricModelCreator = ( index_pattern: indexPattern, interval, time_field: timeField, - type: 'gauge', + type: 'top_n', series: [ { id: 'total', diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_k8s_overview.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_k8s_overview.ts index 86d615231f070..1488fe5504c0b 100644 --- a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_k8s_overview.ts +++ b/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_k8s_overview.ts @@ -16,7 +16,7 @@ export const hostK8sOverview: TSVBMetricModelCreator = ( index_pattern: indexPattern, interval, time_field: timeField, - type: 'gauge', + type: 'top_n', series: [ { id: 'cpucap', diff --git a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_system_overview.ts b/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_system_overview.ts index 953c14ab2a9ce..cbd76dd8e9637 100644 --- a/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_system_overview.ts +++ b/x-pack/plugins/infra/common/inventory_models/host/metrics/tsvb/host_system_overview.ts @@ -16,7 +16,7 @@ export const hostSystemOverview: TSVBMetricModelCreator = ( index_pattern: indexPattern, interval, time_field: timeField, - type: 'gauge', + type: 'top_n', series: [ { id: 'cpu', diff --git a/x-pack/plugins/infra/common/inventory_models/shared/metrics/tsvb/aws_overview.ts b/x-pack/plugins/infra/common/inventory_models/shared/metrics/tsvb/aws_overview.ts index 5ba61d1f92517..28e1b7860aab4 100644 --- a/x-pack/plugins/infra/common/inventory_models/shared/metrics/tsvb/aws_overview.ts +++ b/x-pack/plugins/infra/common/inventory_models/shared/metrics/tsvb/aws_overview.ts @@ -14,7 +14,7 @@ export const awsOverview: TSVBMetricModelCreator = (timeField, indexPattern): TS id_type: 'cloud', interval: '>=5m', time_field: timeField, - type: 'gauge', + type: 'top_n', series: [ { id: 'cpu-util', diff --git a/x-pack/plugins/infra/common/log_analysis/index.ts b/x-pack/plugins/infra/common/log_analysis/index.ts index 0b4fa374a5da9..f055f642c8d1b 100644 --- a/x-pack/plugins/infra/common/log_analysis/index.ts +++ b/x-pack/plugins/infra/common/log_analysis/index.ts @@ -10,3 +10,5 @@ export * from './log_analysis_results'; export * from './log_entry_rate_analysis'; export * from './log_entry_categories_analysis'; export * from './job_parameters'; +export * from './log_entry_anomalies'; +export * from './log_entry_examples'; diff --git a/x-pack/plugins/infra/common/log_analysis/log_analysis_results.ts b/x-pack/plugins/infra/common/log_analysis/log_analysis_results.ts index f4497dbba5056..897a5a4bb84df 100644 --- a/x-pack/plugins/infra/common/log_analysis/log_analysis_results.ts +++ b/x-pack/plugins/infra/common/log_analysis/log_analysis_results.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import * as rt from 'io-ts'; + export const ML_SEVERITY_SCORES = { warning: 3, minor: 25, @@ -55,3 +57,44 @@ export const compareDatasetsByMaximumAnomalyScore = < firstDataset: Dataset, secondDataset: Dataset ) => firstDataset.maximumAnomalyScore - secondDataset.maximumAnomalyScore; + +// Generic Sort + +const sortDirectionsRT = rt.keyof({ + asc: null, + desc: null, +}); + +export const sortRT = (fields: Fields) => + rt.type({ + field: fields, + direction: sortDirectionsRT, + }); + +// Pagination +// [Sort field value, tiebreaker value] +export const paginationCursorRT = rt.tuple([ + rt.union([rt.string, rt.number]), + rt.union([rt.string, rt.number]), +]); + +export type PaginationCursor = rt.TypeOf; + +const paginationPreviousPageCursorRT = rt.type({ + searchBefore: paginationCursorRT, +}); + +const paginationNextPageCursorRT = rt.type({ + searchAfter: paginationCursorRT, +}); + +export const paginationRT = rt.intersection([ + rt.type({ + pageSize: rt.number, + }), + rt.partial({ + cursor: rt.union([paginationPreviousPageCursorRT, paginationNextPageCursorRT]), + }), +]); + +export type Pagination = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/log_analysis/log_entry_anomalies.ts b/x-pack/plugins/infra/common/log_analysis/log_entry_anomalies.ts new file mode 100644 index 0000000000000..c426646e8e847 --- /dev/null +++ b/x-pack/plugins/infra/common/log_analysis/log_entry_anomalies.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 * as rt from 'io-ts'; +import { sortRT } from './log_analysis_results'; + +export const anomalyTypeRT = rt.keyof({ + logRate: null, + logCategory: null, +}); + +export type AnomalyType = rt.TypeOf; + +export const logEntryAnomalyCommonFieldsRT = rt.type({ + id: rt.string, + anomalyScore: rt.number, + dataset: rt.string, + typical: rt.number, + actual: rt.number, + type: anomalyTypeRT, + duration: rt.number, + startTime: rt.number, + jobId: rt.string, +}); +export const logEntrylogRateAnomalyRT = logEntryAnomalyCommonFieldsRT; +export type RateAnomaly = rt.TypeOf; + +export const logEntrylogCategoryAnomalyRT = rt.intersection([ + logEntryAnomalyCommonFieldsRT, + rt.type({ + categoryId: rt.string, + categoryRegex: rt.string, + categoryTerms: rt.string, + }), +]); +export type CategoryAnomaly = rt.TypeOf; + +export const logEntryAnomalyRT = rt.union([logEntrylogRateAnomalyRT, logEntrylogCategoryAnomalyRT]); + +export type LogEntryAnomaly = rt.TypeOf; + +export const logEntryAnomalyDatasetsRT = rt.array(rt.string); +export type LogEntryAnomalyDatasets = rt.TypeOf; + +export const isCategoryAnomaly = (anomaly: LogEntryAnomaly): anomaly is CategoryAnomaly => { + return anomaly.type === 'logCategory'; +}; + +const sortOptionsRT = rt.keyof({ + anomalyScore: null, + dataset: null, + startTime: null, +}); + +export const anomaliesSortRT = sortRT(sortOptionsRT); +export type AnomaliesSort = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/log_analysis/log_entry_categories_analysis.ts b/x-pack/plugins/infra/common/log_analysis/log_entry_categories_analysis.ts index 0957126ee52e3..4292eaeb5f98c 100644 --- a/x-pack/plugins/infra/common/log_analysis/log_entry_categories_analysis.ts +++ b/x-pack/plugins/infra/common/log_analysis/log_entry_categories_analysis.ts @@ -5,6 +5,7 @@ */ import * as rt from 'io-ts'; +import { sortRT } from './log_analysis_results'; export const logEntryCategoriesJobTypeRT = rt.keyof({ 'log-entry-categories-count': null, @@ -15,3 +16,44 @@ export type LogEntryCategoriesJobType = rt.TypeOf; + +export const logEntryCategoryHistogramBucketRT = rt.type({ + startTime: rt.number, + bucketDuration: rt.number, + logEntryCount: rt.number, +}); + +export type LogEntryCategoryHistogramBucket = rt.TypeOf; + +export const logEntryCategoryHistogramRT = rt.type({ + histogramId: rt.string, + buckets: rt.array(logEntryCategoryHistogramBucketRT), +}); + +export type LogEntryCategoryHistogram = rt.TypeOf; + +export const logEntryCategoryRT = rt.type({ + categoryId: rt.number, + datasets: rt.array(logEntryCategoryDatasetRT), + histograms: rt.array(logEntryCategoryHistogramRT), + logEntryCount: rt.number, + maximumAnomalyScore: rt.number, + regularExpression: rt.string, +}); + +export type LogEntryCategory = rt.TypeOf; + +const sortOptionsRT = rt.keyof({ + maximumAnomalyScore: null, + logEntryCount: null, +}); + +export const categoriesSortRT = sortRT(sortOptionsRT); +export type CategoriesSort = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/log_analysis/log_entry_examples.ts b/x-pack/plugins/infra/common/log_analysis/log_entry_examples.ts new file mode 100644 index 0000000000000..78d230e35dc74 --- /dev/null +++ b/x-pack/plugins/infra/common/log_analysis/log_entry_examples.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; + +export const logEntryExampleRT = rt.type({ + id: rt.string, + dataset: rt.string, + message: rt.string, + timestamp: rt.number, + tiebreaker: rt.number, +}); + +export type LogEntryExample = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/log_entry/log_entry.ts b/x-pack/plugins/infra/common/log_entry/log_entry.ts index e02acebe27711..eec1fb59f3091 100644 --- a/x-pack/plugins/infra/common/log_entry/log_entry.ts +++ b/x-pack/plugins/infra/common/log_entry/log_entry.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import * as rt from 'io-ts'; import { TimeKey } from '../time'; -import { InfraLogEntry } from '../graphql/types'; - -export type LogEntry = InfraLogEntry; +import { logEntryCursorRT } from './log_entry_cursor'; +import { jsonArrayRT } from '../typed_json'; export interface LogEntryOrigin { id: string; @@ -42,3 +42,51 @@ export function isLessOrEqual(time1: LogEntryTime, time2: LogEntryTime) { export function isBetween(min: LogEntryTime, max: LogEntryTime, operand: LogEntryTime) { return isLessOrEqual(min, operand) && isLessOrEqual(operand, max); } + +export const logMessageConstantPartRT = rt.type({ + constant: rt.string, +}); +export const logMessageFieldPartRT = rt.type({ + field: rt.string, + value: jsonArrayRT, + highlights: rt.array(rt.string), +}); + +export const logMessagePartRT = rt.union([logMessageConstantPartRT, logMessageFieldPartRT]); + +export const logTimestampColumnRT = rt.type({ columnId: rt.string, timestamp: rt.number }); +export const logFieldColumnRT = rt.type({ + columnId: rt.string, + field: rt.string, + value: jsonArrayRT, + highlights: rt.array(rt.string), +}); +export const logMessageColumnRT = rt.type({ + columnId: rt.string, + message: rt.array(logMessagePartRT), +}); + +export const logColumnRT = rt.union([logTimestampColumnRT, logFieldColumnRT, logMessageColumnRT]); + +export const logEntryContextRT = rt.union([ + rt.type({}), + rt.type({ 'container.id': rt.string }), + rt.type({ 'host.name': rt.string, 'log.file.path': rt.string }), +]); + +export const logEntryRT = rt.type({ + id: rt.string, + cursor: logEntryCursorRT, + columns: rt.array(logColumnRT), + context: logEntryContextRT, +}); + +export type LogMessageConstantPart = rt.TypeOf; +export type LogMessageFieldPart = rt.TypeOf; +export type LogMessagePart = rt.TypeOf; +export type LogEntryContext = rt.TypeOf; +export type LogEntry = rt.TypeOf; +export type LogTimestampColumn = rt.TypeOf; +export type LogFieldColumn = rt.TypeOf; +export type LogMessageColumn = rt.TypeOf; +export type LogColumn = rt.TypeOf; diff --git a/x-pack/plugins/infra/common/time/index.ts b/x-pack/plugins/infra/common/time/index.ts index f49d46fa4920f..63bba2fa807ac 100644 --- a/x-pack/plugins/infra/common/time/index.ts +++ b/x-pack/plugins/infra/common/time/index.ts @@ -7,3 +7,4 @@ export * from './time_unit'; export * from './time_scale'; export * from './time_key'; +export * from './time_range'; diff --git a/x-pack/plugins/infra/common/time/time_range.ts b/x-pack/plugins/infra/common/time/time_range.ts new file mode 100644 index 0000000000000..efda07423748b --- /dev/null +++ b/x-pack/plugins/infra/common/time/time_range.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; + +export const timeRangeRT = rt.type({ + startTime: rt.number, + endTime: rt.number, +}); + +export type TimeRange = rt.TypeOf; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/category_expression.tsx b/x-pack/plugins/infra/public/components/logging/log_analysis_results/category_expression.tsx similarity index 95% rename from x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/category_expression.tsx rename to x-pack/plugins/infra/public/components/logging/log_analysis_results/category_expression.tsx index d5480977e7f9e..9684777ac9216 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/category_expression.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_analysis_results/category_expression.tsx @@ -7,7 +7,7 @@ import { i18n } from '@kbn/i18n'; import React, { memo } from 'react'; -import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common'; +import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; export const RegularExpressionRepresentation: React.FunctionComponent<{ maximumSegmentCount?: number; diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/item.ts b/x-pack/plugins/infra/public/components/logging/log_text_stream/item.ts index 19e8108ee50e8..b0ff36574bede 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/item.ts +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/item.ts @@ -7,7 +7,7 @@ import { bisector } from 'd3-array'; import { compareToTimeKey, TimeKey } from '../../../../common/time'; -import { LogEntry } from '../../../../common/http_api'; +import { LogEntry } from '../../../../common/log_entry'; export type StreamItem = LogEntryStreamItem; diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_field_column.test.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_field_column.test.tsx index 8de9e565b00be..2b30d43f8c38d 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_field_column.test.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_field_column.test.tsx @@ -7,7 +7,7 @@ import { render } from '@testing-library/react'; import React from 'react'; import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common'; -import { LogFieldColumn } from '../../../../common/http_api'; +import { LogFieldColumn } from '../../../../common/log_entry'; import { LogEntryFieldColumn } from './log_entry_field_column'; describe('LogEntryFieldColumn', () => { diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_field_column.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_field_column.tsx index 4a9b0d0906a76..0d295b4df5566 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_field_column.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_field_column.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { JsonValue } from '../../../../../../../src/plugins/kibana_utils/common'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; -import { LogColumn } from '../../../../common/http_api'; +import { LogColumn } from '../../../../common/log_entry'; import { isFieldColumn, isHighlightFieldColumn } from '../../../utils/log_entry'; import { FieldValue } from './field_value'; import { LogEntryColumnContent } from './log_entry_column'; diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_message_column.test.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_message_column.test.tsx index 5d36e5cd47c59..00281c2df3133 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_message_column.test.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_message_column.test.tsx @@ -7,7 +7,7 @@ import { render } from '@testing-library/react'; import React from 'react'; import { EuiThemeProvider } from '../../../../../../../src/plugins/kibana_react/common'; -import { LogMessageColumn } from '../../../../common/http_api'; +import { LogMessageColumn } from '../../../../common/log_entry'; import { LogEntryMessageColumn } from './log_entry_message_column'; describe('LogEntryMessageColumn', () => { diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_message_column.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_message_column.tsx index bfc160ada2e6a..92214dee9de22 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_message_column.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_message_column.tsx @@ -6,7 +6,7 @@ import React, { memo, useMemo } from 'react'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; -import { LogColumn, LogMessagePart } from '../../../../common/http_api'; +import { LogColumn, LogMessagePart } from '../../../../common/log_entry'; import { isConstantSegment, isFieldSegment, diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_row.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_row.tsx index 93c657fbdda97..1a472df2b5c90 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_row.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/log_entry_row.tsx @@ -17,7 +17,7 @@ import { LogEntryFieldColumn } from './log_entry_field_column'; import { LogEntryMessageColumn } from './log_entry_message_column'; import { LogEntryTimestampColumn } from './log_entry_timestamp_column'; import { monospaceTextStyle, hoveredContentStyle, highlightedContentStyle } from './text_styles'; -import { LogEntry, LogColumn } from '../../../../common/http_api'; +import { LogEntry, LogColumn } from '../../../../common/log_entry'; import { LogEntryContextMenu } from './log_entry_context_menu'; import { LogColumnRenderConfiguration, diff --git a/x-pack/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx b/x-pack/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx index d399e47a73562..8fb63533cf61b 100644 --- a/x-pack/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx +++ b/x-pack/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx @@ -25,7 +25,7 @@ import { MeasurableItemView } from './measurable_item_view'; import { VerticalScrollPanel } from './vertical_scroll_panel'; import { useColumnWidths, LogEntryColumnWidths } from './log_entry_column'; import { LogDateRow } from './log_date_row'; -import { LogEntry } from '../../../../common/http_api'; +import { LogEntry } from '../../../../common/log_entry'; import { LogColumnRenderConfiguration } from '../../../utils/log_column_render_configuration'; interface ScrollableLogTextStreamViewProps { diff --git a/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts b/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts index bf4c5fbe0b13b..f1b820857e340 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_entries/index.ts @@ -10,10 +10,10 @@ import { pick, throttle } from 'lodash'; import { TimeKey, timeKeyIsBetween } from '../../../../common/time'; import { LogEntriesResponse, - LogEntry, LogEntriesRequest, LogEntriesBaseRequest, } from '../../../../common/http_api'; +import { LogEntry } from '../../../../common/log_entry'; import { fetchLogEntries } from './api/fetch_log_entries'; import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; diff --git a/x-pack/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.tsx b/x-pack/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.tsx index b4edebe8f8207..fb72874df5409 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.tsx +++ b/x-pack/plugins/infra/public/containers/logs/log_highlights/log_entry_highlights.tsx @@ -9,7 +9,8 @@ import { useEffect, useMemo, useState } from 'react'; import { TimeKey } from '../../../../common/time'; import { useTrackedPromise } from '../../../utils/use_tracked_promise'; import { fetchLogEntriesHighlights } from './api/fetch_log_entries_highlights'; -import { LogEntry, LogEntriesHighlightsResponse } from '../../../../common/http_api'; +import { LogEntriesHighlightsResponse } from '../../../../common/http_api'; +import { LogEntry } from '../../../../common/log_entry'; import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; export const useLogEntryHighlights = ( diff --git a/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts b/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts index ff30e993aa3a9..da7176125dae4 100644 --- a/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts +++ b/x-pack/plugins/infra/public/containers/logs/log_stream/index.ts @@ -10,8 +10,7 @@ import usePrevious from 'react-use/lib/usePrevious'; import { esKuery } from '../../../../../../../src/plugins/data/public'; import { fetchLogEntries } from '../log_entries/api/fetch_log_entries'; import { useTrackedPromise } from '../../../utils/use_tracked_promise'; -import { LogEntry } from '../../../../common/http_api'; -import { LogEntryCursor } from '../../../../common/log_entry'; +import { LogEntryCursor, LogEntry } from '../../../../common/log_entry'; import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; import { LogSourceConfigurationProperties } from '../log_source'; diff --git a/x-pack/plugins/infra/public/containers/logs/view_log_in_context/view_log_in_context.ts b/x-pack/plugins/infra/public/containers/logs/view_log_in_context/view_log_in_context.ts index 61e1ea353880a..2888e5a2b3ac5 100644 --- a/x-pack/plugins/infra/public/containers/logs/view_log_in_context/view_log_in_context.ts +++ b/x-pack/plugins/infra/public/containers/logs/view_log_in_context/view_log_in_context.ts @@ -5,7 +5,7 @@ */ import { useState } from 'react'; import createContainer from 'constate'; -import { LogEntry } from '../../../../common/http_api'; +import { LogEntry } from '../../../../common/log_entry'; interface ViewLogInContextProps { sourceId: string; diff --git a/x-pack/plugins/infra/public/containers/logs/with_stream_items.ts b/x-pack/plugins/infra/public/containers/logs/with_stream_items.ts index 2b8986820d5a4..89b5d993fa01e 100644 --- a/x-pack/plugins/infra/public/containers/logs/with_stream_items.ts +++ b/x-pack/plugins/infra/public/containers/logs/with_stream_items.ts @@ -11,7 +11,7 @@ import { RendererFunction } from '../../utils/typed_react'; import { LogHighlightsState } from './log_highlights/log_highlights'; import { LogEntriesState, LogEntriesStateParams, LogEntriesCallbacks } from './log_entries'; import { UniqueTimeKey } from '../../../common/time'; -import { LogEntry } from '../../../common/http_api'; +import { LogEntry } from '../../../common/log_entry'; export const WithStreamItems: React.FunctionComponent<{ children: RendererFunction< diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx index ecddd8a9aa5be..4445b735bedc9 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx @@ -12,7 +12,7 @@ import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; import { useTrackPageview } from '../../../../../observability/public'; -import { TimeRange } from '../../../../common/http_api/shared/time_range'; +import { TimeRange } from '../../../../common/time/time_range'; import { CategoryJobNoticesSection } from '../../../components/logging/log_analysis_job_status'; import { useLogEntryCategoriesModuleContext } from '../../../containers/logs/log_analysis/modules/log_entry_categories'; import { ViewLogInContext } from '../../../containers/logs/view_log_in_context'; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/analyze_dataset_in_ml_action.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/analyze_dataset_in_ml_action.tsx index 3e1398c804686..8fe87c14c1a7c 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/analyze_dataset_in_ml_action.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/analyze_dataset_in_ml_action.tsx @@ -8,7 +8,7 @@ import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React from 'react'; -import { TimeRange } from '../../../../../../common/http_api/shared'; +import { TimeRange } from '../../../../../../common/time/time_range'; import { getEntitySpecificSingleMetricViewerLink } from '../../../../../components/logging/log_analysis_results'; import { useLinkProps } from '../../../../../hooks/use_link_props'; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/anomaly_severity_indicator_list.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/anomaly_severity_indicator_list.tsx index 47bb31ab4ae3e..20f0ee00bd505 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/anomaly_severity_indicator_list.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/anomaly_severity_indicator_list.tsx @@ -6,7 +6,7 @@ import React from 'react'; -import { LogEntryCategoryDataset } from '../../../../../../common/http_api/log_analysis'; +import { LogEntryCategoryDataset } from '../../../../../../common/log_analysis'; import { getFriendlyNameForPartitionId } from '../../../../../../common/log_analysis'; import { AnomalySeverityIndicator } from '../../../../../components/logging/log_analysis_results/anomaly_severity_indicator'; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/category_details_row.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/category_details_row.tsx index de07f3eb02029..8b4f075b782a9 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/category_details_row.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/category_details_row.tsx @@ -7,7 +7,7 @@ import React, { useEffect } from 'react'; import { useLogEntryCategoryExamples } from '../../use_log_entry_category_examples'; import { LogEntryExampleMessages } from '../../../../../components/logging/log_entry_examples/log_entry_examples'; -import { TimeRange } from '../../../../../../common/http_api/shared'; +import { TimeRange } from '../../../../../../common/time/time_range'; import { CategoryExampleMessage } from './category_example_message'; const exampleCount = 5; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/category_example_message.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/category_example_message.tsx index 84d7e198636e9..e24fdd06bc6d9 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/category_example_message.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/category_example_message.tsx @@ -9,8 +9,8 @@ import { i18n } from '@kbn/i18n'; import { encode } from 'rison-node'; import moment from 'moment'; -import { LogEntry, LogEntryContext } from '../../../../../../common/http_api'; -import { TimeRange } from '../../../../../../common/http_api/shared'; +import { LogEntry, LogEntryContext } from '../../../../../../common/log_entry'; +import { TimeRange } from '../../../../../../common/time'; import { getFriendlyNameForPartitionId, partitionField, diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/datasets_action_list.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/datasets_action_list.tsx index 2321dafaead1c..6bbc640b5b007 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/datasets_action_list.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/datasets_action_list.tsx @@ -6,8 +6,8 @@ import React from 'react'; -import { LogEntryCategoryDataset } from '../../../../../../common/http_api/log_analysis'; -import { TimeRange } from '../../../../../../common/http_api/shared'; +import { LogEntryCategoryDataset } from '../../../../../../common/log_analysis'; +import { TimeRange } from '../../../../../../common/time'; import { getFriendlyNameForPartitionId } from '../../../../../../common/log_analysis'; import { AnalyzeCategoryDatasetInMlAction } from './analyze_dataset_in_ml_action'; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/datasets_list.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/datasets_list.tsx index 779ac3e8c3a07..78690285180d7 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/datasets_list.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/datasets_list.tsx @@ -7,7 +7,7 @@ import React from 'react'; import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common'; -import { LogEntryCategoryDataset } from '../../../../../../common/http_api/log_analysis'; +import { LogEntryCategoryDataset } from '../../../../../../common/log_analysis'; import { getFriendlyNameForPartitionId } from '../../../../../../common/log_analysis'; export const DatasetsList: React.FunctionComponent<{ diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/log_entry_count_sparkline.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/log_entry_count_sparkline.tsx index 42d6509802ed4..d94dbb9d33556 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/log_entry_count_sparkline.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/log_entry_count_sparkline.tsx @@ -6,8 +6,8 @@ import React, { useMemo } from 'react'; -import { LogEntryCategoryHistogram } from '../../../../../../common/http_api/log_analysis'; -import { TimeRange } from '../../../../../../common/http_api/shared'; +import { LogEntryCategoryHistogram } from '../../../../../../common/log_analysis'; +import { TimeRange } from '../../../../../../common/time'; import { SingleMetricComparison } from './single_metric_comparison'; import { SingleMetricSparkline } from './single_metric_sparkline'; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/single_metric_sparkline.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/single_metric_sparkline.tsx index 5fb8e3380f23f..c8453bdcdefbd 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/single_metric_sparkline.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/single_metric_sparkline.tsx @@ -13,7 +13,7 @@ import { } from '@elastic/eui/dist/eui_charts_theme'; import { useKibanaUiSetting } from '../../../../../utils/use_kibana_ui_setting'; -import { TimeRange } from '../../../../../../common/http_api/shared'; +import { TimeRange } from '../../../../../../common/time'; interface TimeSeriesPoint { timestamp: number; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_section.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_section.tsx index c7a6c89012a3a..f810a675a18d1 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_section.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_section.tsx @@ -8,8 +8,8 @@ import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiSpacer, EuiTitle } fro import { i18n } from '@kbn/i18n'; import React from 'react'; -import { LogEntryCategory } from '../../../../../../common/http_api/log_analysis'; -import { TimeRange } from '../../../../../../common/http_api/shared'; +import { LogEntryCategory } from '../../../../../../common/log_analysis'; +import { TimeRange } from '../../../../../../common/time'; import { BetaBadge } from '../../../../../components/beta_badge'; import { LoadingOverlayWrapper } from '../../../../../components/loading_overlay_wrapper'; import { RecreateJobButton } from '../../../../../components/logging/log_analysis_setup/create_job_button'; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_table.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_table.tsx index 954b6a9ab3ed3..834c99502a590 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_table.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_table.tsx @@ -15,12 +15,12 @@ import { LogEntryCategory, LogEntryCategoryDataset, LogEntryCategoryHistogram, -} from '../../../../../../common/http_api/log_analysis'; -import { TimeRange } from '../../../../../../common/http_api/shared'; +} from '../../../../../../common/log_analysis'; +import { TimeRange } from '../../../../../../common/time'; import { RowExpansionButton } from '../../../../../components/basic_table'; import { AnomalySeverityIndicatorList } from './anomaly_severity_indicator_list'; import { CategoryDetailsRow } from './category_details_row'; -import { RegularExpressionRepresentation } from './category_expression'; +import { RegularExpressionRepresentation } from '../../../../../components/logging/log_analysis_results/category_expression'; import { DatasetActionsList } from './datasets_action_list'; import { DatasetsList } from './datasets_list'; import { LogEntryCountSparkline } from './log_entry_count_sparkline'; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_top_log_entry_categories.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_top_log_entry_categories.ts index a0eaecf04fa4b..b25b6cbe6f631 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_top_log_entry_categories.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_top_log_entry_categories.ts @@ -10,8 +10,8 @@ import { getLogEntryCategoriesRequestPayloadRT, getLogEntryCategoriesSuccessReponsePayloadRT, LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORIES_PATH, - CategorySort, } from '../../../../../common/http_api/log_analysis'; +import { CategoriesSort } from '../../../../../common/log_analysis'; import { decodeOrThrow } from '../../../../../common/runtime_types'; interface RequestArgs { @@ -20,7 +20,7 @@ interface RequestArgs { endTime: number; categoryCount: number; datasets?: string[]; - sort: CategorySort; + sort: CategoriesSort; } export const callGetTopLogEntryCategoriesAPI = async ( diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results.ts index a64b73dea25e6..e3fba92610955 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results.ts @@ -9,8 +9,8 @@ import { useMemo, useState } from 'react'; import { GetLogEntryCategoriesSuccessResponsePayload, GetLogEntryCategoryDatasetsSuccessResponsePayload, - CategorySort, } from '../../../../common/http_api/log_analysis'; +import { CategoriesSort } from '../../../../common/log_analysis'; import { useTrackedPromise, CanceledPromiseError } from '../../../utils/use_tracked_promise'; import { callGetTopLogEntryCategoriesAPI } from './service_calls/get_top_log_entry_categories'; import { callGetLogEntryCategoryDatasetsAPI } from './service_calls/get_log_entry_category_datasets'; @@ -19,8 +19,8 @@ import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; type TopLogEntryCategories = GetLogEntryCategoriesSuccessResponsePayload['data']['categories']; type LogEntryCategoryDatasets = GetLogEntryCategoryDatasetsSuccessResponsePayload['data']['datasets']; -export type SortOptions = CategorySort; -export type ChangeSortOptions = (sortOptions: CategorySort) => void; +export type SortOptions = CategoriesSort; +export type ChangeSortOptions = (sortOptions: CategoriesSort) => void; export const useLogEntryCategoriesResults = ({ categoriesCount, diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx index 09d3746c6ace6..f5007a1d48c4a 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx @@ -13,7 +13,7 @@ import { encode, RisonValue } from 'rison-node'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; import { useTrackPageview } from '../../../../../observability/public'; -import { TimeRange } from '../../../../common/http_api/shared/time_range'; +import { TimeRange } from '../../../../common/time/time_range'; import { bucketSpan } from '../../../../common/log_analysis'; import { TimeKey } from '../../../../common/time'; import { diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/chart.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/chart.tsx index ae5c3b5b93b47..503d383201592 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/chart.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/chart.tsx @@ -23,7 +23,7 @@ import moment from 'moment'; import React, { useCallback, useMemo } from 'react'; import { LoadingOverlayWrapper } from '../../../../../components/loading_overlay_wrapper'; -import { TimeRange } from '../../../../../../common/http_api/shared/time_range'; +import { TimeRange } from '../../../../../../common/time/time_range'; import { MLSeverityScoreCategories, ML_SEVERITY_COLORS, diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/expanded_row.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/expanded_row.tsx index 37032a95e9640..39fb1a5e6ae19 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/expanded_row.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/expanded_row.tsx @@ -10,8 +10,8 @@ import { i18n } from '@kbn/i18n'; import React from 'react'; import useMount from 'react-use/lib/useMount'; import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common'; -import { LogEntryAnomaly } from '../../../../../../common/http_api'; -import { TimeRange } from '../../../../../../common/http_api/shared/time_range'; +import { LogEntryAnomaly, isCategoryAnomaly } from '../../../../../../common/log_analysis'; +import { TimeRange } from '../../../../../../common/time/time_range'; import { LogEntryExampleMessages } from '../../../../../components/logging/log_entry_examples/log_entry_examples'; import { useLogSourceContext } from '../../../../../containers/logs/log_source'; import { useLogEntryExamples } from '../../use_log_entry_examples'; @@ -40,7 +40,7 @@ export const AnomaliesTableExpandedRow: React.FunctionComponent<{ exampleCount: EXAMPLE_COUNT, sourceId, startTime: anomaly.startTime, - categoryId: anomaly.categoryId, + categoryId: isCategoryAnomaly(anomaly) ? anomaly.categoryId : undefined, }); useMount(() => { diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/index.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/index.tsx index c89f0329e9f2e..780e8c7ec5ec9 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/index.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/index.tsx @@ -16,7 +16,7 @@ import { i18n } from '@kbn/i18n'; import React, { useMemo } from 'react'; import { euiStyled } from '../../../../../../../../../src/plugins/kibana_react/common'; import { LogEntryRateResults } from '../../use_log_entry_rate_results'; -import { TimeRange } from '../../../../../../common/http_api/shared/time_range'; +import { TimeRange } from '../../../../../../common/time/time_range'; import { getAnnotationsForAll, getLogEntryRateCombinedSeries } from '../helpers/data_formatters'; import { AnomaliesChart } from './chart'; import { AnomaliesTable } from './table'; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/log_entry_example.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/log_entry_example.tsx index ab3476cd78eb3..7446b3c348606 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/log_entry_example.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/log_entry_example.tsx @@ -25,10 +25,10 @@ import { LogColumnHeader, } from '../../../../../components/logging/log_text_stream/column_headers'; import { useLinkProps } from '../../../../../hooks/use_link_props'; -import { TimeRange } from '../../../../../../common/http_api/shared/time_range'; +import { TimeRange } from '../../../../../../common/time/time_range'; import { partitionField } from '../../../../../../common/log_analysis/job_parameters'; import { getEntitySpecificSingleMetricViewerLink } from '../../../../../components/logging/log_analysis_results/analyze_in_ml_button'; -import { LogEntryExample } from '../../../../../../common/http_api/log_analysis/results'; +import { LogEntryExample, isCategoryAnomaly } from '../../../../../../common/log_analysis'; import { LogColumnConfiguration, isTimestampLogColumnConfiguration, @@ -36,7 +36,7 @@ import { isMessageLogColumnConfiguration, } from '../../../../../utils/source_configuration'; import { localizedDate } from '../../../../../../common/formatters/datetime'; -import { LogEntryAnomaly } from '../../../../../../common/http_api'; +import { LogEntryAnomaly } from '../../../../../../common/log_analysis'; import { useLogEntryFlyoutContext } from '../../../../../containers/logs/log_flyout'; export const exampleMessageScale = 'medium' as const; @@ -116,7 +116,7 @@ export const LogEntryExampleMessage: React.FunctionComponent = ({ const viewAnomalyInMachineLearningLinkProps = useLinkProps( getEntitySpecificSingleMetricViewerLink(anomaly.jobId, timeRange, { [partitionField]: dataset, - ...(anomaly.categoryId ? { mlcategory: anomaly.categoryId } : {}), + ...(isCategoryAnomaly(anomaly) ? { mlcategory: anomaly.categoryId } : {}), }) ); diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/table.tsx b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/table.tsx index 855113d66f510..4b8c2b02bb8af 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/table.tsx +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/table.tsx @@ -18,16 +18,18 @@ import moment from 'moment'; import { i18n } from '@kbn/i18n'; import React, { useCallback, useMemo } from 'react'; import useSet from 'react-use/lib/useSet'; -import { TimeRange } from '../../../../../../common/http_api/shared/time_range'; +import { TimeRange } from '../../../../../../common/time/time_range'; import { + AnomalyType, formatAnomalyScore, getFriendlyNameForPartitionId, formatOneDecimalPlace, + isCategoryAnomaly, } from '../../../../../../common/log_analysis'; -import { AnomalyType } from '../../../../../../common/http_api/log_analysis'; import { RowExpansionButton } from '../../../../../components/basic_table'; import { AnomaliesTableExpandedRow } from './expanded_row'; import { AnomalySeverityIndicator } from '../../../../../components/logging/log_analysis_results/anomaly_severity_indicator'; +import { RegularExpressionRepresentation } from '../../../../../components/logging/log_analysis_results/category_expression'; import { useKibanaUiSetting } from '../../../../../utils/use_kibana_ui_setting'; import { Page, @@ -50,6 +52,7 @@ interface TableItem { typical: number; actual: number; type: AnomalyType; + categoryRegex?: string; } const anomalyScoreColumnName = i18n.translate( @@ -124,6 +127,7 @@ export const AnomaliesTable: React.FunctionComponent<{ type: anomaly.type, typical: anomaly.typical, actual: anomaly.actual, + categoryRegex: isCategoryAnomaly(anomaly) ? anomaly.categoryRegex : undefined, }; }); }, [results]); @@ -166,9 +170,7 @@ export const AnomaliesTable: React.FunctionComponent<{ { name: anomalyMessageColumnName, truncateText: true, - render: (item: TableItem) => ( - - ), + render: (item: TableItem) => , }, { field: 'startTime', @@ -226,15 +228,9 @@ export const AnomaliesTable: React.FunctionComponent<{ ); }; -const AnomalyMessage = ({ - actual, - typical, - type, -}: { - actual: number; - typical: number; - type: AnomalyType; -}) => { +const AnomalyMessage = ({ anomaly }: { anomaly: TableItem }) => { + const { type, actual, typical } = anomaly; + const moreThanExpectedAnomalyMessage = i18n.translate( 'xpack.infra.logs.analysis.anomaliesTableMoreThanExpectedAnomalyMessage', { @@ -262,9 +258,20 @@ const AnomalyMessage = ({ const ratioMessage = useRatio ? `${formatOneDecimalPlace(ratio)}x` : ''; return ( - - {`${ratioMessage} ${message}`} - + + + + + + {`${ratioMessage} ${message}`} + {anomaly.categoryRegex && ( + <> + {': '} + + + )} + + ); }; diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies.ts index 7f90604bfefdd..f915b0d78c43d 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/service_calls/get_log_entry_anomalies.ts @@ -11,13 +11,13 @@ import { LOG_ANALYSIS_GET_LOG_ENTRY_ANOMALIES_PATH, } from '../../../../../common/http_api/log_analysis'; import { decodeOrThrow } from '../../../../../common/runtime_types'; -import { Sort, Pagination } from '../../../../../common/http_api/log_analysis'; +import { AnomaliesSort, Pagination } from '../../../../../common/log_analysis'; interface RequestArgs { sourceId: string; startTime: number; endTime: number; - sort: Sort; + sort: AnomaliesSort; pagination: Pagination; datasets?: string[]; } diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_anomalies_results.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_anomalies_results.ts index 396c1ad3e1857..fbfe76f1473f5 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_anomalies_results.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_anomalies_results.ts @@ -9,21 +9,21 @@ import useMount from 'react-use/lib/useMount'; import { useTrackedPromise, CanceledPromiseError } from '../../../utils/use_tracked_promise'; import { callGetLogEntryAnomaliesAPI } from './service_calls/get_log_entry_anomalies'; import { callGetLogEntryAnomaliesDatasetsAPI } from './service_calls/get_log_entry_anomalies_datasets'; +import { GetLogEntryAnomaliesDatasetsSuccessResponsePayload } from '../../../../common/http_api/log_analysis'; import { - Sort, + AnomaliesSort, Pagination, PaginationCursor, - GetLogEntryAnomaliesDatasetsSuccessResponsePayload, LogEntryAnomaly, -} from '../../../../common/http_api/log_analysis'; +} from '../../../../common/log_analysis'; import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; -export type SortOptions = Sort; +export type SortOptions = AnomaliesSort; export type PaginationOptions = Pick; export type Page = number; export type FetchNextPage = () => void; export type FetchPreviousPage = () => void; -export type ChangeSortOptions = (sortOptions: Sort) => void; +export type ChangeSortOptions = (sortOptions: AnomaliesSort) => void; export type ChangePaginationOptions = (paginationOptions: PaginationOptions) => void; export type LogEntryAnomalies = LogEntryAnomaly[]; type LogEntryAnomaliesDatasets = GetLogEntryAnomaliesDatasetsSuccessResponsePayload['data']['datasets']; @@ -38,7 +38,7 @@ interface ReducerState { paginationCursor: Pagination['cursor'] | undefined; hasNextPage: boolean; paginationOptions: PaginationOptions; - sortOptions: Sort; + sortOptions: AnomaliesSort; timeRange: { start: number; end: number; @@ -53,7 +53,7 @@ type ReducerStateDefaults = Pick< type ReducerAction = | { type: 'changePaginationOptions'; payload: { paginationOptions: PaginationOptions } } - | { type: 'changeSortOptions'; payload: { sortOptions: Sort } } + | { type: 'changeSortOptions'; payload: { sortOptions: AnomaliesSort } } | { type: 'fetchNextPage' } | { type: 'fetchPreviousPage' } | { type: 'changeHasNextPage'; payload: { hasNextPage: boolean } } @@ -144,7 +144,7 @@ export const useLogEntryAnomaliesResults = ({ endTime: number; startTime: number; sourceId: string; - defaultSortOptions: Sort; + defaultSortOptions: AnomaliesSort; defaultPaginationOptions: Pick; onGetLogEntryAnomaliesDatasetsError?: (error: Error) => void; filteredDatasets?: string[]; @@ -225,7 +225,7 @@ export const useLogEntryAnomaliesResults = ({ ); const changeSortOptions = useCallback( - (nextSortOptions: Sort) => { + (nextSortOptions: AnomaliesSort) => { dispatch({ type: 'changeSortOptions', payload: { sortOptions: nextSortOptions } }); }, [dispatch] diff --git a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_examples.ts b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_examples.ts index e809ab9cd5a6f..90b8b03a81602 100644 --- a/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_examples.ts +++ b/x-pack/plugins/infra/public/pages/logs/log_entry_rate/use_log_entry_examples.ts @@ -6,7 +6,7 @@ import { useMemo, useState } from 'react'; -import { LogEntryExample } from '../../../../common/http_api'; +import { LogEntryExample } from '../../../../common/log_analysis'; import { useKibanaContextForPlugin } from '../../../hooks/use_kibana'; import { useTrackedPromise } from '../../../utils/use_tracked_promise'; import { callGetLogEntryExamplesAPI } from './service_calls/get_log_entry_examples'; diff --git a/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx b/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx index 3fa89da5b5e51..011653fd6eb47 100644 --- a/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx +++ b/x-pack/plugins/infra/public/pages/logs/stream/page_view_log_in_context.tsx @@ -16,7 +16,7 @@ import { import { FormattedMessage } from '@kbn/i18n/react'; import { isEmpty } from 'lodash'; import React, { useCallback, useContext, useMemo } from 'react'; -import { LogEntry } from '../../../../common/http_api'; +import { LogEntry } from '../../../../common/log_entry'; import { ViewLogInContext } from '../../../containers/logs/view_log_in_context'; import { useViewportDimensions } from '../../../utils/use_viewport_dimensions'; import { euiStyled } from '../../../../../../../src/plugins/kibana_react/common'; diff --git a/x-pack/plugins/infra/public/test_utils/entries.ts b/x-pack/plugins/infra/public/test_utils/entries.ts index 04c87d5f73902..96737fb175365 100644 --- a/x-pack/plugins/infra/public/test_utils/entries.ts +++ b/x-pack/plugins/infra/public/test_utils/entries.ts @@ -5,7 +5,7 @@ */ import faker from 'faker'; -import { LogEntry } from '../../common/http_api'; +import { LogEntry } from '../../common/log_entry'; import { LogSourceConfiguration } from '../containers/logs/log_source'; export const ENTRIES_EMPTY = { diff --git a/x-pack/plugins/infra/public/utils/log_entry/log_entry.ts b/x-pack/plugins/infra/public/utils/log_entry/log_entry.ts index bb528ee5b18c5..c69104ad6177e 100644 --- a/x-pack/plugins/infra/public/utils/log_entry/log_entry.ts +++ b/x-pack/plugins/infra/public/utils/log_entry/log_entry.ts @@ -17,7 +17,7 @@ import { LogMessagePart, LogMessageFieldPart, LogMessageConstantPart, -} from '../../../common/http_api'; +} from '../../../common/log_entry'; export type LogEntryMessageSegment = InfraLogEntryFields.Message; export type LogEntryConstantMessageSegment = InfraLogEntryFields.InfraLogMessageConstantSegmentInlineFragment; diff --git a/x-pack/plugins/infra/public/utils/log_entry/log_entry_highlight.ts b/x-pack/plugins/infra/public/utils/log_entry/log_entry_highlight.ts index abb004911214b..208316c693d4d 100644 --- a/x-pack/plugins/infra/public/utils/log_entry/log_entry_highlight.ts +++ b/x-pack/plugins/infra/public/utils/log_entry/log_entry_highlight.ts @@ -12,7 +12,7 @@ import { LogFieldColumn, LogMessagePart, LogMessageFieldPart, -} from '../../../common/http_api'; +} from '../../../common/log_entry'; export type LogEntryHighlightColumn = InfraLogEntryHighlightFields.Columns; export type LogEntryHighlightMessageColumn = InfraLogEntryHighlightFields.InfraLogEntryMessageColumnInlineFragment; diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts b/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts index 0b1df3abd465a..4c5debe58ed26 100644 --- a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts +++ b/x-pack/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts @@ -10,10 +10,9 @@ import type { InfraPluginRequestHandlerContext } from '../../../types'; import { LogEntriesSummaryBucket, LogEntriesSummaryHighlightsBucket, - LogEntry, - LogColumn, LogEntriesRequest, } from '../../../../common/http_api'; +import { LogEntry, LogColumn } from '../../../../common/log_entry'; import { InfraSourceConfiguration, InfraSources, diff --git a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/message.ts b/x-pack/plugins/infra/server/lib/domains/log_entries_domain/message.ts index 19ab82c9c5ac1..d04e036b33b21 100644 --- a/x-pack/plugins/infra/server/lib/domains/log_entries_domain/message.ts +++ b/x-pack/plugins/infra/server/lib/domains/log_entries_domain/message.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LogMessagePart } from '../../../../common/http_api/log_entries'; +import { LogMessagePart } from '../../../../common/log_entry'; import { JsonArray, JsonValue } from '../../../../../../../src/plugins/kibana_utils/common'; import { LogMessageFormattingCondition, diff --git a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_anomalies.ts b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_anomalies.ts index c6a4593912280..fbcc3671f08a2 100644 --- a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_anomalies.ts +++ b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_anomalies.ts @@ -12,12 +12,11 @@ import { logEntryCategoriesJobTypes, logEntryRateJobTypes, jobCustomSettingsRT, -} from '../../../common/log_analysis'; -import { - Sort, + LogEntryAnomalyDatasets, + AnomaliesSort, Pagination, - GetLogEntryAnomaliesRequestPayload, -} from '../../../common/http_api/log_analysis'; + isCategoryAnomaly, +} from '../../../common/log_analysis'; import type { MlSystem, MlAnomalyDetectors } from '../../types'; import { createLogEntryAnomaliesQuery, logEntryAnomaliesResponseRT } from './queries'; import { @@ -95,9 +94,9 @@ export async function getLogEntryAnomalies( sourceId: string, startTime: number, endTime: number, - sort: Sort, + sort: AnomaliesSort, pagination: Pagination, - datasets: GetLogEntryAnomaliesRequestPayload['data']['datasets'] + datasets?: LogEntryAnomalyDatasets ) { const finalizeLogEntryAnomaliesSpan = startTracingSpan('get log entry anomalies'); @@ -131,7 +130,7 @@ export async function getLogEntryAnomalies( datasets ); - const data = anomalies.map((anomaly) => { + const parsedAnomalies = anomalies.map((anomaly) => { const { jobId } = anomaly; if (!anomaly.categoryId) { @@ -141,10 +140,41 @@ export async function getLogEntryAnomalies( } }); + const categoryIds = parsedAnomalies.reduce((acc, anomaly) => { + return isCategoryAnomaly(anomaly) ? [...acc, parseInt(anomaly.categoryId, 10)] : acc; + }, []); + + const logEntryCategoriesCountJobId = getJobId( + context.infra.spaceId, + sourceId, + logEntryCategoriesJobTypes[0] + ); + + const { logEntryCategoriesById } = await fetchLogEntryCategories( + context, + logEntryCategoriesCountJobId, + categoryIds + ); + + const parsedAnomaliesWithExpandedCategoryInformation = parsedAnomalies.map((anomaly) => { + if (isCategoryAnomaly(anomaly)) { + if (logEntryCategoriesById[parseInt(anomaly.categoryId, 10)]) { + const { + _source: { regex, terms }, + } = logEntryCategoriesById[parseInt(anomaly.categoryId, 10)]; + return { ...anomaly, ...{ categoryRegex: regex, categoryTerms: terms } }; + } else { + return { ...anomaly, ...{ categoryRegex: '', categoryTerms: '' } }; + } + } else { + return anomaly; + } + }); + const logEntryAnomaliesSpan = finalizeLogEntryAnomaliesSpan(); return { - data, + data: parsedAnomaliesWithExpandedCategoryInformation, paginationCursors, hasMoreEntries, timing: { @@ -208,9 +238,9 @@ async function fetchLogEntryAnomalies( jobIds: string[], startTime: number, endTime: number, - sort: Sort, + sort: AnomaliesSort, pagination: Pagination, - datasets: GetLogEntryAnomaliesRequestPayload['data']['datasets'] + datasets?: LogEntryAnomalyDatasets ) { // We'll request 1 extra entry on top of our pageSize to determine if there are // more entries to be fetched. This avoids scenarios where the client side can't diff --git a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts index 7dd5aae9784f5..071a8a94e009b 100644 --- a/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts +++ b/x-pack/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts @@ -5,14 +5,14 @@ */ import type { ILegacyScopedClusterClient } from 'src/core/server'; -import { LogEntryContext } from '../../../common/http_api'; +import { LogEntryContext } from '../../../common/log_entry'; import { compareDatasetsByMaximumAnomalyScore, getJobId, jobCustomSettingsRT, logEntryCategoriesJobTypes, + CategoriesSort, } from '../../../common/log_analysis'; -import { CategorySort } from '../../../common/http_api/log_analysis'; import { startTracingSpan } from '../../../common/performance_tracing'; import { decodeOrThrow } from '../../../common/runtime_types'; import type { MlAnomalyDetectors, MlSystem } from '../../types'; @@ -51,7 +51,7 @@ export async function getTopLogEntryCategories( categoryCount: number, datasets: string[], histograms: HistogramParameters[], - sort: CategorySort + sort: CategoriesSort ) { const finalizeTopLogEntryCategoriesSpan = startTracingSpan('get top categories'); @@ -218,7 +218,7 @@ async function fetchTopLogEntryCategories( endTime: number, categoryCount: number, datasets: string[], - sort: CategorySort + sort: CategoriesSort ) { const finalizeEsSearchSpan = startTracingSpan('Fetch top categories from ES'); diff --git a/x-pack/plugins/infra/server/lib/log_analysis/queries/log_entry_anomalies.ts b/x-pack/plugins/infra/server/lib/log_analysis/queries/log_entry_anomalies.ts index e692ed019cf86..8e01cafcf62ae 100644 --- a/x-pack/plugins/infra/server/lib/log_analysis/queries/log_entry_anomalies.ts +++ b/x-pack/plugins/infra/server/lib/log_analysis/queries/log_entry_anomalies.ts @@ -14,10 +14,10 @@ import { createDatasetsFilters, } from './common'; import { - Sort, + AnomaliesSort, + LogEntryAnomalyDatasets, Pagination, - GetLogEntryAnomaliesRequestPayload, -} from '../../../../common/http_api/log_analysis'; +} from '../../../../common/log_analysis'; // TODO: Reassess validity of this against ML docs const TIEBREAKER_FIELD = '_doc'; @@ -32,9 +32,9 @@ export const createLogEntryAnomaliesQuery = ( jobIds: string[], startTime: number, endTime: number, - sort: Sort, + sort: AnomaliesSort, pagination: Pagination, - datasets: GetLogEntryAnomaliesRequestPayload['data']['datasets'] + datasets?: LogEntryAnomalyDatasets ) => { const { field } = sort; const { pageSize } = pagination; @@ -118,7 +118,7 @@ export const logEntryAnomaliesResponseRT = rt.intersection([ export type LogEntryAnomaliesResponseRT = rt.TypeOf; -const parsePaginationCursor = (sort: Sort, pagination: Pagination) => { +const parsePaginationCursor = (sort: AnomaliesSort, pagination: Pagination) => { const { cursor } = pagination; const { direction } = sort; diff --git a/x-pack/plugins/infra/server/lib/log_analysis/queries/top_log_entry_categories.ts b/x-pack/plugins/infra/server/lib/log_analysis/queries/top_log_entry_categories.ts index 057054b427227..f1363900d3696 100644 --- a/x-pack/plugins/infra/server/lib/log_analysis/queries/top_log_entry_categories.ts +++ b/x-pack/plugins/infra/server/lib/log_analysis/queries/top_log_entry_categories.ts @@ -14,13 +14,13 @@ import { createDatasetsFilters, } from './common'; -import { CategorySort } from '../../../../common/http_api/log_analysis'; +import { CategoriesSort } from '../../../../common/log_analysis'; type CategoryAggregationOrder = | 'filter_record>maximum_record_score' | 'filter_model_plot>sum_actual'; const getAggregationOrderForSortField = ( - field: CategorySort['field'] + field: CategoriesSort['field'] ): CategoryAggregationOrder => { switch (field) { case 'maximumAnomalyScore': @@ -40,7 +40,7 @@ export const createTopLogEntryCategoriesQuery = ( endTime: number, size: number, datasets: string[], - sort: CategorySort + sort: CategoriesSort ) => ({ ...defaultRequestParameters, body: { diff --git a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_anomalies.ts b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_anomalies.ts index ec2bc6e5ed739..42d126d4ef036 100644 --- a/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_anomalies.ts +++ b/x-pack/plugins/infra/server/routes/log_analysis/results/log_entry_anomalies.ts @@ -11,9 +11,8 @@ import { getLogEntryAnomaliesSuccessReponsePayloadRT, getLogEntryAnomaliesRequestPayloadRT, GetLogEntryAnomaliesRequestPayload, - Sort, - Pagination, } from '../../../../common/http_api/log_analysis'; +import { AnomaliesSort, Pagination } from '../../../../common/log_analysis'; import { createValidationFunction } from '../../../../common/runtime_types'; import { assertHasInfraMlPlugins } from '../../../utils/request_context'; import { getLogEntryAnomalies } from '../../../lib/log_analysis'; @@ -98,7 +97,7 @@ const getSortAndPagination = ( sort: Partial = {}, pagination: Partial = {} ): { - sort: Sort; + sort: AnomaliesSort; pagination: Pagination; } => { const sortDefaults = { diff --git a/x-pack/plugins/lens/kibana.json b/x-pack/plugins/lens/kibana.json index dc0a92ac702d0..9df3f41fbd855 100644 --- a/x-pack/plugins/lens/kibana.json +++ b/x-pack/plugins/lens/kibana.json @@ -19,5 +19,5 @@ "optionalPlugins": ["usageCollection", "taskManager", "globalSearch", "savedObjectsTagging"], "configPath": ["xpack", "lens"], "extraPublicDirs": ["common/constants"], - "requiredBundles": ["savedObjects", "kibanaUtils", "kibanaReact", "embeddable", "lensOss", "presentationUtil"] + "requiredBundles": ["savedObjects", "kibanaUtils", "kibanaReact", "embeddable", "presentationUtil"] } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.test.tsx b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.test.tsx index 0891dd27fcf17..ed1b695640922 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.test.tsx +++ b/x-pack/plugins/lens/public/indexpattern_datasource/dimension_panel/reference_editor.test.tsx @@ -13,7 +13,7 @@ import type { IUiSettingsClient, SavedObjectsClientContract, HttpSetup } from 'k import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import type { DataPublicPluginStart } from 'src/plugins/data/public'; import { OperationMetadata } from '../../types'; -import { createMockedIndexPattern } from '../mocks'; +import { createMockedIndexPattern, createMockedIndexPatternWithoutType } from '../mocks'; import { ReferenceEditor, ReferenceEditorProps } from './reference_editor'; import { insertOrReplaceColumn } from '../operations'; import { FieldSelect } from './field_select'; @@ -260,6 +260,48 @@ describe('reference editor', () => { ); }); + it("should show the sub-function as invalid if there's no field compatible with it", () => { + // This may happen for saved objects after changing the type of a field + wrapper = mount( + true, + }} + /> + ); + + const subFunctionSelect = wrapper + .find('[data-test-subj="indexPattern-reference-function"]') + .first(); + + expect(subFunctionSelect.prop('isInvalid')).toEqual(true); + expect(subFunctionSelect.prop('selectedOptions')).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + 'data-test-subj': 'lns-indexPatternDimension-avg incompatible', + label: 'Average', + value: 'avg', + }), + ]) + ); + }); + it('should hide the function selector when using a field-only selection style', () => { wrapper = mount( { expect(fieldSelect.prop('markAllFieldsCompatible')).toEqual(true); }); + it('should show the FieldSelect as invalid if the selected field is missing', () => { + wrapper = mount( + true, + }} + /> + ); + + const fieldSelect = wrapper.find(FieldSelect); + expect(fieldSelect.prop('fieldIsInvalid')).toEqual(true); + expect(fieldSelect.prop('selectedField')).toEqual('missing'); + expect(fieldSelect.prop('selectedOperationType')).toEqual('avg'); + expect(fieldSelect.prop('incompleteOperation')).toBeUndefined(); + expect(fieldSelect.prop('markAllFieldsCompatible')).toEqual(false); + }); + it('should show the ParamEditor for functions that offer one', () => { wrapper = mount( value === incompleteInfo.operationType)!] + const selectedOption = incompleteOperation + ? [functionOptions.find(({ value }) => value === incompleteOperation)!] : column ? [functionOptions.find(({ value }) => value === column.operationType)!] : []; // If the operationType is incomplete, the user needs to select a field- so // the function is marked as valid. - const showOperationInvalid = !column && !Boolean(incompleteInfo?.operationType); + const showOperationInvalid = !column && !Boolean(incompleteOperation); // The field is invalid if the operation has been updated without a field, // or if we are in a field-only mode but empty state - const showFieldInvalid = - Boolean(incompleteInfo?.operationType) || (selectionStyle === 'field' && !column); + const showFieldInvalid = Boolean(incompleteOperation) || (selectionStyle === 'field' && !column); + // Check if the field still exists to protect from changes + const showFieldMissingInvalid = !currentIndexPattern.getFieldByName( + incompleteField ?? (column as FieldBasedIndexPatternColumn)?.sourceField + ); + + // what about a field changing type and becoming invalid? + // Let's say this change makes the indexpattern without any number field but the operation was set to a numeric operation. + // At this point the ComboBox will crash. + // Therefore check if the selectedOption is in functionOptions and in case fill it in as disabled option + const showSelectionFunctionInvalid = Boolean(selectedOption.length && selectedOption[0] == null); + if (showSelectionFunctionInvalid) { + const selectedOperationType = incompleteOperation || column.operationType; + const brokenFunctionOption = { + label: operationPanels[selectedOperationType].displayName, + value: selectedOperationType, + className: 'lnsIndexPatternDimensionEditor__operation', + 'data-test-subj': `lns-indexPatternDimension-${selectedOperationType} incompatible`, + }; + functionOptions.push(brokenFunctionOption); + selectedOption[0] = brokenFunctionOption; + } return (
@@ -216,7 +236,7 @@ export function ReferenceEditor(props: ReferenceEditorProps) { defaultMessage: 'Choose a sub-function', })} fullWidth - isInvalid={showOperationInvalid} + isInvalid={showOperationInvalid || showSelectionFunctionInvalid} > { @@ -258,11 +278,11 @@ export function ReferenceEditor(props: ReferenceEditorProps) { defaultMessage: 'Select a field', })} fullWidth - isInvalid={showFieldInvalid} + isInvalid={showFieldInvalid || showFieldMissingInvalid} labelAppend={labelAppend} > - (getErrorMessages(layer) ?? []).map((message) => ({ - shortMessage: '', // Not displayed currently - longMessage: message, - })) + (getErrorMessages(layer, state.indexPatterns[layer.indexPatternId]) ?? []).map( + (message) => ({ + shortMessage: '', // Not displayed currently + longMessage: message, + }) + ) ); // Single layer case, no need to explain more diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/mocks.ts b/x-pack/plugins/lens/public/indexpattern_datasource/mocks.ts index d0cbcee61db6f..4aea9e8ac67a9 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/mocks.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/mocks.ts @@ -6,7 +6,83 @@ import { DragContextState } from '../drag_drop'; import { getFieldByNameFactory } from './pure_helpers'; -import type { IndexPattern } from './types'; +import type { IndexPattern, IndexPatternField } from './types'; + +export const createMockedIndexPatternWithoutType = ( + typeToFilter: IndexPatternField['type'] +): IndexPattern => { + const fields = [ + { + name: 'timestamp', + displayName: 'timestampLabel', + type: 'date', + aggregatable: true, + searchable: true, + }, + { + name: 'start_date', + displayName: 'start_date', + type: 'date', + aggregatable: true, + searchable: true, + }, + { + name: 'bytes', + displayName: 'bytes', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'memory', + displayName: 'memory', + type: 'number', + aggregatable: true, + searchable: true, + }, + { + name: 'source', + displayName: 'source', + type: 'string', + aggregatable: true, + searchable: true, + esTypes: ['keyword'], + }, + { + name: 'unsupported', + displayName: 'unsupported', + type: 'geo', + aggregatable: true, + searchable: true, + }, + { + name: 'dest', + displayName: 'dest', + type: 'string', + aggregatable: true, + searchable: true, + esTypes: ['keyword'], + }, + { + name: 'scripted', + displayName: 'Scripted', + type: 'string', + searchable: true, + aggregatable: true, + scripted: true, + lang: 'painless', + script: '1234', + }, + ].filter(({ type }) => type !== typeToFilter); + return { + id: '1', + title: 'my-fake-index-pattern', + timeFieldName: 'timestamp', + hasRestrictions: false, + fields, + getFieldByName: getFieldByNameFactory(fields), + }; +}; export const createMockedIndexPattern = (): IndexPattern => { const fields = [ diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts index 7dbc7d3b986a5..1cdaff53c5458 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/definitions/index.ts @@ -197,7 +197,7 @@ interface BaseOperationDefinitionProps { getErrorMessage?: ( layer: IndexPatternLayer, columnId: string, - indexPattern?: IndexPattern + indexPattern: IndexPattern ) => string[] | undefined; /* @@ -301,7 +301,7 @@ interface FieldBasedOperationDefinition { getErrorMessage: ( layer: IndexPatternLayer, columnId: string, - indexPattern?: IndexPattern + indexPattern: IndexPattern ) => string[] | undefined; } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts index c0e71dc1509e7..63f8bfb97d8f8 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.test.ts @@ -2147,14 +2147,17 @@ describe('state_helpers', () => { it('should collect errors from metric-type operation definitions', () => { const mock = jest.fn().mockReturnValue(['error 1']); operationDefinitionMap.avg.getErrorMessage = mock; - const errors = getErrorMessages({ - indexPatternId: '1', - columnOrder: [], - columns: { - // @ts-expect-error invalid column - col1: { operationType: 'avg' }, + const errors = getErrorMessages( + { + indexPatternId: '1', + columnOrder: [], + columns: { + // @ts-expect-error invalid column + col1: { operationType: 'avg' }, + }, }, - }); + indexPattern + ); expect(mock).toHaveBeenCalled(); expect(errors).toHaveLength(1); }); @@ -2162,20 +2165,23 @@ describe('state_helpers', () => { it('should collect errors from reference-type operation definitions', () => { const mock = jest.fn().mockReturnValue(['error 1']); operationDefinitionMap.testReference.getErrorMessage = mock; - const errors = getErrorMessages({ - indexPatternId: '1', - columnOrder: [], - columns: { - col1: - // @ts-expect-error not statically analyzed - { operationType: 'testReference', references: [] }, + const errors = getErrorMessages( + { + indexPatternId: '1', + columnOrder: [], + columns: { + col1: + // @ts-expect-error not statically analyzed + { operationType: 'testReference', references: [] }, + }, }, - }); + indexPattern + ); expect(mock).toHaveBeenCalled(); expect(errors).toHaveLength(1); }); - it('should consider incompleteColumns before layer columns', () => { + it('should ignore incompleteColumns when checking for errors', () => { const savedRef = jest.fn().mockReturnValue(['error 1']); const incompleteRef = jest.fn(); operationDefinitionMap.testReference.getErrorMessage = savedRef; @@ -2184,24 +2190,55 @@ describe('state_helpers', () => { getErrorMessage: incompleteRef, }; - const errors = getErrorMessages({ - indexPatternId: '1', - columnOrder: [], - columns: { - col1: + const errors = getErrorMessages( + { + indexPatternId: '1', + columnOrder: [], + columns: { + col1: + // @ts-expect-error not statically analyzed + { operationType: 'testReference', references: [] }, + }, + incompleteColumns: { // @ts-expect-error not statically analyzed - { operationType: 'testReference', references: [] }, - }, - incompleteColumns: { - // @ts-expect-error not statically analyzed - col1: { operationType: 'testIncompleteReference' }, + col1: { operationType: 'testIncompleteReference' }, + }, }, - }); - expect(savedRef).not.toHaveBeenCalled(); - expect(incompleteRef).toHaveBeenCalled(); - expect(errors).toBeUndefined(); + indexPattern + ); + expect(savedRef).toHaveBeenCalled(); + expect(incompleteRef).not.toHaveBeenCalled(); + expect(errors).toHaveLength(1); delete operationDefinitionMap.testIncompleteReference; }); + + it('should forward the indexpattern when available', () => { + const mock = jest.fn(); + operationDefinitionMap.testReference.getErrorMessage = mock; + getErrorMessages( + { + indexPatternId: '1', + columnOrder: [], + columns: { + col1: + // @ts-expect-error not statically analyzed + { operationType: 'testReference', references: [] }, + }, + }, + indexPattern + ); + expect(mock).toHaveBeenCalledWith( + { + indexPatternId: '1', + columnOrder: [], + columns: { + col1: { operationType: 'testReference', references: [] }, + }, + }, + 'col1', + indexPattern + ); + }); }); }); diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts index d8244f3902a6e..7c0036de62124 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/operations/layer_helpers.ts @@ -864,18 +864,19 @@ export function updateLayerIndexPattern( * - All column references are valid * - All prerequisites are met */ -export function getErrorMessages(layer: IndexPatternLayer): string[] | undefined { - const errors: string[] = []; - Object.entries(layer.columns).forEach(([columnId, column]) => { - // If we're transitioning to another operation, check for "new" incompleteColumns rather - // than "old" saved operation on the layer - const columnFinalRef = - layer.incompleteColumns?.[columnId]?.operationType || column.operationType; - const def = operationDefinitionMap[columnFinalRef]; - if (def.getErrorMessage) { - errors.push(...(def.getErrorMessage(layer, columnId) ?? [])); - } - }); +export function getErrorMessages( + layer: IndexPatternLayer, + indexPattern: IndexPattern +): string[] | undefined { + const errors: string[] = Object.entries(layer.columns) + .flatMap(([columnId, column]) => { + const def = operationDefinitionMap[column.operationType]; + if (def.getErrorMessage) { + return def.getErrorMessage(layer, columnId, indexPattern); + } + }) + // remove the undefined values + .filter((v: string | undefined): v is string => v != null); return errors.length ? errors : undefined; } diff --git a/x-pack/plugins/lens/public/indexpattern_datasource/utils.ts b/x-pack/plugins/lens/public/indexpattern_datasource/utils.ts index 57cc4abeb723a..b5a4905a29738 100644 --- a/x-pack/plugins/lens/public/indexpattern_datasource/utils.ts +++ b/x-pack/plugins/lens/public/indexpattern_datasource/utils.ts @@ -7,9 +7,10 @@ import { DataType } from '../types'; import { IndexPattern, IndexPatternLayer } from './types'; import { DraggedField } from './indexpattern'; -import { +import type { BaseIndexPatternColumn, FieldBasedIndexPatternColumn, + ReferenceBasedIndexPatternColumn, } from './operations/definitions/column_types'; import { operationDefinitionMap, IndexPatternColumn } from './operations'; @@ -53,12 +54,29 @@ export function isColumnInvalid( if (!column) return; const operationDefinition = column.operationType && operationDefinitionMap[column.operationType]; - return !!( - operationDefinition.getErrorMessage && - operationDefinition.getErrorMessage(layer, columnId, indexPattern) + // check also references for errors + const referencesHaveErrors = + true && + 'references' in column && + Boolean(getReferencesErrors(layer, column, indexPattern).filter(Boolean).length); + + return ( + !!operationDefinition.getErrorMessage?.(layer, columnId, indexPattern) || referencesHaveErrors ); } +function getReferencesErrors( + layer: IndexPatternLayer, + column: ReferenceBasedIndexPatternColumn, + indexPattern: IndexPattern +) { + return column.references?.map((referenceId: string) => { + const referencedOperation = layer.columns[referenceId]?.operationType; + const referencedDefinition = operationDefinitionMap[referencedOperation]; + return referencedDefinition?.getErrorMessage?.(layer, referenceId, indexPattern); + }); +} + export function fieldIsInvalid(column: IndexPatternColumn | undefined, indexPattern: IndexPattern) { if (!column || !hasField(column)) { return false; diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts index cdffdb342fd23..3fb7186aeac59 100644 --- a/x-pack/plugins/lens/public/plugin.ts +++ b/x-pack/plugins/lens/public/plugin.ts @@ -39,7 +39,6 @@ import { VISUALIZE_FIELD_TRIGGER, } from '../../../../src/plugins/ui_actions/public'; import { getEditPath, NOT_INTERNATIONALIZED_PRODUCT_NAME } from '../common'; -import { PLUGIN_ID_OSS } from '../../../../src/plugins/lens_oss/common/constants'; import { EditorFrameStart } from './types'; import { getLensAliasConfig } from './vis_type_alias'; import { visualizeFieldAction } from './trigger_actions/visualize_field_actions'; @@ -208,8 +207,6 @@ export class LensPlugin { start(core: CoreStart, startDependencies: LensPluginStartDependencies): LensPublicStart { const frameStart = this.editorFrameService.start(core, startDependencies); this.createEditorFrame = frameStart.createInstance; - // unregisters the OSS alias - startDependencies.visualizations.unRegisterAlias(PLUGIN_ID_OSS); // unregisters the Visualize action and registers the lens one if (startDependencies.uiActions.hasAction(ACTION_VISUALIZE_FIELD)) { startDependencies.uiActions.unregisterAction(ACTION_VISUALIZE_FIELD); diff --git a/x-pack/plugins/lens/server/usage/collectors.ts b/x-pack/plugins/lens/server/usage/collectors.ts index c32fc0371ed8a..71a699aadb865 100644 --- a/x-pack/plugins/lens/server/usage/collectors.ts +++ b/x-pack/plugins/lens/server/usage/collectors.ts @@ -16,11 +16,6 @@ export function registerLensUsageCollector( usageCollection: UsageCollectionSetup, taskManager: Promise ) { - let isCollectorReady = false; - taskManager.then(() => { - // mark lensUsageCollector as ready to collect when the TaskManager is ready - isCollectorReady = true; - }); const lensUsageCollector = usageCollection.makeUsageCollector({ type: 'lens', async fetch() { @@ -55,7 +50,10 @@ export function registerLensUsageCollector( }; } }, - isReady: () => isCollectorReady, + isReady: async () => { + await taskManager; + return true; + }, schema: lensUsageSchema, }); diff --git a/x-pack/plugins/lens/tsconfig.json b/x-pack/plugins/lens/tsconfig.json index 7ac5a2980d0ba..636d2f44b0217 100644 --- a/x-pack/plugins/lens/tsconfig.json +++ b/x-pack/plugins/lens/tsconfig.json @@ -35,7 +35,6 @@ { "path": "../../../src/plugins/kibana_utils/tsconfig.json" }, { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, { "path": "../../../src/plugins/embeddable/tsconfig.json"}, - { "path": "../../../src/plugins/lens_oss/tsconfig.json"}, { "path": "../../../src/plugins/presentation_util/tsconfig.json"}, ] } \ No newline at end of file diff --git a/x-pack/plugins/license_management/tsconfig.json b/x-pack/plugins/license_management/tsconfig.json new file mode 100644 index 0000000000000..e6cb0101ee838 --- /dev/null +++ b/x-pack/plugins/license_management/tsconfig.json @@ -0,0 +1,29 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "public/**/*", + "server/**/*", + "common/**/*", + "__jest__/**/*", + "__mocks__/*", + ], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + { "path": "../../../src/plugins/kibana_react/tsconfig.json" }, + { "path": "../../../src/plugins/telemetry_management_section/tsconfig.json" }, + { "path": "../../../src/plugins/home/tsconfig.json" }, + { "path": "../../../src/plugins/management/tsconfig.json" }, + { "path": "../../../src/plugins/telemetry/tsconfig.json" }, + { "path": "../../../src/plugins/es_ui_shared/tsconfig.json" }, + { "path": "../licensing/tsconfig.json"}, + { "path": "../features/tsconfig.json"}, + { "path": "../security/tsconfig.json"}, + ] +} diff --git a/x-pack/plugins/maps/kibana.json b/x-pack/plugins/maps/kibana.json index 5f6f5224e32a3..2536601d0e6b1 100644 --- a/x-pack/plugins/maps/kibana.json +++ b/x-pack/plugins/maps/kibana.json @@ -23,5 +23,5 @@ "ui": true, "server": true, "extraPublicDirs": ["common/constants"], - "requiredBundles": ["kibanaReact", "kibanaUtils", "home", "mapsOss", "presentationUtil"] + "requiredBundles": ["kibanaReact", "kibanaUtils", "home", "presentationUtil"] } diff --git a/x-pack/plugins/maps/public/connected_components/add_layer_panel/view.tsx b/x-pack/plugins/maps/public/connected_components/add_layer_panel/view.tsx index e2529fff66f3b..78a9f82bb698f 100644 --- a/x-pack/plugins/maps/public/connected_components/add_layer_panel/view.tsx +++ b/x-pack/plugins/maps/public/connected_components/add_layer_panel/view.tsx @@ -26,7 +26,7 @@ const ADD_LAYER_STEP_LABEL = i18n.translate('xpack.maps.addLayerPanel.addLayer', }); const SELECT_WIZARD_LABEL = ADD_LAYER_STEP_LABEL; -interface Props { +export interface Props { addPreviewLayers: (layerDescriptors: LayerDescriptor[]) => void; closeFlyout: () => void; hasPreviewLayers: boolean; diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/join_editor.tsx b/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/join_editor.tsx index 1bcee961db9e1..d47f130d4ede3 100644 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/join_editor.tsx +++ b/x-pack/plugins/maps/public/connected_components/layer_panel/join_editor/join_editor.tsx @@ -25,7 +25,7 @@ import { ILayer } from '../../../classes/layers/layer'; import { JoinDescriptor } from '../../../../common/descriptor_types'; import { IField } from '../../../classes/fields/field'; -interface Props { +export interface Props { joins: JoinDescriptor[]; layer: ILayer; layerDisplayName: string; diff --git a/x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.tsx b/x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.tsx index 33d684b320208..c0462f824cd06 100644 --- a/x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.tsx +++ b/x-pack/plugins/maps/public/connected_components/layer_panel/layer_settings/layer_settings.tsx @@ -21,7 +21,7 @@ import { AlphaSlider } from '../../../components/alpha_slider'; import { ValidatedDualRange } from '../../../../../../../src/plugins/kibana_react/public'; import { ILayer } from '../../../classes/layers/layer'; -interface Props { +export interface Props { layer: ILayer; updateLabel: (layerId: string, label: string) => void; updateMinZoom: (layerId: string, minZoom: number) => void; diff --git a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx index 93476f6e14da5..36d07e3870818 100644 --- a/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx +++ b/x-pack/plugins/maps/public/connected_components/map_container/map_container.tsx @@ -35,7 +35,7 @@ import 'mapbox-gl/dist/mapbox-gl.css'; const RENDER_COMPLETE_EVENT = 'renderComplete'; -interface Props { +export interface Props { addFilters: ((filters: Filter[]) => Promise) | null; getFilterActions?: () => Promise; getActionContext?: () => ActionExecutionContext; diff --git a/x-pack/plugins/maps/public/connected_components/map_settings_panel/map_settings_panel.tsx b/x-pack/plugins/maps/public/connected_components/map_settings_panel/map_settings_panel.tsx index 726e2c3be7846..9cbbdec5e3d17 100644 --- a/x-pack/plugins/maps/public/connected_components/map_settings_panel/map_settings_panel.tsx +++ b/x-pack/plugins/maps/public/connected_components/map_settings_panel/map_settings_panel.tsx @@ -23,7 +23,7 @@ import { SpatialFiltersPanel } from './spatial_filters_panel'; import { DisplayPanel } from './display_panel'; import { MapCenter } from '../../../common/descriptor_types'; -interface Props { +export interface Props { cancelChanges: () => void; center: MapCenter; hasMapSettingsChanges: boolean; diff --git a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx index 820453f166a46..21a8abcbaa4e9 100644 --- a/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx +++ b/x-pack/plugins/maps/public/connected_components/mb_map/mb_map.tsx @@ -49,7 +49,7 @@ import mbWorkerUrl from '!!file-loader!mapbox-gl/dist/mapbox-gl-csp-worker'; mapboxgl.workerUrl = mbWorkerUrl; mapboxgl.setRTLTextPlugin(mbRtlPlugin); -interface Props { +export interface Props { isMapReady: boolean; settings: MapSettings; layerList: ILayer[]; diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/fit_to_data.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/fit_to_data.tsx index 3f56d8d50b0f0..edf626612cb69 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/fit_to_data.tsx +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/fit_to_data/fit_to_data.tsx @@ -10,7 +10,7 @@ import { EuiButtonIcon } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { ILayer } from '../../../classes/layers/layer'; -interface Props { +export interface Props { layerList: ILayer[]; fitToBounds: () => void; } diff --git a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx index ef320c73bce2f..a0f3aa40e75dd 100644 --- a/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx +++ b/x-pack/plugins/maps/public/connected_components/toolbar_overlay/tools_control/tools_control.tsx @@ -50,7 +50,7 @@ const DRAW_DISTANCE_LABEL_SHORT = i18n.translate( } ); -interface Props { +export interface Props { cancelDraw: () => void; geoFields: GeoFieldWithIndex[]; initiateDraw: (drawState: DrawState) => void; diff --git a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx index 4d669dfbe235e..fd0a0d55d2c1b 100644 --- a/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx +++ b/x-pack/plugins/maps/public/connected_components/widget_overlay/layer_control/layer_toc/toc_entry/toc_entry_actions_popover/toc_entry_actions_popover.tsx @@ -11,7 +11,7 @@ import { i18n } from '@kbn/i18n'; import { ILayer } from '../../../../../../classes/layers/layer'; import { TOCEntryButton } from '../toc_entry_button'; -interface Props { +export interface Props { cloneLayer: (layerId: string) => void; displayName: string; editLayer: () => void; diff --git a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx index 67981ab3aede5..bcdc23bddd2eb 100644 --- a/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx +++ b/x-pack/plugins/maps/public/embeddable/map_embeddable.tsx @@ -68,7 +68,7 @@ import { MapEmbeddableInput, MapEmbeddableOutput, } from './types'; -export { MapEmbeddableInput }; +export { MapEmbeddableInput, MapEmbeddableOutput }; export class MapEmbeddable extends Embeddable diff --git a/x-pack/plugins/maps/public/plugin.ts b/x-pack/plugins/maps/public/plugin.ts index dd256126fae62..4173328a41d57 100644 --- a/x-pack/plugins/maps/public/plugin.ts +++ b/x-pack/plugins/maps/public/plugin.ts @@ -34,7 +34,6 @@ import { VisualizationsStart, } from '../../../../src/plugins/visualizations/public'; import { APP_ICON_SOLUTION, APP_ID, MAP_SAVED_OBJECT_TYPE } from '../common/constants'; -import { PLUGIN_ID_OSS } from '../../../../src/plugins/maps_oss/common/constants'; import { VISUALIZE_GEO_FIELD_TRIGGER } from '../../../../src/plugins/ui_actions/public'; import { createMapsUrlGenerator, @@ -162,9 +161,6 @@ export class MapsPlugin setLicensingPluginStart(plugins.licensing); setStartServices(core, plugins); - // unregisters the OSS alias - plugins.visualizations.unRegisterAlias(PLUGIN_ID_OSS); - if (core.application.capabilities.maps.show) { plugins.uiActions.addTriggerAction(VISUALIZE_GEO_FIELD_TRIGGER, visualizeGeoFieldAction); } diff --git a/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx b/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx index 817fbf3656103..c0a378f38fc13 100644 --- a/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx +++ b/x-pack/plugins/maps/public/routes/map_page/map_app/map_app.tsx @@ -52,7 +52,7 @@ import { unsavedChangesWarning, } from '../saved_map'; -interface Props { +export interface Props { savedMap: SavedMap; // saveCounter used to trigger MapApp render after SaveMap.save saveCounter: number; @@ -83,7 +83,7 @@ interface Props { setHeaderActionMenu: AppMountParameters['setHeaderActionMenu']; } -interface State { +export interface State { initialized: boolean; indexPatterns: IndexPattern[]; savedQuery?: SavedQuery; diff --git a/x-pack/plugins/maps/public/routes/map_page/url_state/global_sync.ts b/x-pack/plugins/maps/public/routes/map_page/url_state/global_sync.ts index 7fefc6662ada7..398c05b8ed69a 100644 --- a/x-pack/plugins/maps/public/routes/map_page/url_state/global_sync.ts +++ b/x-pack/plugins/maps/public/routes/map_page/url_state/global_sync.ts @@ -30,6 +30,6 @@ export function updateGlobalState(newState: MapsGlobalState, flushUrlState = fal ...newState, }); if (flushUrlState) { - kbnUrlStateStorage.flush({ replace: true }); + kbnUrlStateStorage.kbnUrlControls.flush(true); } } diff --git a/x-pack/plugins/maps/public/url_generator.ts b/x-pack/plugins/maps/public/url_generator.ts index be6a7f5fe6fa7..7f4215f4b1275 100644 --- a/x-pack/plugins/maps/public/url_generator.ts +++ b/x-pack/plugins/maps/public/url_generator.ts @@ -3,6 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import rison from 'rison-node'; import { TimeRange, diff --git a/x-pack/plugins/maps/tsconfig.json b/x-pack/plugins/maps/tsconfig.json new file mode 100644 index 0000000000000..b70459c690c07 --- /dev/null +++ b/x-pack/plugins/maps/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": [ + "common/**/*", + "public/**/*", + "server/**/*", + "config.ts", + "../../../typings/**/*", + ], + "references": [ + { "path": "../../../src/core/tsconfig.json" }, + { "path": "../../../src/plugins/maps_legacy/tsconfig.json" }, + { "path": "../features/tsconfig.json" }, + { "path": "../licensing/tsconfig.json" }, + { "path": "../maps_file_upload/tsconfig.json" }, + { "path": "../saved_objects_tagging/tsconfig.json" }, + ] +} diff --git a/x-pack/plugins/maps_file_upload/tsconfig.json b/x-pack/plugins/maps_file_upload/tsconfig.json new file mode 100644 index 0000000000000..f068d62b71739 --- /dev/null +++ b/x-pack/plugins/maps_file_upload/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": ["common/**/*", "public/**/*", "server/**/*", "mappings.ts"], + "references": [ + { "path": "../../../src/plugins/data/tsconfig.json" }, + { "path": "../../../src/plugins/usage_collection/tsconfig.json" } + ] +} diff --git a/x-pack/plugins/maps_legacy_licensing/tsconfig.json b/x-pack/plugins/maps_legacy_licensing/tsconfig.json new file mode 100644 index 0000000000000..90e8265515a16 --- /dev/null +++ b/x-pack/plugins/maps_legacy_licensing/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../../tsconfig.base.json", + "compilerOptions": { + "composite": true, + "outDir": "./target/types", + "emitDeclarationOnly": true, + "declaration": true, + "declarationMap": true + }, + "include": ["public/**/*"], + "references": [ + { "path": "../licensing/tsconfig.json" }, + ] +} diff --git a/x-pack/plugins/ml/kibana.json b/x-pack/plugins/ml/kibana.json index 8ec9b8ee976d4..1c47512e0b3de 100644 --- a/x-pack/plugins/ml/kibana.json +++ b/x-pack/plugins/ml/kibana.json @@ -24,7 +24,8 @@ "security", "spaces", "management", - "licenseManagement" + "licenseManagement", + "maps" ], "server": true, "ui": true, @@ -35,7 +36,8 @@ "dashboard", "savedObjects", "home", - "spaces" + "spaces", + "maps" ], "extraPublicDirs": [ "common" diff --git a/x-pack/plugins/ml/public/application/_index.scss b/x-pack/plugins/ml/public/application/_index.scss index 7512c180970ad..da1c226a665f6 100644 --- a/x-pack/plugins/ml/public/application/_index.scss +++ b/x-pack/plugins/ml/public/application/_index.scss @@ -30,6 +30,7 @@ @import 'components/navigation_menu/index'; @import 'components/rule_editor/index'; // SASSTODO: This file overwrites EUI directly @import 'components/stats_bar/index'; + @import 'components/ml_embedded_map/index'; // Hacks are last so they can overwrite anything above if needed @import 'hacks'; diff --git a/x-pack/plugins/ml/public/application/app.tsx b/x-pack/plugins/ml/public/application/app.tsx index d3a055f957c3a..68ee32e24391c 100644 --- a/x-pack/plugins/ml/public/application/app.tsx +++ b/x-pack/plugins/ml/public/application/app.tsx @@ -77,6 +77,8 @@ const App: FC = ({ coreStart, deps, appMountParams }) => { security: deps.security, licenseManagement: deps.licenseManagement, storage: localStorage, + embeddable: deps.embeddable, + maps: deps.maps, ...coreStart, }; @@ -118,6 +120,7 @@ export const renderApp = ( http: coreStart.http, security: deps.security, urlGenerators: deps.share.urlGenerators, + maps: deps.maps, }); appMountParams.onAppLeave((actions) => actions.default()); diff --git a/x-pack/plugins/ml/public/application/components/ml_embedded_map/_index.scss b/x-pack/plugins/ml/public/application/components/ml_embedded_map/_index.scss new file mode 100644 index 0000000000000..6d0d30dae670e --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/ml_embedded_map/_index.scss @@ -0,0 +1 @@ +@import 'ml_embedded_map'; diff --git a/x-pack/plugins/ml/public/application/components/ml_embedded_map/_ml_embedded_map.scss b/x-pack/plugins/ml/public/application/components/ml_embedded_map/_ml_embedded_map.scss new file mode 100644 index 0000000000000..495fc40ddb27c --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/ml_embedded_map/_ml_embedded_map.scss @@ -0,0 +1,8 @@ +.mlEmbeddedMapContent { + width: 100%; + height: 100%; + display: flex; + flex: 1 1 100%; + z-index: 1; + min-height: 0; // Absolute must for Firefox to scroll contents +} diff --git a/x-pack/plugins/ml/public/application/components/ml_embedded_map/index.ts b/x-pack/plugins/ml/public/application/components/ml_embedded_map/index.ts new file mode 100644 index 0000000000000..ce5dfb5171a6d --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/ml_embedded_map/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { MlEmbeddedMapComponent } from './ml_embedded_map'; diff --git a/x-pack/plugins/ml/public/application/components/ml_embedded_map/ml_embedded_map.tsx b/x-pack/plugins/ml/public/application/components/ml_embedded_map/ml_embedded_map.tsx new file mode 100644 index 0000000000000..d5fdc9d52a102 --- /dev/null +++ b/x-pack/plugins/ml/public/application/components/ml_embedded_map/ml_embedded_map.tsx @@ -0,0 +1,160 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useEffect, useRef, useState } from 'react'; + +import { htmlIdGenerator } from '@elastic/eui'; +import { LayerDescriptor } from '../../../../../maps/common/descriptor_types'; +import { + MapEmbeddable, + MapEmbeddableInput, + MapEmbeddableOutput, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../../maps/public/embeddable'; +import { MAP_SAVED_OBJECT_TYPE, RenderTooltipContentParams } from '../../../../../maps/public'; +import { + EmbeddableFactory, + ErrorEmbeddable, + isErrorEmbeddable, + ViewMode, +} from '../../../../../../../src/plugins/embeddable/public'; +import { useMlKibana } from '../../contexts/kibana'; + +export function MlEmbeddedMapComponent({ + layerList, + mapEmbeddableInput, + renderTooltipContent, +}: { + layerList: LayerDescriptor[]; + mapEmbeddableInput?: MapEmbeddableInput; + renderTooltipContent?: (params: RenderTooltipContentParams) => JSX.Element; +}) { + const [embeddable, setEmbeddable] = useState(); + + const embeddableRoot: React.RefObject = useRef(null); + const baseLayers = useRef(); + + const { + services: { embeddable: embeddablePlugin, maps: mapsPlugin }, + } = useMlKibana(); + + const factory: + | EmbeddableFactory + | undefined = embeddablePlugin + ? embeddablePlugin.getEmbeddableFactory(MAP_SAVED_OBJECT_TYPE) + : undefined; + + // Update the layer list with updated geo points upon refresh + useEffect(() => { + async function updateIndexPatternSearchLayer() { + if ( + embeddable && + !isErrorEmbeddable(embeddable) && + Array.isArray(layerList) && + Array.isArray(baseLayers.current) + ) { + embeddable.setLayerList([...baseLayers.current, ...layerList]); + } + } + updateIndexPatternSearchLayer(); + }, [embeddable, layerList]); + + useEffect(() => { + async function setupEmbeddable() { + if (!factory) { + // eslint-disable-next-line no-console + console.error('Map embeddable not found.'); + return; + } + const input: MapEmbeddableInput = { + id: htmlIdGenerator()(), + attributes: { title: '' }, + filters: [], + hidePanelTitles: true, + refreshConfig: { + value: 0, + pause: false, + }, + viewMode: ViewMode.VIEW, + isLayerTOCOpen: false, + hideFilterActions: true, + // Zoom Lat/Lon values are set to make sure map is in center in the panel + // It will also omit Greenland/Antarctica etc. NOTE: Can be removed when initialLocation is set + mapCenter: { + lon: 11, + lat: 20, + zoom: 1, + }, + // can use mapSettings to center map on anomalies + mapSettings: { + disableInteractive: false, + hideToolbarOverlay: false, + hideLayerControl: false, + hideViewControl: false, + // Doesn't currently work with GEO_JSON. Will uncomment when https://github.com/elastic/kibana/pull/88294 is in + // initialLocation: INITIAL_LOCATION.AUTO_FIT_TO_BOUNDS, // this will startup based on data-extent + autoFitToDataBounds: true, // this will auto-fit when there are changes to the filter and/or query + }, + }; + + const embeddableObject = await factory.create(input); + + if (embeddableObject && !isErrorEmbeddable(embeddableObject)) { + const basemapLayerDescriptor = mapsPlugin + ? await mapsPlugin.createLayerDescriptors.createBasemapLayerDescriptor() + : null; + + if (basemapLayerDescriptor) { + baseLayers.current = [basemapLayerDescriptor]; + await embeddableObject.setLayerList(baseLayers.current); + } + } + + setEmbeddable(embeddableObject); + } + + setupEmbeddable(); + // we want this effect to execute exactly once after the component mounts + }, []); + + useEffect(() => { + if (embeddable && !isErrorEmbeddable(embeddable) && mapEmbeddableInput !== undefined) { + embeddable.updateInput(mapEmbeddableInput); + } + }, [embeddable, mapEmbeddableInput]); + + useEffect(() => { + if (embeddable && !isErrorEmbeddable(embeddable) && renderTooltipContent !== undefined) { + embeddable.setRenderTooltipContent(renderTooltipContent); + } + }, [embeddable, renderTooltipContent]); + + // We can only render after embeddable has already initialized + useEffect(() => { + if (embeddableRoot.current && embeddable) { + embeddable.render(embeddableRoot.current); + } + }, [embeddable, embeddableRoot]); + + if (!embeddablePlugin) { + // eslint-disable-next-line no-console + console.error('Embeddable start plugin not found'); + return null; + } + if (!mapsPlugin) { + // eslint-disable-next-line no-console + console.error('Maps start plugin not found'); + return null; + } + + return ( +
+ ); +} diff --git a/x-pack/plugins/ml/public/application/contexts/kibana/kibana_context.ts b/x-pack/plugins/ml/public/application/contexts/kibana/kibana_context.ts index 4a7550788db56..a97fd513c9c99 100644 --- a/x-pack/plugins/ml/public/application/contexts/kibana/kibana_context.ts +++ b/x-pack/plugins/ml/public/application/contexts/kibana/kibana_context.ts @@ -15,12 +15,16 @@ import { LicenseManagementUIPluginSetup } from '../../../../../license_managemen import { SharePluginStart } from '../../../../../../../src/plugins/share/public'; import { MlServicesContext } from '../../app'; import { IStorageWrapper } from '../../../../../../../src/plugins/kibana_utils/public'; +import type { EmbeddableStart } from '../../../../../../../src/plugins/embeddable/public'; +import { MapsStartApi } from '../../../../../maps/public'; interface StartPlugins { data: DataPublicPluginStart; security?: SecurityPluginSetup; licenseManagement?: LicenseManagementUIPluginSetup; share: SharePluginStart; + embeddable: EmbeddableStart; + maps?: MapsStartApi; } export type StartServices = CoreStart & StartPlugins & { diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/file_based_expanded_row.tsx b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/file_based_expanded_row.tsx index 77f31ae9c2322..a8d8eb18776ec 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/file_based_expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/file_based_expanded_row.tsx @@ -8,13 +8,13 @@ import React from 'react'; import { BooleanContent, DateContent, - GeoPointContent, IpContent, KeywordContent, OtherContent, TextContent, NumberContent, } from '../../../stats_table/components/field_data_expanded_row'; +import { GeoPointContent } from './geo_point_content/geo_point_content'; import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types'; import type { FileBasedFieldVisConfig } from '../../../stats_table/types/field_vis_config'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/format_utils.ts b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/format_utils.ts new file mode 100644 index 0000000000000..8a442e345014d --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/format_utils.ts @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Feature, Point } from 'geojson'; +import { euiPaletteColorBlind } from '@elastic/eui'; +import { DEFAULT_GEO_REGEX } from './geo_point_content'; +import { SOURCE_TYPES } from '../../../../../../../../maps/common/constants'; + +export const convertWKTGeoToLonLat = ( + value: string | number +): { lat: number; lon: number } | undefined => { + if (typeof value === 'string') { + const trimmedValue = value.trim().replace('POINT (', '').replace(')', ''); + const regExpSerializer = DEFAULT_GEO_REGEX; + const parsed = regExpSerializer.exec(trimmedValue.trim()); + + if (parsed?.groups?.lat != null && parsed?.groups?.lon != null) { + return { + lat: parseFloat(parsed.groups.lat.trim()), + lon: parseFloat(parsed.groups.lon.trim()), + }; + } + } +}; + +export const DEFAULT_POINT_COLOR = euiPaletteColorBlind()[0]; +export const getGeoPointsLayer = ( + features: Array>, + pointColor: string = DEFAULT_POINT_COLOR +) => { + return { + id: 'geo_points', + label: 'Geo points', + sourceDescriptor: { + type: SOURCE_TYPES.GEOJSON_FILE, + __featureCollection: { + features, + type: 'FeatureCollection', + }, + }, + visible: true, + style: { + type: 'VECTOR', + properties: { + fillColor: { + type: 'STATIC', + options: { + color: pointColor, + }, + }, + lineColor: { + type: 'STATIC', + options: { + color: '#fff', + }, + }, + lineWidth: { + type: 'STATIC', + options: { + size: 2, + }, + }, + iconSize: { + type: 'STATIC', + options: { + size: 6, + }, + }, + }, + }, + type: 'VECTOR', + }; +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/geo_point_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/geo_point_content.tsx new file mode 100644 index 0000000000000..a498b786229bb --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/geo_point_content.tsx @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, useMemo } from 'react'; + +import { EuiFlexItem } from '@elastic/eui'; +import { Feature, Point } from 'geojson'; +import type { FieldDataRowProps } from '../../../../stats_table/types/field_data_row'; +import { DocumentStatsTable } from '../../../../stats_table/components/field_data_expanded_row/document_stats'; +import { MlEmbeddedMapComponent } from '../../../../../components/ml_embedded_map'; +import { convertWKTGeoToLonLat, getGeoPointsLayer } from './format_utils'; +import { ExpandedRowContent } from '../../../../stats_table/components/field_data_expanded_row/expanded_row_content'; +import { ExamplesList } from '../../../../index_based/components/field_data_row/examples_list'; + +export const DEFAULT_GEO_REGEX = RegExp('(?.+) (?.+)'); + +export const GeoPointContent: FC = ({ config }) => { + const formattedResults = useMemo(() => { + const { stats } = config; + + if (stats === undefined || stats.topValues === undefined) return null; + if (Array.isArray(stats.topValues)) { + const geoPointsFeatures: Array> = []; + + // reformatting the top values from POINT (-2.359207 51.37837) to (-2.359207, 51.37837) + const formattedExamples = []; + + for (let i = 0; i < stats.topValues.length; i++) { + const value = stats.topValues[i]; + const coordinates = convertWKTGeoToLonLat(value.key); + if (coordinates) { + const formattedGeoPoint = `(${coordinates.lat}, ${coordinates.lon})`; + formattedExamples.push(coordinates); + + geoPointsFeatures.push({ + type: 'Feature', + id: `ml-${config.fieldName}-${i}`, + geometry: { + type: 'Point', + coordinates: [coordinates.lat, coordinates.lon], + }, + properties: { + value: formattedGeoPoint, + count: value.doc_count, + }, + }); + } + } + + if (geoPointsFeatures.length > 0) { + return { + examples: formattedExamples, + layerList: [getGeoPointsLayer(geoPointsFeatures)], + }; + } + } + }, [config]); + return ( + + + {formattedResults && Array.isArray(formattedResults.examples) && ( + + + + )} + {formattedResults && Array.isArray(formattedResults.layerList) && ( + + + + )} + + ); +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/index.ts new file mode 100644 index 0000000000000..d3a50db45ec57 --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/expanded_row/geo_point_content/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { GeoPointContent } from './geo_point_content'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/_index.scss b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/_index.scss index 957ca0eec24d3..c1191ad270e4c 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/_index.scss +++ b/x-pack/plugins/ml/public/application/datavisualizer/file_based/components/file_datavisualizer_view/_index.scss @@ -1 +1 @@ -@import 'file_datavisualizer_view' +@import 'file_datavisualizer_view'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/expanded_row/expanded_row.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/expanded_row/expanded_row.tsx index 7018f73ff6c32..6d58efee36f36 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/expanded_row/expanded_row.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/expanded_row/expanded_row.tsx @@ -6,23 +6,31 @@ import React from 'react'; +import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types'; +import { LoadingIndicator } from '../field_data_row/loading_indicator'; +import { NotInDocsContent } from '../field_data_row/content_types'; import { FieldVisConfig } from '../../../stats_table/types'; import { BooleanContent, DateContent, - GeoPointContent, IpContent, KeywordContent, NumberContent, OtherContent, TextContent, } from '../../../stats_table/components/field_data_expanded_row'; +import { CombinedQuery, GeoPointContent } from './geo_point_content'; +import { IndexPattern } from '../../../../../../../../../src/plugins/data/common/index_patterns/index_patterns'; -import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types'; -import { LoadingIndicator } from '../field_data_row/loading_indicator'; -import { NotInDocsContent } from '../field_data_row/content_types'; - -export const IndexBasedDataVisualizerExpandedRow = ({ item }: { item: FieldVisConfig }) => { +export const IndexBasedDataVisualizerExpandedRow = ({ + item, + indexPattern, + combinedQuery, +}: { + item: FieldVisConfig; + indexPattern: IndexPattern | undefined; + combinedQuery: CombinedQuery; +}) => { const config = item; const { loading, type, existsInDocs, fieldName } = config; @@ -42,7 +50,13 @@ export const IndexBasedDataVisualizerExpandedRow = ({ item }: { item: FieldVisCo return ; case ML_JOB_FIELD_TYPES.GEO_POINT: - return ; + return ( + + ); case ML_JOB_FIELD_TYPES.IP: return ; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/expanded_row/geo_point_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/expanded_row/geo_point_content.tsx new file mode 100644 index 0000000000000..94db5d1ba686c --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/expanded_row/geo_point_content.tsx @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, useEffect, useState } from 'react'; + +import { EuiFlexItem } from '@elastic/eui'; +import { ExamplesList } from '../../../index_based/components/field_data_row/examples_list'; +import { FieldVisConfig } from '../../../stats_table/types'; +import { IndexPattern } from '../../../../../../../../../src/plugins/data/common/index_patterns/index_patterns'; +import { MlEmbeddedMapComponent } from '../../../../components/ml_embedded_map'; +import { ML_JOB_FIELD_TYPES } from '../../../../../../common/constants/field_types'; +import { ES_GEO_FIELD_TYPE } from '../../../../../../../maps/common/constants'; +import { LayerDescriptor } from '../../../../../../../maps/common/descriptor_types'; +import { useMlKibana } from '../../../../contexts/kibana'; +import { DocumentStatsTable } from '../../../stats_table/components/field_data_expanded_row/document_stats'; +import { ExpandedRowContent } from '../../../stats_table/components/field_data_expanded_row/expanded_row_content'; + +export interface CombinedQuery { + searchString: string | { [key: string]: any }; + searchQueryLanguage: string; +} +export const GeoPointContent: FC<{ + config: FieldVisConfig; + indexPattern: IndexPattern | undefined; + combinedQuery: CombinedQuery; +}> = ({ config, indexPattern, combinedQuery }) => { + const { stats } = config; + const [layerList, setLayerList] = useState([]); + const { + services: { maps: mapsPlugin }, + } = useMlKibana(); + + // Update the layer list with updated geo points upon refresh + useEffect(() => { + async function updateIndexPatternSearchLayer() { + if ( + indexPattern?.id !== undefined && + config !== undefined && + config.fieldName !== undefined && + config.type === ML_JOB_FIELD_TYPES.GEO_POINT + ) { + const params = { + indexPatternId: indexPattern.id, + geoFieldName: config.fieldName, + geoFieldType: config.type as ES_GEO_FIELD_TYPE.GEO_POINT, + query: { + query: combinedQuery.searchString, + language: combinedQuery.searchQueryLanguage, + }, + }; + const searchLayerDescriptor = mapsPlugin + ? await mapsPlugin.createLayerDescriptors.createESSearchSourceLayerDescriptor(params) + : null; + if (searchLayerDescriptor) { + setLayerList([...layerList, searchLayerDescriptor]); + } + } + } + updateIndexPatternSearchLayer(); + }, [indexPattern, config.fieldName, combinedQuery]); + + if (stats?.examples === undefined) return null; + return ( + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/examples_list/examples_list.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/examples_list/examples_list.tsx index 1e8f7586258d5..a59c6e0fe4183 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/examples_list/examples_list.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/examples_list/examples_list.tsx @@ -15,25 +15,29 @@ interface Props { } export const ExamplesList: FC = ({ examples }) => { - if ( - examples === undefined || - examples === null || - !Array.isArray(examples) || - examples.length === 0 - ) { + if (examples === undefined || examples === null || !Array.isArray(examples)) { return null; } - - const examplesContent = examples.map((example, i) => { - return ( - ); - }); + } else { + examplesContent = examples.map((example, i) => { + return ( + + ); + }); + } return (
diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/top_values/top_values.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/top_values/top_values.tsx index bd83aaa1f6149..8ad48a3e60d3a 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/top_values/top_values.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/components/field_data_row/top_values/top_values.tsx @@ -19,9 +19,11 @@ import { FormattedMessage } from '@kbn/i18n/react'; import classNames from 'classnames'; import { kibanaFieldFormat } from '../../../../../formatters/kibana_field_format'; import { roundToDecimalPlace } from '../../../../../formatters/round_to_decimal_place'; +import { ExpandedRowFieldHeader } from '../../../../stats_table/components/expanded_row_field_header'; +import { FieldVisStats } from '../../../../stats_table/types'; interface Props { - stats: any; + stats: FieldVisStats | undefined; fieldFormat?: any; barColor?: 'primary' | 'secondary' | 'danger' | 'subdued' | 'accent'; compressed?: boolean; @@ -37,6 +39,7 @@ function getPercentLabel(docCount: number, topValuesSampleSize: number): string } export const TopValues: FC = ({ stats, fieldFormat, barColor, compressed }) => { + if (stats === undefined) return null; const { topValues, topValuesSampleSize, @@ -46,51 +49,64 @@ export const TopValues: FC = ({ stats, fieldFormat, barColor, compressed } = stats; const progressBarMax = isTopValuesSampled === true ? topValuesSampleSize : count; return ( -
- {Array.isArray(topValues) && - topValues.map((value: any) => ( - - + + + + +
+ {Array.isArray(topValues) && + topValues.map((value) => ( + + + + + {kibanaFieldFormat(value.key, fieldFormat)} + + + + + + + {progressBarMax !== undefined && ( + + + {getPercentLabel(value.doc_count, progressBarMax)} + + )} - > - - - {kibanaFieldFormat(value.key, fieldFormat)} - - - - - - - - - {getPercentLabel(value.doc_count, progressBarMax)} - - - - ))} - {isTopValuesSampled === true && ( - - - - - - - )} -
+
+ ))} + {isTopValuesSampled === true && ( + + + + + + + )} +
+ ); }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx b/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx index e5b243d524034..8cf5e28ad3b5b 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/index_based/page.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, Fragment, useEffect, useMemo, useState } from 'react'; +import React, { FC, Fragment, useEffect, useMemo, useState, useCallback } from 'react'; import { merge } from 'rxjs'; import { EuiFlexGroup, @@ -112,19 +112,6 @@ export const getDefaultDataVisualizerListState = (): Required { - const item = items.find((fieldVisConfig) => fieldVisConfig.fieldName === fieldName); - if (item !== undefined) { - m[fieldName] = ; - } - return m; - }, {} as ItemIdToExpandedRowMap); -} - export const Page: FC = () => { const mlContext = useMlContext(); const restorableDefaults = getDefaultDataVisualizerListState(); @@ -678,6 +665,26 @@ export const Page: FC = () => { } return { visibleFieldsCount: _visibleFieldsCount, totalFieldsCount: _totalFieldsCount }; }, [overallStats, showEmptyFields]); + + const getItemIdToExpandedRowMap = useCallback( + function (itemIds: string[], items: FieldVisConfig[]): ItemIdToExpandedRowMap { + return itemIds.reduce((m: ItemIdToExpandedRowMap, fieldName: string) => { + const item = items.find((fieldVisConfig) => fieldVisConfig.fieldName === fieldName); + if (item !== undefined) { + m[fieldName] = ( + + ); + } + return m; + }, {} as ItemIdToExpandedRowMap); + }, + [currentIndexPattern, searchQuery] + ); + const { services: { docLinks }, } = useMlKibana(); diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/_field_data_row.scss b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/_field_data_row.scss index 2ab26f1564a1f..1832b0f78b895 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/_field_data_row.scss +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/_field_data_row.scss @@ -60,6 +60,21 @@ @include euiCodeFont; } + .mlFieldDataCard__geoContent { + z-index: auto; + flex: 1; + display: flex; + flex-direction: column; + height: 100%; + position: relative; + .embPanel__content { + display: flex; + flex: 1 1 100%; + z-index: 1; + min-height: 0; // Absolute must for Firefox to scroll contents + } + } + .mlFieldDataCard__stats { padding: $euiSizeS $euiSizeS 0 $euiSizeS; text-align: center; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/_index.scss b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/_index.scss index 6e7e66db9e03a..9e838c180713f 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/_index.scss +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/_index.scss @@ -1,5 +1,6 @@ -@import 'components/field_data_expanded_row/number_content'; +@import 'components/field_data_expanded_row/index'; @import 'components/field_count_stats/index'; +@import 'components/field_data_row/index'; .mlDataVisualizerFieldExpandedRow { padding-left: $euiSize * 4; @@ -37,6 +38,7 @@ } .mlDataVisualizerSummaryTable { max-width: 350px; + min-width: 250px; .euiTableRow > .euiTableRowCell { border-bottom: 0; } @@ -45,6 +47,10 @@ } } .mlDataVisualizerSummaryTableWrapper { - max-width: 350px; + max-width: 300px; + } + .mlDataVisualizerMapWrapper { + min-height: 300px; + min-width: 600px; } } diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/_index.scss b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/_index.scss index fdc591a140fea..799beec093cca 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/_index.scss +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/_index.scss @@ -1 +1,7 @@ @import 'number_content'; + +.mlDataVisualizerExpandedRow { + @include euiBreakpoint('xs', 's', 'm') { + flex-direction: column; + } +} diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/boolean_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/boolean_content.tsx index a75920dd09b34..70e98153fd55c 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/boolean_content.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/boolean_content.tsx @@ -5,7 +5,7 @@ */ import React, { FC, ReactNode, useMemo } from 'react'; -import { EuiBasicTable, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { EuiBasicTable, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { Axis, BarSeries, Chart, Settings } from '@elastic/charts'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -16,6 +16,7 @@ import { getTFPercentage } from '../../utils'; import { roundToDecimalPlace } from '../../../../formatters/round_to_decimal_place'; import { useDataVizChartTheme } from '../../hooks'; import { DocumentStatsTable } from './document_stats'; +import { ExpandedRowContent } from './expanded_row_content'; function getPercentLabel(value: number): string { if (value === 0) { @@ -85,7 +86,7 @@ export const BooleanContent: FC = ({ config }) => { ); return ( - + @@ -138,6 +139,6 @@ export const BooleanContent: FC = ({ config }) => { /> - + ); }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/date_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/date_content.tsx index 8d122df628381..a6e7901df4e8e 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/date_content.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/date_content.tsx @@ -5,7 +5,7 @@ */ import React, { FC, ReactNode } from 'react'; -import { EuiBasicTable, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { EuiBasicTable, EuiFlexItem } from '@elastic/eui'; // @ts-ignore import { formatDate } from '@elastic/eui/lib/services/format'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -14,6 +14,7 @@ import { i18n } from '@kbn/i18n'; import type { FieldDataRowProps } from '../../types/field_data_row'; import { ExpandedRowFieldHeader } from '../expanded_row_field_header'; import { DocumentStatsTable } from './document_stats'; +import { ExpandedRowContent } from './expanded_row_content'; const TIME_FORMAT = 'MMM D YYYY, HH:mm:ss.SSS'; interface SummaryTableItem { function: string; @@ -66,7 +67,7 @@ export const DateContent: FC = ({ config }) => { ]; return ( - + {summaryTableTitle} @@ -80,6 +81,6 @@ export const DateContent: FC = ({ config }) => { tableLayout="auto" /> - + ); }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/document_stats.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/document_stats.tsx index 177ac722166f7..fd23e9d51bf4e 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/document_stats.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/document_stats.tsx @@ -10,6 +10,7 @@ import { i18n } from '@kbn/i18n'; import { EuiBasicTable, EuiFlexItem } from '@elastic/eui'; import { ExpandedRowFieldHeader } from '../expanded_row_field_header'; import { FieldDataRowProps } from '../../types'; +import { roundToDecimalPlace } from '../../../../formatters/round_to_decimal_place'; const metaTableColumns = [ { @@ -59,7 +60,7 @@ export const DocumentStatsTable: FC = ({ config }) => { defaultMessage="percentage" /> ), - value: `${(count / sampleCount) * 100}%`, + value: `${roundToDecimalPlace((count / sampleCount) * 100)}%`, }, { function: 'distinctValues', diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/expanded_row_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/expanded_row_content.tsx new file mode 100644 index 0000000000000..6c14bd7a64a94 --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/expanded_row_content.tsx @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, ReactNode } from 'react'; +import { EuiFlexGroup } from '@elastic/eui'; + +interface Props { + children: ReactNode; + dataTestSubj: string; +} +export const ExpandedRowContent: FC = ({ children, dataTestSubj }) => { + return ( + + {children} + + ); +}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/geo_point_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/geo_point_content.tsx deleted file mode 100644 index 993c7a94f5e06..0000000000000 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/geo_point_content.tsx +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { FC } from 'react'; - -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import type { FieldDataRowProps } from '../../types/field_data_row'; -import { ExamplesList } from '../../../index_based/components/field_data_row/examples_list'; -import { DocumentStatsTable } from './document_stats'; -import { TopValues } from '../../../index_based/components/field_data_row/top_values'; - -export const GeoPointContent: FC = ({ config }) => { - const { stats } = config; - if (stats === undefined || (stats?.examples === undefined && stats?.topValues === undefined)) - return null; - - return ( - - - {Array.isArray(stats.examples) && ( - - - - )} - {Array.isArray(stats.topValues) && ( - - - - )} - - ); -}; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/index.ts b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/index.ts index c6cd50f6bc2e9..b9b1e181343b7 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/index.ts +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/index.ts @@ -6,7 +6,7 @@ export { BooleanContent } from './boolean_content'; export { DateContent } from './date_content'; -export { GeoPointContent } from './geo_point_content'; +export { GeoPointContent } from '../../../file_based/components/expanded_row/geo_point_content/geo_point_content'; export { KeywordContent } from './keyword_content'; export { IpContent } from './ip_content'; export { NumberContent } from './number_content'; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/ip_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/ip_content.tsx index 79492bb44a2dc..183268d6a7ef8 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/ip_content.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/ip_content.tsx @@ -5,14 +5,10 @@ */ import React, { FC } from 'react'; -import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; - -import { FormattedMessage } from '@kbn/i18n/react'; - import type { FieldDataRowProps } from '../../types/field_data_row'; import { TopValues } from '../../../index_based/components/field_data_row/top_values'; -import { ExpandedRowFieldHeader } from '../expanded_row_field_header'; import { DocumentStatsTable } from './document_stats'; +import { ExpandedRowContent } from './expanded_row_content'; export const IpContent: FC = ({ config }) => { const { stats } = config; @@ -22,17 +18,9 @@ export const IpContent: FC = ({ config }) => { const fieldFormat = 'fieldFormat' in config ? config.fieldFormat : undefined; return ( - + - - - - - - - + + ); }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/keyword_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/keyword_content.tsx index 634f5b55513a3..d11ecc7d8804b 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/keyword_content.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/keyword_content.tsx @@ -5,31 +5,20 @@ */ import React, { FC } from 'react'; -import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; import type { FieldDataRowProps } from '../../types/field_data_row'; import { TopValues } from '../../../index_based/components/field_data_row/top_values'; -import { ExpandedRowFieldHeader } from '../expanded_row_field_header'; import { DocumentStatsTable } from './document_stats'; +import { ExpandedRowContent } from './expanded_row_content'; export const KeywordContent: FC = ({ config }) => { const { stats } = config; const fieldFormat = 'fieldFormat' in config ? config.fieldFormat : undefined; return ( - + - - - - - - - - + + ); }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/number_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/number_content.tsx index d05de26d3c5d4..56811e61ad891 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/number_content.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/number_content.tsx @@ -5,7 +5,7 @@ */ import React, { FC, ReactNode, useEffect, useState } from 'react'; -import { EuiBasicTable, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; +import { EuiBasicTable, EuiFlexItem, EuiText } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; @@ -20,6 +20,7 @@ import { import { TopValues } from '../../../index_based/components/field_data_row/top_values'; import { ExpandedRowFieldHeader } from '../expanded_row_field_header'; import { DocumentStatsTable } from './document_stats'; +import { ExpandedRowContent } from './expanded_row_content'; const METRIC_DISTRIBUTION_CHART_WIDTH = 325; const METRIC_DISTRIBUTION_CHART_HEIGHT = 200; @@ -97,7 +98,7 @@ export const NumberContent: FC = ({ config }) => { } ); return ( - + {summaryTableTitle} @@ -112,24 +113,7 @@ export const NumberContent: FC = ({ config }) => { {stats && ( - - - - - - - - - - + )} {distribution && ( @@ -164,6 +148,6 @@ export const NumberContent: FC = ({ config }) => { )} - + ); }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/other_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/other_content.tsx index a6d7398990cd3..08884619db2d6 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/other_content.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/other_content.tsx @@ -5,18 +5,18 @@ */ import React, { FC } from 'react'; -import { EuiFlexGroup } from '@elastic/eui'; import type { FieldDataRowProps } from '../../types/field_data_row'; import { ExamplesList } from '../../../index_based/components/field_data_row/examples_list'; import { DocumentStatsTable } from './document_stats'; +import { ExpandedRowContent } from './expanded_row_content'; export const OtherContent: FC = ({ config }) => { const { stats } = config; if (stats === undefined) return null; return ( - + {Array.isArray(stats.examples) && } - + ); }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/text_content.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/text_content.tsx index 55639ecc5761f..c0a5ca18d03b5 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/text_content.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_expanded_row/text_content.tsx @@ -5,13 +5,14 @@ */ import React, { FC, Fragment } from 'react'; -import { EuiCallOut, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import { EuiCallOut, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import type { FieldDataRowProps } from '../../types/field_data_row'; import { ExamplesList } from '../../../index_based/components/field_data_row/examples_list'; +import { ExpandedRowContent } from './expanded_row_content'; export const TextContent: FC = ({ config }) => { const { stats } = config; @@ -23,7 +24,7 @@ export const TextContent: FC = ({ config }) => { const numExamples = examples.length; return ( - + {numExamples > 0 && } {numExamples === 0 && ( @@ -59,6 +60,6 @@ export const TextContent: FC = ({ config }) => { )} - + ); }; diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/_index.scss b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/_index.scss new file mode 100644 index 0000000000000..27483feb573b8 --- /dev/null +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/_index.scss @@ -0,0 +1,3 @@ +.mlDataVisualizerColumnHeaderIcon { + max-width: $euiSizeM; +} diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/distinct_values.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/distinct_values.tsx index 819d5278c0d78..44c028d1ba8b5 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/distinct_values.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/distinct_values.tsx @@ -12,7 +12,7 @@ export const DistinctValues = ({ cardinality }: { cardinality?: number }) => { if (cardinality === undefined) return null; return ( - + diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/document_stats.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/document_stats.tsx index 9c89d74fa751b..b223fd5d98c1a 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/document_stats.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/document_stats.tsx @@ -21,7 +21,7 @@ export const DocumentStat = ({ config }: FieldDataRowProps) => { return ( - + diff --git a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/number_content_preview.tsx b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/number_content_preview.tsx index 3a84ae644cb4e..996fffd225f96 100644 --- a/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/number_content_preview.tsx +++ b/x-pack/plugins/ml/public/application/datavisualizer/stats_table/components/field_data_row/number_content_preview.tsx @@ -14,6 +14,7 @@ import { } from '../metric_distribution_chart'; import { formatSingleValue } from '../../../../formatters/format_value'; import { FieldVisConfig } from '../../types'; +import { kibanaFieldFormat } from '../../../../formatters/kibana_field_format'; const METRIC_DISTRIBUTION_CHART_WIDTH = 150; const METRIC_DISTRIBUTION_CHART_HEIGHT = 80; @@ -59,14 +60,16 @@ export const IndexBasedNumberContentPreview: FC = ({ <> - {legendText.min} + + {kibanaFieldFormat(legendText.min, fieldFormat)} + - {legendText.max} + {kibanaFieldFormat(legendText.max, fieldFormat)} diff --git a/x-pack/plugins/ml/public/application/util/dependency_cache.ts b/x-pack/plugins/ml/public/application/util/dependency_cache.ts index d7ca0203ab69e..9aa16f521554c 100644 --- a/x-pack/plugins/ml/public/application/util/dependency_cache.ts +++ b/x-pack/plugins/ml/public/application/util/dependency_cache.ts @@ -21,6 +21,7 @@ import type { import type { IndexPatternsContract, DataPublicPluginStart } from 'src/plugins/data/public'; import type { SharePluginStart } from 'src/plugins/share/public'; import type { SecurityPluginSetup } from '../../../../security/public'; +import type { MapsStartApi } from '../../../../maps/public'; export interface DependencyCache { timefilter: DataPublicPluginSetup['query']['timefilter'] | null; @@ -40,6 +41,7 @@ export interface DependencyCache { security: SecurityPluginSetup | undefined | null; i18n: I18nStart | null; urlGenerators: SharePluginStart['urlGenerators'] | null; + maps: MapsStartApi | null; } const cache: DependencyCache = { @@ -60,6 +62,7 @@ const cache: DependencyCache = { security: null, i18n: null, urlGenerators: null, + maps: null, }; export function setDependencyCache(deps: Partial) { diff --git a/x-pack/plugins/ml/public/plugin.ts b/x-pack/plugins/ml/public/plugin.ts index 7c32671be93c4..3ba79e0eb9187 100644 --- a/x-pack/plugins/ml/public/plugin.ts +++ b/x-pack/plugins/ml/public/plugin.ts @@ -25,7 +25,7 @@ import type { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import type { DataPublicPluginStart } from 'src/plugins/data/public'; import type { HomePublicPluginSetup } from 'src/plugins/home/public'; import type { IndexPatternManagementSetup } from 'src/plugins/index_pattern_management/public'; -import type { EmbeddableSetup } from 'src/plugins/embeddable/public'; +import type { EmbeddableSetup, EmbeddableStart } from 'src/plugins/embeddable/public'; import type { SpacesPluginStart } from '../../spaces/public'; import { AppStatus, AppUpdater, DEFAULT_APP_CATEGORIES } from '../../../../src/core/public'; @@ -45,6 +45,7 @@ import { setDependencyCache } from './application/util/dependency_cache'; import { registerFeature } from './register_feature'; // Not importing from `ml_url_generator/index` here to avoid importing unnecessary code import { registerUrlGenerator } from './ml_url_generator/ml_url_generator'; +import type { MapsStartApi } from '../../maps/public'; export interface MlStartDependencies { data: DataPublicPluginStart; @@ -52,6 +53,8 @@ export interface MlStartDependencies { kibanaLegacy: KibanaLegacyStart; uiActions: UiActionsStart; spaces?: SpacesPluginStart; + embeddable: EmbeddableStart; + maps?: MapsStartApi; } export interface MlSetupDependencies { security?: SecurityPluginSetup; @@ -102,7 +105,8 @@ export class MlPlugin implements Plugin { usageCollection: pluginsSetup.usageCollection, licenseManagement: pluginsSetup.licenseManagement, home: pluginsSetup.home, - embeddable: pluginsSetup.embeddable, + embeddable: { ...pluginsSetup.embeddable, ...pluginsStart.embeddable }, + maps: pluginsStart.maps, uiActions: pluginsStart.uiActions, kibanaVersion, }, diff --git a/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts b/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts index 7b3f97b684edc..22e75ec694733 100644 --- a/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts +++ b/x-pack/plugins/ml/server/models/data_visualizer/data_visualizer.ts @@ -1189,7 +1189,8 @@ export class DataVisualizer { }); const searchBody = { - _source: field, + fields: [field], + _source: false, query: { bool: { filter: filterCriteria, @@ -1209,16 +1210,16 @@ export class DataVisualizer { if (body.hits.total.value > 0) { const hits = body.hits.hits; for (let i = 0; i < hits.length; i++) { - // Look in the _source for the field value. - // If the field is not in the _source (as will happen if the - // field is populated using copy_to in the index mapping), - // there will be no example to add. // Use lodash get() to support field names containing dots. - const example: any = get(hits[i]._source, field); - if (example !== undefined && stats.examples.indexOf(example) === -1) { - stats.examples.push(example); - if (stats.examples.length === maxExamples) { - break; + const doc: object[] | undefined = get(hits[i].fields, field); + // the results from fields query is always an array + if (Array.isArray(doc) && doc.length > 0) { + const example = doc[0]; + if (example !== undefined && stats.examples.indexOf(example) === -1) { + stats.examples.push(example); + if (stats.examples.length === maxExamples) { + break; + } } } } diff --git a/x-pack/plugins/observability/server/index.ts b/x-pack/plugins/observability/server/index.ts index 78550b781b411..e88541f69d6cf 100644 --- a/x-pack/plugins/observability/server/index.ts +++ b/x-pack/plugins/observability/server/index.ts @@ -9,6 +9,7 @@ import { PluginInitializerContext } from 'src/core/server'; import { ObservabilityPlugin, ObservabilityPluginSetup } from './plugin'; import { createOrUpdateIndex, MappingsDefinition } from './utils/create_or_update_index'; import { ScopedAnnotationsClient } from './lib/annotations/bootstrap_annotations'; +import { unwrapEsResponse } from './utils/unwrap_es_response'; export const config = { schema: schema.object({ @@ -30,4 +31,5 @@ export { MappingsDefinition, ObservabilityPluginSetup, ScopedAnnotationsClient, + unwrapEsResponse, }; diff --git a/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts b/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts index 6fcd780d5af29..90084611d7efc 100644 --- a/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts +++ b/x-pack/plugins/observability/server/lib/annotations/bootstrap_annotations.ts @@ -38,7 +38,7 @@ export async function bootstrapAnnotations({ index, core, context }: Params) { ) => { return createAnnotationsClient({ index, - apiCaller: requestContext.core.elasticsearch.legacy.client.callAsCurrentUser, + esClient: requestContext.core.elasticsearch.client.asCurrentUser, logger, license: requestContext.licensing?.license, }); diff --git a/x-pack/plugins/observability/server/lib/annotations/create_annotations_client.ts b/x-pack/plugins/observability/server/lib/annotations/create_annotations_client.ts index 41f45683d244c..76890cbd587e9 100644 --- a/x-pack/plugins/observability/server/lib/annotations/create_annotations_client.ts +++ b/x-pack/plugins/observability/server/lib/annotations/create_annotations_client.ts @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LegacyAPICaller, Logger } from 'kibana/server'; +import { ElasticsearchClient, Logger } from 'kibana/server'; import * as t from 'io-ts'; -import { Client } from 'elasticsearch'; import Boom from '@hapi/boom'; import { ILicense } from '../../../../licensing/server'; import { @@ -15,9 +14,9 @@ import { Annotation, getAnnotationByIdRt, } from '../../../common/annotations'; -import { PromiseReturnType } from '../../../typings/common'; import { createOrUpdateIndex } from '../../utils/create_or_update_index'; import { mappings } from './mappings'; +import { unwrapEsResponse } from '../../utils/unwrap_es_response'; type CreateParams = t.TypeOf; type DeleteParams = t.TypeOf; @@ -38,19 +37,25 @@ interface IndexDocumentResponse { result: string; } +interface GetResponse { + _id: string; + _index: string; + _source: Annotation; +} + export function createAnnotationsClient(params: { index: string; - apiCaller: LegacyAPICaller; + esClient: ElasticsearchClient; logger: Logger; license?: ILicense; }) { - const { index, apiCaller, logger, license } = params; + const { index, esClient, logger, license } = params; const initIndex = () => createOrUpdateIndex({ index, mappings, - apiCaller, + client: esClient, logger, }); @@ -71,9 +76,11 @@ export function createAnnotationsClient(params: { async ( createParams: CreateParams ): Promise<{ _id: string; _index: string; _source: Annotation }> => { - const indexExists = await apiCaller('indices.exists', { - index, - }); + const indexExists = await unwrapEsResponse( + esClient.indices.exists({ + index, + }) + ); if (!indexExists) { await initIndex(); @@ -86,35 +93,42 @@ export function createAnnotationsClient(params: { }, }; - const response = (await apiCaller('index', { - index, - body: annotation, - refresh: 'wait_for', - })) as IndexDocumentResponse; + const body = await unwrapEsResponse( + esClient.index({ + index, + body: annotation, + refresh: 'wait_for', + }) + ); - return apiCaller('get', { - index, - id: response._id, - }); + return ( + await esClient.get({ + index, + id: body._id, + }) + ).body; } ), getById: ensureGoldLicense(async (getByIdParams: GetByIdParams) => { const { id } = getByIdParams; - return apiCaller('get', { - id, - index, - }); + return unwrapEsResponse( + esClient.get({ + id, + index, + }) + ); }), delete: ensureGoldLicense(async (deleteParams: DeleteParams) => { const { id } = deleteParams; - const response = (await apiCaller('delete', { - index, - id, - refresh: 'wait_for', - })) as PromiseReturnType; - return response; + return unwrapEsResponse( + esClient.delete({ + index, + id, + refresh: 'wait_for', + }) + ); }), }; } diff --git a/x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts b/x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts index 8f0b53b5a3df2..6ae80880d22b5 100644 --- a/x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts +++ b/x-pack/plugins/observability/server/lib/annotations/register_annotation_apis.ts @@ -55,11 +55,11 @@ export function registerAnnotationAPIs({ }); } - const apiCaller = context.core.elasticsearch.legacy.client.callAsCurrentUser; + const esClient = context.core.elasticsearch.client.asCurrentUser; const client = createAnnotationsClient({ index, - apiCaller, + esClient, logger, license: context.licensing?.license, }); diff --git a/x-pack/plugins/observability/server/utils/create_or_update_index.ts b/x-pack/plugins/observability/server/utils/create_or_update_index.ts index 1898331451262..248488b4a5ebe 100644 --- a/x-pack/plugins/observability/server/utils/create_or_update_index.ts +++ b/x-pack/plugins/observability/server/utils/create_or_update_index.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import pRetry from 'p-retry'; -import { Logger, LegacyAPICaller } from 'src/core/server'; +import { Logger, ElasticsearchClient } from 'src/core/server'; export interface MappingsObject { type: string; @@ -24,12 +24,12 @@ export interface MappingsDefinition { export async function createOrUpdateIndex({ index, mappings, - apiCaller, + client, logger, }: { index: string; mappings: MappingsDefinition; - apiCaller: LegacyAPICaller; + client: ElasticsearchClient; logger: Logger; }) { try { @@ -43,21 +43,21 @@ export async function createOrUpdateIndex({ */ await pRetry( async () => { - const indexExists = await apiCaller('indices.exists', { index }); + const indexExists = (await client.indices.exists({ index })).body; const result = indexExists ? await updateExistingIndex({ index, - apiCaller, + client, mappings, }) : await createNewIndex({ index, - apiCaller, + client, mappings, }); - if (!result.acknowledged) { - const resultError = result && result.error && JSON.stringify(result.error); + if (!result.body.acknowledged) { + const resultError = result && result.body.error && JSON.stringify(result.body.error); throw new Error(resultError); } }, @@ -75,14 +75,14 @@ export async function createOrUpdateIndex({ function createNewIndex({ index, - apiCaller, + client, mappings, }: { index: string; - apiCaller: LegacyAPICaller; + client: ElasticsearchClient; mappings: MappingsDefinition; }) { - return apiCaller('indices.create', { + return client.indices.create<{ acknowledged: boolean; error: any }>({ index, body: { // auto_expand_replicas: Allows cluster to not have replicas for this index @@ -94,14 +94,14 @@ function createNewIndex({ function updateExistingIndex({ index, - apiCaller, + client, mappings, }: { index: string; - apiCaller: LegacyAPICaller; + client: ElasticsearchClient; mappings: MappingsDefinition; }) { - return apiCaller('indices.putMapping', { + return client.indices.putMapping<{ acknowledged: boolean; error: any }>({ index, body: mappings, }); diff --git a/x-pack/plugins/observability/server/utils/unwrap_es_response.ts b/x-pack/plugins/observability/server/utils/unwrap_es_response.ts new file mode 100644 index 0000000000000..418ceeb64cc87 --- /dev/null +++ b/x-pack/plugins/observability/server/utils/unwrap_es_response.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 { PromiseValueType } from '../../../apm/typings/common'; + +export function unwrapEsResponse>( + responsePromise: T +): Promise['body']> { + return responsePromise.then((res) => res.body); +} diff --git a/x-pack/plugins/osquery/README.md b/x-pack/plugins/osquery/README.md new file mode 100755 index 0000000000000..e0861fab2040b --- /dev/null +++ b/x-pack/plugins/osquery/README.md @@ -0,0 +1,9 @@ +# osquery + +This plugin adds extended support to Security Solution Fleet Osquery integration + +--- + +## Development + +See the [kibana contributing guide](https://github.com/elastic/kibana/blob/master/CONTRIBUTING.md) for instructions setting up your development environment. diff --git a/x-pack/plugins/osquery/common/constants.ts b/x-pack/plugins/osquery/common/constants.ts new file mode 100644 index 0000000000000..f6027d416beb1 --- /dev/null +++ b/x-pack/plugins/osquery/common/constants.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const DEFAULT_MAX_TABLE_QUERY_SIZE = 10000; +export const DEFAULT_DARK_MODE = 'theme:darkMode'; diff --git a/x-pack/plugins/osquery/common/ecs/agent/index.ts b/x-pack/plugins/osquery/common/ecs/agent/index.ts new file mode 100644 index 0000000000000..6f29a2020c944 --- /dev/null +++ b/x-pack/plugins/osquery/common/ecs/agent/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface AgentEcs { + type?: string[]; +} diff --git a/x-pack/plugins/osquery/common/ecs/auditd/index.ts b/x-pack/plugins/osquery/common/ecs/auditd/index.ts new file mode 100644 index 0000000000000..7611e5424e297 --- /dev/null +++ b/x-pack/plugins/osquery/common/ecs/auditd/index.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface AuditdEcs { + result?: string[]; + session?: string[]; + data?: AuditdDataEcs; + summary?: SummaryEcs; + sequence?: string[]; +} + +export interface AuditdDataEcs { + acct?: string[]; + terminal?: string[]; + op?: string[]; +} + +export interface SummaryEcs { + actor?: PrimarySecondaryEcs; + object?: PrimarySecondaryEcs; + how?: string[]; + message_type?: string[]; + sequence?: string[]; +} + +export interface PrimarySecondaryEcs { + primary?: string[]; + secondary?: string[]; + type?: string[]; +} diff --git a/x-pack/plugins/osquery/common/ecs/cloud/index.ts b/x-pack/plugins/osquery/common/ecs/cloud/index.ts new file mode 100644 index 0000000000000..812b30bcc13f1 --- /dev/null +++ b/x-pack/plugins/osquery/common/ecs/cloud/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface CloudEcs { + instance?: CloudInstanceEcs; + machine?: CloudMachineEcs; + provider?: string[]; + region?: string[]; +} + +export interface CloudMachineEcs { + type?: string[]; +} + +export interface CloudInstanceEcs { + id?: string[]; +} diff --git a/x-pack/plugins/vis_type_timeseries_enhanced/server/index.ts b/x-pack/plugins/osquery/common/ecs/destination/index.ts similarity index 50% rename from x-pack/plugins/vis_type_timeseries_enhanced/server/index.ts rename to x-pack/plugins/osquery/common/ecs/destination/index.ts index d2665ec1e2813..be12e829108a9 100644 --- a/x-pack/plugins/vis_type_timeseries_enhanced/server/index.ts +++ b/x-pack/plugins/osquery/common/ecs/destination/index.ts @@ -4,8 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializerContext } from 'src/core/server'; -import { VisTypeTimeseriesEnhanced } from './plugin'; +import { GeoEcs } from '../geo'; -export const plugin = (initializerContext: PluginInitializerContext) => - new VisTypeTimeseriesEnhanced(initializerContext); +export interface DestinationEcs { + bytes?: number[]; + ip?: string[]; + port?: number[]; + domain?: string[]; + geo?: GeoEcs; + packets?: number[]; +} diff --git a/x-pack/plugins/osquery/common/ecs/dns/index.ts b/x-pack/plugins/osquery/common/ecs/dns/index.ts new file mode 100644 index 0000000000000..45192d03a10b6 --- /dev/null +++ b/x-pack/plugins/osquery/common/ecs/dns/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface DnsEcs { + question?: DnsQuestionEcs; + resolved_ip?: string[]; + response_code?: string[]; +} + +export interface DnsQuestionEcs { + name?: string[]; + type?: string[]; +} diff --git a/x-pack/plugins/osquery/common/ecs/ecs_fields/extend_map.test.ts b/x-pack/plugins/osquery/common/ecs/ecs_fields/extend_map.test.ts new file mode 100644 index 0000000000000..9ba22e83b4b4d --- /dev/null +++ b/x-pack/plugins/osquery/common/ecs/ecs_fields/extend_map.test.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { extendMap } from './extend_map'; + +describe('ecs_fields test', () => { + describe('extendMap', () => { + test('it should extend a record', () => { + const osFieldsMap: Readonly> = { + 'os.platform': 'os.platform', + 'os.full': 'os.full', + 'os.family': 'os.family', + 'os.version': 'os.version', + 'os.kernel': 'os.kernel', + }; + const expected: Record = { + 'host.os.family': 'host.os.family', + 'host.os.full': 'host.os.full', + 'host.os.kernel': 'host.os.kernel', + 'host.os.platform': 'host.os.platform', + 'host.os.version': 'host.os.version', + }; + expect(extendMap('host', osFieldsMap)).toEqual(expected); + }); + + test('it should extend a sample hosts record', () => { + const hostMap: Record = { + 'host.id': 'host.id', + 'host.ip': 'host.ip', + 'host.name': 'host.name', + }; + const osFieldsMap: Readonly> = { + 'os.platform': 'os.platform', + 'os.full': 'os.full', + 'os.family': 'os.family', + 'os.version': 'os.version', + 'os.kernel': 'os.kernel', + }; + const expected: Record = { + 'host.id': 'host.id', + 'host.ip': 'host.ip', + 'host.name': 'host.name', + 'host.os.family': 'host.os.family', + 'host.os.full': 'host.os.full', + 'host.os.kernel': 'host.os.kernel', + 'host.os.platform': 'host.os.platform', + 'host.os.version': 'host.os.version', + }; + const output = { ...hostMap, ...extendMap('host', osFieldsMap) }; + expect(output).toEqual(expected); + }); + }); +}); diff --git a/x-pack/plugins/osquery/common/ecs/ecs_fields/extend_map.ts b/x-pack/plugins/osquery/common/ecs/ecs_fields/extend_map.ts new file mode 100644 index 0000000000000..c25979cbcdcee --- /dev/null +++ b/x-pack/plugins/osquery/common/ecs/ecs_fields/extend_map.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const extendMap = ( + path: string, + map: Readonly> +): Readonly> => + Object.entries(map).reduce>((accum, [key, value]) => { + accum[`${path}.${key}`] = `${path}.${value}`; + return accum; + }, {}); diff --git a/x-pack/plugins/osquery/common/ecs/ecs_fields/index.ts b/x-pack/plugins/osquery/common/ecs/ecs_fields/index.ts new file mode 100644 index 0000000000000..19b16bd4bc6d2 --- /dev/null +++ b/x-pack/plugins/osquery/common/ecs/ecs_fields/index.ts @@ -0,0 +1,358 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { extendMap } from './extend_map'; + +export const auditdMap: Readonly> = { + 'auditd.result': 'auditd.result', + 'auditd.session': 'auditd.session', + 'auditd.data.acct': 'auditd.data.acct', + 'auditd.data.terminal': 'auditd.data.terminal', + 'auditd.data.op': 'auditd.data.op', + 'auditd.summary.actor.primary': 'auditd.summary.actor.primary', + 'auditd.summary.actor.secondary': 'auditd.summary.actor.secondary', + 'auditd.summary.object.primary': 'auditd.summary.object.primary', + 'auditd.summary.object.secondary': 'auditd.summary.object.secondary', + 'auditd.summary.object.type': 'auditd.summary.object.type', + 'auditd.summary.how': 'auditd.summary.how', + 'auditd.summary.message_type': 'auditd.summary.message_type', + 'auditd.summary.sequence': 'auditd.summary.sequence', +}; + +export const cloudFieldsMap: Readonly> = { + 'cloud.account.id': 'cloud.account.id', + 'cloud.availability_zone': 'cloud.availability_zone', + 'cloud.instance.id': 'cloud.instance.id', + 'cloud.instance.name': 'cloud.instance.name', + 'cloud.machine.type': 'cloud.machine.type', + 'cloud.provider': 'cloud.provider', + 'cloud.region': 'cloud.region', +}; + +export const fileMap: Readonly> = { + 'file.name': 'file.name', + 'file.path': 'file.path', + 'file.target_path': 'file.target_path', + 'file.extension': 'file.extension', + 'file.type': 'file.type', + 'file.device': 'file.device', + 'file.inode': 'file.inode', + 'file.uid': 'file.uid', + 'file.owner': 'file.owner', + 'file.gid': 'file.gid', + 'file.group': 'file.group', + 'file.mode': 'file.mode', + 'file.size': 'file.size', + 'file.mtime': 'file.mtime', + 'file.ctime': 'file.ctime', +}; + +export const osFieldsMap: Readonly> = { + 'os.platform': 'os.platform', + 'os.name': 'os.name', + 'os.full': 'os.full', + 'os.family': 'os.family', + 'os.version': 'os.version', + 'os.kernel': 'os.kernel', +}; + +export const hostFieldsMap: Readonly> = { + 'host.architecture': 'host.architecture', + 'host.id': 'host.id', + 'host.ip': 'host.ip', + 'host.mac': 'host.mac', + 'host.name': 'host.name', + ...extendMap('host', osFieldsMap), +}; + +export const processFieldsMap: Readonly> = { + 'process.hash.md5': 'process.hash.md5', + 'process.hash.sha1': 'process.hash.sha1', + 'process.hash.sha256': 'process.hash.sha256', + 'process.pid': 'process.pid', + 'process.name': 'process.name', + 'process.ppid': 'process.ppid', + 'process.args': 'process.args', + 'process.entity_id': 'process.entity_id', + 'process.executable': 'process.executable', + 'process.title': 'process.title', + 'process.thread': 'process.thread', + 'process.working_directory': 'process.working_directory', +}; + +export const agentFieldsMap: Readonly> = { + 'agent.type': 'agent.type', +}; + +export const userFieldsMap: Readonly> = { + 'user.domain': 'user.domain', + 'user.id': 'user.id', + 'user.name': 'user.name', + // NOTE: This field is not tested and available from ECS. Please remove this tag once it is + 'user.full_name': 'user.full_name', + // NOTE: This field is not tested and available from ECS. Please remove this tag once it is + 'user.email': 'user.email', + // NOTE: This field is not tested and available from ECS. Please remove this tag once it is + 'user.hash': 'user.hash', + // NOTE: This field is not tested and available from ECS. Please remove this tag once it is + 'user.group': 'user.group', +}; + +export const winlogFieldsMap: Readonly> = { + 'winlog.event_id': 'winlog.event_id', +}; + +export const suricataFieldsMap: Readonly> = { + 'suricata.eve.flow_id': 'suricata.eve.flow_id', + 'suricata.eve.proto': 'suricata.eve.proto', + 'suricata.eve.alert.signature': 'suricata.eve.alert.signature', + 'suricata.eve.alert.signature_id': 'suricata.eve.alert.signature_id', +}; + +export const tlsFieldsMap: Readonly> = { + 'tls.client_certificate.fingerprint.sha1': 'tls.client_certificate.fingerprint.sha1', + 'tls.fingerprints.ja3.hash': 'tls.fingerprints.ja3.hash', + 'tls.server_certificate.fingerprint.sha1': 'tls.server_certificate.fingerprint.sha1', +}; + +export const urlFieldsMap: Readonly> = { + 'url.original': 'url.original', + 'url.domain': 'url.domain', + 'user.username': 'user.username', + 'user.password': 'user.password', +}; + +export const httpFieldsMap: Readonly> = { + 'http.version': 'http.version', + 'http.request': 'http.request', + 'http.request.method': 'http.request.method', + 'http.request.body.bytes': 'http.request.body.bytes', + 'http.request.body.content': 'http.request.body.content', + 'http.request.referrer': 'http.request.referrer', + 'http.response.status_code': 'http.response.status_code', + 'http.response.body': 'http.response.body', + 'http.response.body.bytes': 'http.response.body.bytes', + 'http.response.body.content': 'http.response.body.content', +}; + +export const zeekFieldsMap: Readonly> = { + 'zeek.session_id': 'zeek.session_id', + 'zeek.connection.local_resp': 'zeek.connection.local_resp', + 'zeek.connection.local_orig': 'zeek.connection.local_orig', + 'zeek.connection.missed_bytes': 'zeek.connection.missed_bytes', + 'zeek.connection.state': 'zeek.connection.state', + 'zeek.connection.history': 'zeek.connection.history', + 'zeek.notice.suppress_for': 'zeek.notice.suppress_for', + 'zeek.notice.msg': 'zeek.notice.msg', + 'zeek.notice.note': 'zeek.notice.note', + 'zeek.notice.sub': 'zeek.notice.sub', + 'zeek.notice.dst': 'zeek.notice.dst', + 'zeek.notice.dropped': 'zeek.notice.dropped', + 'zeek.notice.peer_descr': 'zeek.notice.peer_descr', + 'zeek.dns.AA': 'zeek.dns.AA', + 'zeek.dns.qclass_name': 'zeek.dns.qclass_name', + 'zeek.dns.RD': 'zeek.dns.RD', + 'zeek.dns.qtype_name': 'zeek.dns.qtype_name', + 'zeek.dns.qtype': 'zeek.dns.qtype', + 'zeek.dns.query': 'zeek.dns.query', + 'zeek.dns.trans_id': 'zeek.dns.trans_id', + 'zeek.dns.qclass': 'zeek.dns.qclass', + 'zeek.dns.RA': 'zeek.dns.RA', + 'zeek.dns.TC': 'zeek.dns.TC', + 'zeek.http.resp_mime_types': 'zeek.http.resp_mime_types', + 'zeek.http.trans_depth': 'zeek.http.trans_depth', + 'zeek.http.status_msg': 'zeek.http.status_msg', + 'zeek.http.resp_fuids': 'zeek.http.resp_fuids', + 'zeek.http.tags': 'zeek.http.tags', + 'zeek.files.session_ids': 'zeek.files.session_ids', + 'zeek.files.timedout': 'zeek.files.timedout', + 'zeek.files.local_orig': 'zeek.files.local_orig', + 'zeek.files.tx_host': 'zeek.files.tx_host', + 'zeek.files.source': 'zeek.files.source', + 'zeek.files.is_orig': 'zeek.files.is_orig', + 'zeek.files.overflow_bytes': 'zeek.files.overflow_bytes', + 'zeek.files.sha1': 'zeek.files.sha1', + 'zeek.files.duration': 'zeek.files.duration', + 'zeek.files.depth': 'zeek.files.depth', + 'zeek.files.analyzers': 'zeek.files.analyzers', + 'zeek.files.mime_type': 'zeek.files.mime_type', + 'zeek.files.rx_host': 'zeek.files.rx_host', + 'zeek.files.total_bytes': 'zeek.files.total_bytes', + 'zeek.files.fuid': 'zeek.files.fuid', + 'zeek.files.seen_bytes': 'zeek.files.seen_bytes', + 'zeek.files.missing_bytes': 'zeek.files.missing_bytes', + 'zeek.files.md5': 'zeek.files.md5', + 'zeek.ssl.cipher': 'zeek.ssl.cipher', + 'zeek.ssl.established': 'zeek.ssl.established', + 'zeek.ssl.resumed': 'zeek.ssl.resumed', + 'zeek.ssl.version': 'zeek.ssl.version', +}; + +export const sourceFieldsMap: Readonly> = { + 'source.bytes': 'source.bytes', + 'source.ip': 'source.ip', + 'source.packets': 'source.packets', + 'source.port': 'source.port', + 'source.domain': 'source.domain', + 'source.geo.continent_name': 'source.geo.continent_name', + 'source.geo.country_name': 'source.geo.country_name', + 'source.geo.country_iso_code': 'source.geo.country_iso_code', + 'source.geo.city_name': 'source.geo.city_name', + 'source.geo.region_iso_code': 'source.geo.region_iso_code', + 'source.geo.region_name': 'source.geo.region_name', +}; + +export const destinationFieldsMap: Readonly> = { + 'destination.bytes': 'destination.bytes', + 'destination.ip': 'destination.ip', + 'destination.packets': 'destination.packets', + 'destination.port': 'destination.port', + 'destination.domain': 'destination.domain', + 'destination.geo.continent_name': 'destination.geo.continent_name', + 'destination.geo.country_name': 'destination.geo.country_name', + 'destination.geo.country_iso_code': 'destination.geo.country_iso_code', + 'destination.geo.city_name': 'destination.geo.city_name', + 'destination.geo.region_iso_code': 'destination.geo.region_iso_code', + 'destination.geo.region_name': 'destination.geo.region_name', +}; + +export const networkFieldsMap: Readonly> = { + 'network.bytes': 'network.bytes', + 'network.community_id': 'network.community_id', + 'network.direction': 'network.direction', + 'network.packets': 'network.packets', + 'network.protocol': 'network.protocol', + 'network.transport': 'network.transport', +}; + +export const geoFieldsMap: Readonly> = { + 'geo.region_name': 'destination.geo.region_name', + 'geo.country_iso_code': 'destination.geo.country_iso_code', +}; + +export const dnsFieldsMap: Readonly> = { + 'dns.question.name': 'dns.question.name', + 'dns.question.type': 'dns.question.type', + 'dns.resolved_ip': 'dns.resolved_ip', + 'dns.response_code': 'dns.response_code', +}; + +export const endgameFieldsMap: Readonly> = { + 'endgame.exit_code': 'endgame.exit_code', + 'endgame.file_name': 'endgame.file_name', + 'endgame.file_path': 'endgame.file_path', + 'endgame.logon_type': 'endgame.logon_type', + 'endgame.parent_process_name': 'endgame.parent_process_name', + 'endgame.pid': 'endgame.pid', + 'endgame.process_name': 'endgame.process_name', + 'endgame.subject_domain_name': 'endgame.subject_domain_name', + 'endgame.subject_logon_id': 'endgame.subject_logon_id', + 'endgame.subject_user_name': 'endgame.subject_user_name', + 'endgame.target_domain_name': 'endgame.target_domain_name', + 'endgame.target_logon_id': 'endgame.target_logon_id', + 'endgame.target_user_name': 'endgame.target_user_name', +}; + +export const eventBaseFieldsMap: Readonly> = { + 'event.action': 'event.action', + 'event.category': 'event.category', + 'event.code': 'event.code', + 'event.created': 'event.created', + 'event.dataset': 'event.dataset', + 'event.duration': 'event.duration', + 'event.end': 'event.end', + 'event.hash': 'event.hash', + 'event.id': 'event.id', + 'event.kind': 'event.kind', + 'event.module': 'event.module', + 'event.original': 'event.original', + 'event.outcome': 'event.outcome', + 'event.risk_score': 'event.risk_score', + 'event.risk_score_norm': 'event.risk_score_norm', + 'event.severity': 'event.severity', + 'event.start': 'event.start', + 'event.timezone': 'event.timezone', + 'event.type': 'event.type', +}; + +export const systemFieldsMap: Readonly> = { + 'system.audit.package.arch': 'system.audit.package.arch', + 'system.audit.package.entity_id': 'system.audit.package.entity_id', + 'system.audit.package.name': 'system.audit.package.name', + 'system.audit.package.size': 'system.audit.package.size', + 'system.audit.package.summary': 'system.audit.package.summary', + 'system.audit.package.version': 'system.audit.package.version', + 'system.auth.ssh.signature': 'system.auth.ssh.signature', + 'system.auth.ssh.method': 'system.auth.ssh.method', +}; + +export const signalFieldsMap: Readonly> = { + 'signal.original_time': 'signal.original_time', + 'signal.rule.id': 'signal.rule.id', + 'signal.rule.saved_id': 'signal.rule.saved_id', + 'signal.rule.timeline_id': 'signal.rule.timeline_id', + 'signal.rule.timeline_title': 'signal.rule.timeline_title', + 'signal.rule.output_index': 'signal.rule.output_index', + 'signal.rule.from': 'signal.rule.from', + 'signal.rule.index': 'signal.rule.index', + 'signal.rule.language': 'signal.rule.language', + 'signal.rule.query': 'signal.rule.query', + 'signal.rule.to': 'signal.rule.to', + 'signal.rule.filters': 'signal.rule.filters', + 'signal.rule.rule_id': 'signal.rule.rule_id', + 'signal.rule.false_positives': 'signal.rule.false_positives', + 'signal.rule.max_signals': 'signal.rule.max_signals', + 'signal.rule.risk_score': 'signal.rule.risk_score', + 'signal.rule.description': 'signal.rule.description', + 'signal.rule.name': 'signal.rule.name', + 'signal.rule.immutable': 'signal.rule.immutable', + 'signal.rule.references': 'signal.rule.references', + 'signal.rule.severity': 'signal.rule.severity', + 'signal.rule.tags': 'signal.rule.tags', + 'signal.rule.threat': 'signal.rule.threat', + 'signal.rule.type': 'signal.rule.type', + 'signal.rule.size': 'signal.rule.size', + 'signal.rule.enabled': 'signal.rule.enabled', + 'signal.rule.created_at': 'signal.rule.created_at', + 'signal.rule.updated_at': 'signal.rule.updated_at', + 'signal.rule.created_by': 'signal.rule.created_by', + 'signal.rule.updated_by': 'signal.rule.updated_by', + 'signal.rule.version': 'signal.rule.version', + 'signal.rule.note': 'signal.rule.note', + 'signal.rule.threshold': 'signal.rule.threshold', + 'signal.rule.exceptions_list': 'signal.rule.exceptions_list', +}; + +export const ruleFieldsMap: Readonly> = { + 'rule.reference': 'rule.reference', +}; + +export const eventFieldsMap: Readonly> = { + timestamp: '@timestamp', + '@timestamp': '@timestamp', + message: 'message', + ...{ ...agentFieldsMap }, + ...{ ...auditdMap }, + ...{ ...destinationFieldsMap }, + ...{ ...dnsFieldsMap }, + ...{ ...endgameFieldsMap }, + ...{ ...eventBaseFieldsMap }, + ...{ ...fileMap }, + ...{ ...geoFieldsMap }, + ...{ ...hostFieldsMap }, + ...{ ...networkFieldsMap }, + ...{ ...ruleFieldsMap }, + ...{ ...signalFieldsMap }, + ...{ ...sourceFieldsMap }, + ...{ ...suricataFieldsMap }, + ...{ ...systemFieldsMap }, + ...{ ...tlsFieldsMap }, + ...{ ...zeekFieldsMap }, + ...{ ...httpFieldsMap }, + ...{ ...userFieldsMap }, + ...{ ...winlogFieldsMap }, + ...{ ...processFieldsMap }, +}; diff --git a/x-pack/plugins/osquery/common/ecs/endgame/index.ts b/x-pack/plugins/osquery/common/ecs/endgame/index.ts new file mode 100644 index 0000000000000..d2fc5d61527a5 --- /dev/null +++ b/x-pack/plugins/osquery/common/ecs/endgame/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface EndgameEcs { + exit_code?: number[]; + file_name?: string[]; + file_path?: string[]; + logon_type?: number[]; + parent_process_name?: string[]; + pid?: number[]; + process_name?: string[]; + subject_domain_name?: string[]; + subject_logon_id?: string[]; + subject_user_name?: string[]; + target_domain_name?: string[]; + target_logon_id?: string[]; + target_user_name?: string[]; +} diff --git a/x-pack/plugins/osquery/common/ecs/event/index.ts b/x-pack/plugins/osquery/common/ecs/event/index.ts new file mode 100644 index 0000000000000..c3b7b1d0b8436 --- /dev/null +++ b/x-pack/plugins/osquery/common/ecs/event/index.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface EventEcs { + action?: string[]; + category?: string[]; + code?: string[]; + created?: string[]; + dataset?: string[]; + duration?: number[]; + end?: string[]; + hash?: string[]; + id?: string[]; + kind?: string[]; + module?: string[]; + original?: string[]; + outcome?: string[]; + risk_score?: number[]; + risk_score_norm?: number[]; + severity?: number[]; + start?: string[]; + timezone?: string[]; + type?: string[]; +} diff --git a/x-pack/plugins/osquery/common/ecs/file/index.ts b/x-pack/plugins/osquery/common/ecs/file/index.ts new file mode 100644 index 0000000000000..b01e9514bf425 --- /dev/null +++ b/x-pack/plugins/osquery/common/ecs/file/index.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface CodeSignature { + subject_name: string[]; + trusted: string[]; +} +export interface Ext { + code_signature: CodeSignature[] | CodeSignature; +} +export interface Hash { + sha256: string[]; +} + +export interface FileEcs { + name?: string[]; + path?: string[]; + target_path?: string[]; + extension?: string[]; + Ext?: Ext; + type?: string[]; + device?: string[]; + inode?: string[]; + uid?: string[]; + owner?: string[]; + gid?: string[]; + group?: string[]; + mode?: string[]; + size?: number[]; + mtime?: string[]; + ctime?: string[]; + hash?: Hash; +} diff --git a/x-pack/plugins/osquery/common/ecs/geo/index.ts b/x-pack/plugins/osquery/common/ecs/geo/index.ts new file mode 100644 index 0000000000000..4a4c76adb097b --- /dev/null +++ b/x-pack/plugins/osquery/common/ecs/geo/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface GeoEcs { + city_name?: string[]; + continent_name?: string[]; + country_iso_code?: string[]; + country_name?: string[]; + location?: Location; + region_iso_code?: string[]; + region_name?: string[]; +} + +export interface Location { + lon?: number[]; + lat?: number[]; +} diff --git a/x-pack/plugins/osquery/common/ecs/host/index.ts b/x-pack/plugins/osquery/common/ecs/host/index.ts new file mode 100644 index 0000000000000..27cbe433f9bf7 --- /dev/null +++ b/x-pack/plugins/osquery/common/ecs/host/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface HostEcs { + architecture?: string[]; + id?: string[]; + ip?: string[]; + mac?: string[]; + name?: string[]; + os?: OsEcs; + type?: string[]; +} + +export interface OsEcs { + platform?: string[]; + name?: string[]; + full?: string[]; + family?: string[]; + version?: string[]; + kernel?: string[]; +} diff --git a/x-pack/plugins/osquery/common/ecs/http/index.ts b/x-pack/plugins/osquery/common/ecs/http/index.ts new file mode 100644 index 0000000000000..c5c5d1e140d0a --- /dev/null +++ b/x-pack/plugins/osquery/common/ecs/http/index.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface HttpEcs { + version?: string[]; + request?: HttpRequestData; + response?: HttpResponseData; +} + +export interface HttpRequestData { + method?: string[]; + body?: HttpBodyData; + referrer?: string[]; + bytes?: number[]; +} + +export interface HttpBodyData { + content?: string[]; + bytes?: number[]; +} + +export interface HttpResponseData { + status_code?: number[]; + body?: HttpBodyData; + bytes?: number[]; +} diff --git a/x-pack/plugins/osquery/common/ecs/index.ts b/x-pack/plugins/osquery/common/ecs/index.ts new file mode 100644 index 0000000000000..b8190463f5da5 --- /dev/null +++ b/x-pack/plugins/osquery/common/ecs/index.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 { AgentEcs } from './agent'; +import { AuditdEcs } from './auditd'; +import { DestinationEcs } from './destination'; +import { DnsEcs } from './dns'; +import { EndgameEcs } from './endgame'; +import { EventEcs } from './event'; +import { FileEcs } from './file'; +import { GeoEcs } from './geo'; +import { HostEcs } from './host'; +import { NetworkEcs } from './network'; +import { RuleEcs } from './rule'; +import { SignalEcs } from './signal'; +import { SourceEcs } from './source'; +import { SuricataEcs } from './suricata'; +import { TlsEcs } from './tls'; +import { ZeekEcs } from './zeek'; +import { HttpEcs } from './http'; +import { UrlEcs } from './url'; +import { UserEcs } from './user'; +import { WinlogEcs } from './winlog'; +import { ProcessEcs } from './process'; +import { SystemEcs } from './system'; + +export interface Ecs { + _id: string; + _index?: string; + agent?: AgentEcs; + auditd?: AuditdEcs; + destination?: DestinationEcs; + dns?: DnsEcs; + endgame?: EndgameEcs; + event?: EventEcs; + geo?: GeoEcs; + host?: HostEcs; + network?: NetworkEcs; + rule?: RuleEcs; + signal?: SignalEcs; + source?: SourceEcs; + suricata?: SuricataEcs; + tls?: TlsEcs; + zeek?: ZeekEcs; + http?: HttpEcs; + url?: UrlEcs; + timestamp?: string; + message?: string[]; + user?: UserEcs; + winlog?: WinlogEcs; + process?: ProcessEcs; + file?: FileEcs; + system?: SystemEcs; +} diff --git a/x-pack/plugins/osquery/common/ecs/network/index.ts b/x-pack/plugins/osquery/common/ecs/network/index.ts new file mode 100644 index 0000000000000..18f7583d12231 --- /dev/null +++ b/x-pack/plugins/osquery/common/ecs/network/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface NetworkEcs { + bytes?: number[]; + community_id?: string[]; + direction?: string[]; + packets?: number[]; + protocol?: string[]; + transport?: string[]; +} diff --git a/x-pack/plugins/osquery/common/ecs/process/index.ts b/x-pack/plugins/osquery/common/ecs/process/index.ts new file mode 100644 index 0000000000000..451f1455f55d4 --- /dev/null +++ b/x-pack/plugins/osquery/common/ecs/process/index.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface ProcessEcs { + entity_id?: string[]; + hash?: ProcessHashData; + pid?: number[]; + name?: string[]; + ppid?: number[]; + args?: string[]; + executable?: string[]; + title?: string[]; + thread?: Thread; + working_directory?: string[]; +} + +export interface ProcessHashData { + md5?: string[]; + sha1?: string[]; + sha256?: string[]; +} + +export interface Thread { + id?: number[]; + start?: string[]; +} diff --git a/x-pack/plugins/osquery/common/ecs/rule/index.ts b/x-pack/plugins/osquery/common/ecs/rule/index.ts new file mode 100644 index 0000000000000..47d1323371941 --- /dev/null +++ b/x-pack/plugins/osquery/common/ecs/rule/index.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface RuleEcs { + id?: string[]; + rule_id?: string[]; + name?: string[]; + false_positives: string[]; + saved_id?: string[]; + timeline_id?: string[]; + timeline_title?: string[]; + max_signals?: number[]; + risk_score?: string[]; + output_index?: string[]; + description?: string[]; + from?: string[]; + immutable?: boolean[]; + index?: string[]; + interval?: string[]; + language?: string[]; + query?: string[]; + references?: string[]; + severity?: string[]; + tags?: string[]; + threat?: unknown; + threshold?: { + field: string; + value: number; + }; + type?: string[]; + size?: string[]; + to?: string[]; + enabled?: boolean[]; + filters?: unknown; + created_at?: string[]; + updated_at?: string[]; + created_by?: string[]; + updated_by?: string[]; + version?: string[]; + note?: string[]; + building_block_type?: string[]; +} diff --git a/x-pack/plugins/osquery/common/ecs/signal/index.ts b/x-pack/plugins/osquery/common/ecs/signal/index.ts new file mode 100644 index 0000000000000..6482b892bc18d --- /dev/null +++ b/x-pack/plugins/osquery/common/ecs/signal/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RuleEcs } from '../rule'; + +export interface SignalEcs { + rule?: RuleEcs; + original_time?: string[]; + status?: string[]; + group?: { + id?: string[]; + }; +} diff --git a/x-pack/plugins/osquery/common/ecs/source/index.ts b/x-pack/plugins/osquery/common/ecs/source/index.ts new file mode 100644 index 0000000000000..2c8618f4edcd0 --- /dev/null +++ b/x-pack/plugins/osquery/common/ecs/source/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { GeoEcs } from '../geo'; + +export interface SourceEcs { + bytes?: number[]; + ip?: string[]; + port?: number[]; + domain?: string[]; + geo?: GeoEcs; + packets?: number[]; +} diff --git a/x-pack/plugins/osquery/common/ecs/suricata/index.ts b/x-pack/plugins/osquery/common/ecs/suricata/index.ts new file mode 100644 index 0000000000000..0ef253ada2620 --- /dev/null +++ b/x-pack/plugins/osquery/common/ecs/suricata/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface SuricataEcs { + eve?: SuricataEveData; +} + +export interface SuricataEveData { + alert?: SuricataAlertData; + flow_id?: number[]; + proto?: string[]; +} + +export interface SuricataAlertData { + signature?: string[]; + signature_id?: number[]; +} diff --git a/x-pack/plugins/osquery/common/ecs/system/index.ts b/x-pack/plugins/osquery/common/ecs/system/index.ts new file mode 100644 index 0000000000000..641a10209c150 --- /dev/null +++ b/x-pack/plugins/osquery/common/ecs/system/index.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface SystemEcs { + audit?: AuditEcs; + auth?: AuthEcs; +} + +export interface AuditEcs { + package?: PackageEcs; +} + +export interface PackageEcs { + arch?: string[]; + entity_id?: string[]; + name?: string[]; + size?: number[]; + summary?: string[]; + version?: string[]; +} + +export interface AuthEcs { + ssh?: SshEcs; +} + +export interface SshEcs { + method?: string[]; + signature?: string[]; +} diff --git a/x-pack/plugins/osquery/common/ecs/tls/index.ts b/x-pack/plugins/osquery/common/ecs/tls/index.ts new file mode 100644 index 0000000000000..1533d46417d0a --- /dev/null +++ b/x-pack/plugins/osquery/common/ecs/tls/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. + */ + +export interface TlsEcs { + client_certificate?: TlsClientCertificateData; + fingerprints?: TlsFingerprintsData; + server_certificate?: TlsServerCertificateData; +} + +export interface TlsClientCertificateData { + fingerprint?: FingerprintData; +} + +export interface FingerprintData { + sha1?: string[]; +} + +export interface TlsFingerprintsData { + ja3?: TlsJa3Data; +} + +export interface TlsJa3Data { + hash?: string[]; +} + +export interface TlsServerCertificateData { + fingerprint?: FingerprintData; +} diff --git a/x-pack/plugins/osquery/common/ecs/url/index.ts b/x-pack/plugins/osquery/common/ecs/url/index.ts new file mode 100644 index 0000000000000..91d7958c813a3 --- /dev/null +++ b/x-pack/plugins/osquery/common/ecs/url/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface UrlEcs { + domain?: string[]; + original?: string[]; + username?: string[]; + password?: string[]; +} diff --git a/x-pack/plugins/osquery/common/ecs/user/index.ts b/x-pack/plugins/osquery/common/ecs/user/index.ts new file mode 100644 index 0000000000000..35de2e0459ceb --- /dev/null +++ b/x-pack/plugins/osquery/common/ecs/user/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface UserEcs { + domain?: string[]; + id?: string[]; + name?: string[]; + full_name?: string[]; + email?: string[]; + hash?: string[]; + group?: string[]; +} diff --git a/x-pack/plugins/osquery/common/ecs/winlog/index.ts b/x-pack/plugins/osquery/common/ecs/winlog/index.ts new file mode 100644 index 0000000000000..a449fb9130e6f --- /dev/null +++ b/x-pack/plugins/osquery/common/ecs/winlog/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface WinlogEcs { + event_id?: number[]; +} diff --git a/x-pack/plugins/osquery/common/ecs/zeek/index.ts b/x-pack/plugins/osquery/common/ecs/zeek/index.ts new file mode 100644 index 0000000000000..2563612f09bfb --- /dev/null +++ b/x-pack/plugins/osquery/common/ecs/zeek/index.ts @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface ZeekEcs { + session_id?: string[]; + connection?: ZeekConnectionData; + notice?: ZeekNoticeData; + dns?: ZeekDnsData; + http?: ZeekHttpData; + files?: ZeekFileData; + ssl?: ZeekSslData; +} + +export interface ZeekConnectionData { + local_resp?: boolean[]; + local_orig?: boolean[]; + missed_bytes?: number[]; + state?: string[]; + history?: string[]; +} + +export interface ZeekNoticeData { + suppress_for?: number[]; + msg?: string[]; + note?: string[]; + sub?: string[]; + dst?: string[]; + dropped?: boolean[]; + peer_descr?: string[]; +} + +export interface ZeekDnsData { + AA?: boolean[]; + qclass_name?: string[]; + RD?: boolean[]; + qtype_name?: string[]; + rejected?: boolean[]; + qtype?: string[]; + query?: string[]; + trans_id?: number[]; + qclass?: string[]; + RA?: boolean[]; + TC?: boolean[]; +} + +export interface ZeekHttpData { + resp_mime_types?: string[]; + trans_depth?: string[]; + status_msg?: string[]; + resp_fuids?: string[]; + tags?: string[]; +} + +export interface ZeekFileData { + session_ids?: string[]; + timedout?: boolean[]; + local_orig?: boolean[]; + tx_host?: string[]; + source?: string[]; + is_orig?: boolean[]; + overflow_bytes?: number[]; + sha1?: string[]; + duration?: number[]; + depth?: number[]; + analyzers?: string[]; + mime_type?: string[]; + rx_host?: string[]; + total_bytes?: number[]; + fuid?: string[]; + seen_bytes?: number[]; + missing_bytes?: number[]; + md5?: string[]; +} + +export interface ZeekSslData { + cipher?: string[]; + established?: boolean[]; + resumed?: boolean[]; + version?: string[]; +} diff --git a/x-pack/plugins/osquery/common/index.ts b/x-pack/plugins/osquery/common/index.ts new file mode 100644 index 0000000000000..e4bbf4781e881 --- /dev/null +++ b/x-pack/plugins/osquery/common/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './constants'; + +export const PLUGIN_ID = 'osquery'; +export const PLUGIN_NAME = 'osquery'; diff --git a/x-pack/plugins/osquery/common/search_strategy/common/index.ts b/x-pack/plugins/osquery/common/search_strategy/common/index.ts new file mode 100644 index 0000000000000..0c1f13dac2e69 --- /dev/null +++ b/x-pack/plugins/osquery/common/search_strategy/common/index.ts @@ -0,0 +1,125 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEsSearchResponse } from '../../../../../../src/plugins/data/common'; + +export type Maybe = T | null; + +export type SearchHit = IEsSearchResponse['rawResponse']['hits']['hits'][0]; + +export interface TotalValue { + value: number; + relation: string; +} + +export interface Inspect { + dsl: string[]; +} + +export interface PageInfoPaginated { + activePage: number; + fakeTotalCount: number; + showMorePagesIndicator: boolean; +} + +export interface CursorType { + value?: Maybe; + tiebreaker?: Maybe; +} + +export enum Direction { + asc = 'asc', + desc = 'desc', +} + +export interface SortField { + field: Field; + direction: Direction; +} + +export interface TimerangeInput { + /** The interval string to use for last bucket. The format is '{value}{unit}'. For example '5m' would return the metrics for the last 5 minutes of the timespan. */ + interval: string; + /** The end of the timerange */ + to: string; + /** The beginning of the timerange */ + from: string; +} + +export interface PaginationInput { + /** The limit parameter allows you to configure the maximum amount of items to be returned */ + limit: number; + /** The cursor parameter defines the next result you want to fetch */ + cursor?: Maybe; + /** The tiebreaker parameter allow to be more precise to fetch the next item */ + tiebreaker?: Maybe; +} + +export interface PaginationInputPaginated { + /** The activePage parameter defines the page of results you want to fetch */ + activePage: number; + /** The cursorStart parameter defines the start of the results to be displayed */ + cursorStart: number; + /** The fakePossibleCount parameter determines the total count in order to show 5 additional pages */ + fakePossibleCount: number; + /** The querySize parameter is the number of items to be returned */ + querySize: number; +} + +export interface DocValueFields { + field: string; + format?: string | null; +} + +export interface Explanation { + value: number; + description: string; + details: Explanation[]; +} + +export interface ShardsResponse { + total: number; + successful: number; + failed: number; + skipped: number; +} + +export interface TotalHit { + value: number; + relation: string; +} + +export interface Hit { + _index: string; + _type: string; + _id: string; + _score: number | null; +} + +export interface Hits { + hits: { + total: T; + max_score: number | null; + hits: U[]; + }; +} + +export interface GenericBuckets { + key: string; + doc_count: number; +} + +export type StringOrNumber = string | number; + +export interface TimerangeFilter { + range: { + [timestamp: string]: { + gte: string; + lte: string; + format: string; + }; + }; +} diff --git a/x-pack/plugins/osquery/common/search_strategy/index.ts b/x-pack/plugins/osquery/common/search_strategy/index.ts new file mode 100644 index 0000000000000..ff9a8d1aa64c9 --- /dev/null +++ b/x-pack/plugins/osquery/common/search_strategy/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './common'; +export * from './osquery'; diff --git a/x-pack/plugins/osquery/common/search_strategy/osquery/actions/index.ts b/x-pack/plugins/osquery/common/search_strategy/osquery/actions/index.ts new file mode 100644 index 0000000000000..076fa02747573 --- /dev/null +++ b/x-pack/plugins/osquery/common/search_strategy/osquery/actions/index.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SearchResponse } from 'elasticsearch'; +import { IEsSearchResponse } from '../../../../../../../src/plugins/data/common'; + +import { Inspect, Maybe, PageInfoPaginated } from '../../common'; +import { RequestOptions, RequestOptionsPaginated } from '../..'; + +export type ActionEdges = SearchResponse['hits']['hits']; + +export type ActionResultEdges = SearchResponse['hits']['hits']; +export interface ActionsStrategyResponse extends IEsSearchResponse { + edges: ActionEdges; + totalCount: number; + pageInfo: PageInfoPaginated; + inspect?: Maybe; +} + +export type ActionsRequestOptions = RequestOptionsPaginated<{}>; + +export interface ActionDetailsStrategyResponse extends IEsSearchResponse { + actionDetails: Record; + inspect?: Maybe; +} + +export interface ActionDetailsRequestOptions extends RequestOptions { + actionId: string; +} + +export interface ActionResultsStrategyResponse extends IEsSearchResponse { + edges: ActionResultEdges; + totalCount: number; + pageInfo: PageInfoPaginated; + inspect?: Maybe; +} + +export interface ActionResultsRequestOptions extends RequestOptionsPaginated { + actionId: string; +} diff --git a/x-pack/plugins/osquery/common/search_strategy/osquery/agents/index.ts b/x-pack/plugins/osquery/common/search_strategy/osquery/agents/index.ts new file mode 100644 index 0000000000000..64a570ef5525b --- /dev/null +++ b/x-pack/plugins/osquery/common/search_strategy/osquery/agents/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 { IEsSearchResponse } from '../../../../../../../src/plugins/data/common'; + +import { Inspect, Maybe, PageInfoPaginated } from '../../common'; +import { RequestOptionsPaginated } from '../..'; +import { Agent } from '../../../shared_imports'; + +export interface AgentsStrategyResponse extends IEsSearchResponse { + edges: Agent[]; + totalCount: number; + pageInfo: PageInfoPaginated; + inspect?: Maybe; +} + +export type AgentsRequestOptions = RequestOptionsPaginated<{}>; diff --git a/x-pack/plugins/osquery/common/search_strategy/osquery/common/index.ts b/x-pack/plugins/osquery/common/search_strategy/osquery/common/index.ts new file mode 100644 index 0000000000000..fc58184f40afe --- /dev/null +++ b/x-pack/plugins/osquery/common/search_strategy/osquery/common/index.ts @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CloudEcs } from '../../../ecs/cloud'; +import { HostEcs, OsEcs } from '../../../ecs/host'; +import { Hit, Hits, Maybe, SearchHit, StringOrNumber, TotalValue } from '../../common'; + +export enum HostPolicyResponseActionStatus { + success = 'success', + failure = 'failure', + warning = 'warning', +} + +export enum HostsFields { + lastSeen = 'lastSeen', + hostName = 'hostName', +} + +export interface EndpointFields { + endpointPolicy?: Maybe; + sensorVersion?: Maybe; + policyStatus?: Maybe; +} + +export interface HostItem { + _id?: Maybe; + cloud?: Maybe; + endpoint?: Maybe; + host?: Maybe; + lastSeen?: Maybe; +} + +export interface HostValue { + value: number; + value_as_string: string; +} + +export interface HostBucketItem { + key: string; + doc_count: number; + timestamp: HostValue; +} + +export interface HostBuckets { + buckets: HostBucketItem[]; +} + +export interface HostOsHitsItem { + hits: { + total: TotalValue | number; + max_score: number | null; + hits: Array<{ + _source: { host: { os: Maybe } }; + sort?: [number]; + _index?: string; + _type?: string; + _id?: string; + _score?: number | null; + }>; + }; +} + +export interface HostAggEsItem { + cloud_instance_id?: HostBuckets; + cloud_machine_type?: HostBuckets; + cloud_provider?: HostBuckets; + cloud_region?: HostBuckets; + firstSeen?: HostValue; + host_architecture?: HostBuckets; + host_id?: HostBuckets; + host_ip?: HostBuckets; + host_mac?: HostBuckets; + host_name?: HostBuckets; + host_os_name?: HostBuckets; + host_os_version?: HostBuckets; + host_type?: HostBuckets; + key?: string; + lastSeen?: HostValue; + os?: HostOsHitsItem; +} + +export interface HostEsData extends SearchHit { + sort: string[]; + aggregations: { + host_count: { + value: number; + }; + host_data: { + buckets: HostAggEsItem[]; + }; + }; +} + +export interface HostAggEsData extends SearchHit { + sort: string[]; + aggregations: HostAggEsItem; +} + +export interface HostHit extends Hit { + _source: { + '@timestamp'?: string; + host: HostEcs; + }; + cursor?: string; + firstSeen?: string; + sort?: StringOrNumber[]; +} + +export type HostHits = Hits; diff --git a/x-pack/plugins/osquery/common/search_strategy/osquery/index.ts b/x-pack/plugins/osquery/common/search_strategy/osquery/index.ts new file mode 100644 index 0000000000000..70882ffcc2e5c --- /dev/null +++ b/x-pack/plugins/osquery/common/search_strategy/osquery/index.ts @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEsSearchRequest } from '../../../../../../src/plugins/data/common'; +import { ESQuery } from '../../typed_json'; +import { + ActionsStrategyResponse, + ActionsRequestOptions, + ActionDetailsStrategyResponse, + ActionDetailsRequestOptions, + ActionResultsStrategyResponse, + ActionResultsRequestOptions, +} from './actions'; +import { AgentsStrategyResponse, AgentsRequestOptions } from './agents'; +import { ResultsStrategyResponse, ResultsRequestOptions } from './results'; + +import { DocValueFields, SortField, PaginationInputPaginated } from '../common'; + +export * from './actions'; +export * from './agents'; +export * from './results'; + +export enum OsqueryQueries { + actions = 'actions', + actionDetails = 'actionDetails', + actionResults = 'actionResults', + agents = 'agents', + results = 'results', +} + +export type FactoryQueryTypes = OsqueryQueries; + +export interface RequestBasicOptions extends IEsSearchRequest { + filterQuery: ESQuery | string | undefined; + docValueFields?: DocValueFields[]; + factoryQueryType?: FactoryQueryTypes; +} + +/** A mapping of semantic fields to their document counterparts */ + +export type RequestOptions = RequestBasicOptions; + +export interface RequestOptionsPaginated extends RequestBasicOptions { + pagination: PaginationInputPaginated; + sort: SortField; +} + +export type StrategyResponseType = T extends OsqueryQueries.actions + ? ActionsStrategyResponse + : T extends OsqueryQueries.actionDetails + ? ActionDetailsStrategyResponse + : T extends OsqueryQueries.actionResults + ? ActionResultsStrategyResponse + : T extends OsqueryQueries.agents + ? AgentsStrategyResponse + : T extends OsqueryQueries.results + ? ResultsStrategyResponse + : never; + +export type StrategyRequestType = T extends OsqueryQueries.actions + ? ActionsRequestOptions + : T extends OsqueryQueries.actionDetails + ? ActionDetailsRequestOptions + : T extends OsqueryQueries.actionResults + ? ActionResultsRequestOptions + : T extends OsqueryQueries.agents + ? AgentsRequestOptions + : T extends OsqueryQueries.results + ? ResultsRequestOptions + : never; diff --git a/x-pack/plugins/osquery/common/search_strategy/osquery/results/index.ts b/x-pack/plugins/osquery/common/search_strategy/osquery/results/index.ts new file mode 100644 index 0000000000000..65df2591338e4 --- /dev/null +++ b/x-pack/plugins/osquery/common/search_strategy/osquery/results/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SearchResponse } from 'elasticsearch'; +import { IEsSearchResponse } from '../../../../../../../src/plugins/data/common'; + +import { Inspect, Maybe, PageInfoPaginated } from '../../common'; +import { RequestOptionsPaginated } from '../..'; + +export type ResultEdges = SearchResponse['hits']['hits']; + +export interface ResultsStrategyResponse extends IEsSearchResponse { + edges: ResultEdges; + totalCount: number; + pageInfo: PageInfoPaginated; + inspect?: Maybe; +} + +export interface ResultsRequestOptions extends RequestOptionsPaginated<{}> { + actionId: string; +} diff --git a/x-pack/plugins/osquery/common/shared_imports.ts b/x-pack/plugins/osquery/common/shared_imports.ts new file mode 100644 index 0000000000000..58133db6aa1b0 --- /dev/null +++ b/x-pack/plugins/osquery/common/shared_imports.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { Agent } from '../../fleet/common'; diff --git a/x-pack/plugins/osquery/common/typed_json.ts b/x-pack/plugins/osquery/common/typed_json.ts new file mode 100644 index 0000000000000..0d6e3877eae55 --- /dev/null +++ b/x-pack/plugins/osquery/common/typed_json.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 { DslQuery, Filter } from 'src/plugins/data/common'; + +import { JsonObject } from '../../../../src/plugins/kibana_utils/common'; + +export type ESQuery = + | ESRangeQuery + | ESQueryStringQuery + | ESMatchQuery + | ESTermQuery + | ESBoolQuery + | JsonObject; + +export interface ESRangeQuery { + range: { + [name: string]: { + gte: number; + lte: number; + format: string; + }; + }; +} + +export interface ESMatchQuery { + match: { + [name: string]: { + query: string; + operator: string; + zero_terms_query: string; + }; + }; +} + +export interface ESQueryStringQuery { + query_string: { + query: string; + analyze_wildcard: boolean; + }; +} + +export interface ESTermQuery { + term: Record; +} + +export interface ESBoolQuery { + bool: { + must: DslQuery[]; + filter: Filter[]; + should: never[]; + must_not: Filter[]; + }; +} diff --git a/x-pack/plugins/osquery/common/utility_types.ts b/x-pack/plugins/osquery/common/utility_types.ts new file mode 100644 index 0000000000000..4a7bd02d0442b --- /dev/null +++ b/x-pack/plugins/osquery/common/utility_types.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as runtimeTypes from 'io-ts'; +import { ReactNode } from 'react'; + +// This type is for typing EuiDescriptionList +export interface DescriptionList { + title: NonNullable; + description: NonNullable; +} + +export const unionWithNullType = (type: T) => + runtimeTypes.union([type, runtimeTypes.null]); + +export const stringEnum = (enumObj: T, enumName = 'enum') => + new runtimeTypes.Type( + enumName, + (u): u is T[keyof T] => Object.values(enumObj).includes(u), + (u, c) => + Object.values(enumObj).includes(u) + ? runtimeTypes.success(u as T[keyof T]) + : runtimeTypes.failure(u, c), + (a) => (a as unknown) as string + ); + +/** + * Unreachable Assertion helper for scenarios like exhaustive switches. + * For references see: https://stackoverflow.com/questions/39419170/how-do-i-check-that-a-switch-block-is-exhaustive-in-typescript + * This "x" should _always_ be a type of "never" and not change to "unknown" or any other type. See above link or the generic + * concept of exhaustive checks in switch blocks. + * + * Optionally you can avoid the use of this by using early returns and TypeScript will clear your type checking without complaints + * but there are situations and times where this function might still be needed. + * @param x Unreachable field + * @param message Message of error thrown + */ +export const assertUnreachable = ( + x: never, // This should always be a type of "never" + message = 'Unknown Field in switch statement' +): never => { + throw new Error(`${message}: ${x}`); +}; diff --git a/x-pack/plugins/osquery/common/utils/build_query/filters.ts b/x-pack/plugins/osquery/common/utils/build_query/filters.ts new file mode 100644 index 0000000000000..bde03be3f5edc --- /dev/null +++ b/x-pack/plugins/osquery/common/utils/build_query/filters.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { isEmpty, isString } from 'lodash/fp'; + +import { ESQuery } from '../../../common/typed_json'; + +export const createQueryFilterClauses = (filterQuery: ESQuery | string | undefined) => + !isEmpty(filterQuery) ? [isString(filterQuery) ? JSON.parse(filterQuery) : filterQuery] : []; diff --git a/x-pack/plugins/osquery/common/utils/build_query/index.ts b/x-pack/plugins/osquery/common/utils/build_query/index.ts new file mode 100644 index 0000000000000..05606d556528c --- /dev/null +++ b/x-pack/plugins/osquery/common/utils/build_query/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './filters'; + +export const inspectStringifyObject = (obj: unknown) => { + try { + return JSON.stringify(obj, null, 2); + } catch { + return 'Sorry about that, something went wrong.'; + } +}; diff --git a/x-pack/plugins/vis_type_timeseries_enhanced/jest.config.js b/x-pack/plugins/osquery/jest.config.js similarity index 82% rename from x-pack/plugins/vis_type_timeseries_enhanced/jest.config.js rename to x-pack/plugins/osquery/jest.config.js index 17c5c87e3ccc2..8132491df8534 100644 --- a/x-pack/plugins/vis_type_timeseries_enhanced/jest.config.js +++ b/x-pack/plugins/osquery/jest.config.js @@ -7,5 +7,5 @@ module.exports = { preset: '@kbn/test', rootDir: '../../..', - roots: ['/x-pack/plugins/vis_type_timeseries_enhanced'], + roots: ['/x-pack/plugins/osquery'], }; diff --git a/x-pack/plugins/osquery/kibana.json b/x-pack/plugins/osquery/kibana.json new file mode 100644 index 0000000000000..f6e90b9460506 --- /dev/null +++ b/x-pack/plugins/osquery/kibana.json @@ -0,0 +1,29 @@ +{ + "configPath": [ + "xpack", + "osquery" + ], + "extraPublicDirs": [ + "common" + ], + "id": "osquery", + "kibanaVersion": "kibana", + "optionalPlugins": [ + "home" + ], + "requiredBundles": [ + "esUiShared", + "kibanaUtils", + "kibanaReact", + "kibanaUtils" + ], + "requiredPlugins": [ + "data", + "dataEnhanced", + "fleet", + "navigation" + ], + "server": true, + "ui": true, + "version": "8.0.0" +} diff --git a/x-pack/plugins/osquery/public/action_results/action_results_table.tsx b/x-pack/plugins/osquery/public/action_results/action_results_table.tsx new file mode 100644 index 0000000000000..68424d848a9c7 --- /dev/null +++ b/x-pack/plugins/osquery/public/action_results/action_results_table.tsx @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { isEmpty, isEqual, keys, map } from 'lodash/fp'; +import { EuiDataGrid, EuiDataGridProps, EuiDataGridColumn, EuiDataGridSorting } from '@elastic/eui'; +import React, { createContext, useEffect, useState, useCallback, useContext, useMemo } from 'react'; + +import { useAllResults } from './use_action_results'; +import { Direction, ResultEdges } from '../../common/search_strategy'; + +const DataContext = createContext([]); + +interface ActionResultsTableProps { + actionId: string; +} + +const ActionResultsTableComponent: React.FC = ({ actionId }) => { + const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 50 }); + const onChangeItemsPerPage = useCallback( + (pageSize) => + setPagination((currentPagination) => ({ + ...currentPagination, + pageSize, + pageIndex: 0, + })), + [setPagination] + ); + const onChangePage = useCallback( + (pageIndex) => setPagination((currentPagination) => ({ ...currentPagination, pageIndex })), + [setPagination] + ); + + const [columns, setColumns] = useState([]); + + // ** Sorting config + const [sortingColumns, setSortingColumns] = useState([]); + + const [, { results, totalCount }] = useAllResults({ + actionId, + activePage: pagination.pageIndex, + limit: pagination.pageSize, + direction: Direction.asc, + sortField: '@timestamp', + }); + + // Column visibility + const [visibleColumns, setVisibleColumns] = useState([]); // initialize to the full set of columns + + const columnVisibility = useMemo(() => ({ visibleColumns, setVisibleColumns }), [ + visibleColumns, + setVisibleColumns, + ]); + + const renderCellValue: EuiDataGridProps['renderCellValue'] = useMemo( + () => ({ rowIndex, columnId, setCellProps }) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const data = useContext(DataContext); + const value = data[rowIndex].fields[columnId]; + + return !isEmpty(value) ? value : '-'; + }, + [] + ); + + const tableSorting: EuiDataGridSorting = useMemo( + () => ({ columns: sortingColumns, onSort: setSortingColumns }), + [sortingColumns] + ); + + const tablePagination = useMemo( + () => ({ + ...pagination, + pageSizeOptions: [10, 50, 100], + onChangeItemsPerPage, + onChangePage, + }), + [onChangeItemsPerPage, onChangePage, pagination] + ); + + useEffect(() => { + const newColumns = keys(results[0]?.fields) + .sort() + .map((fieldName) => ({ + id: fieldName, + displayAsText: fieldName.split('.')[1], + defaultSortDirection: Direction.asc, + })); + + if (!isEqual(columns, newColumns)) { + setColumns(newColumns); + setVisibleColumns(map('id', newColumns)); + } + }, [columns, results]); + + return ( + + + + ); +}; + +export const ActionResultsTable = React.memo(ActionResultsTableComponent); diff --git a/x-pack/plugins/osquery/public/action_results/helpers.ts b/x-pack/plugins/osquery/public/action_results/helpers.ts new file mode 100644 index 0000000000000..9f908e16c2eb2 --- /dev/null +++ b/x-pack/plugins/osquery/public/action_results/helpers.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 { + PaginationInputPaginated, + FactoryQueryTypes, + StrategyResponseType, + Inspect, +} from '../../common/search_strategy'; + +export type InspectResponse = Inspect & { response: string[] }; + +export const generateTablePaginationOptions = ( + activePage: number, + limit: number, + isBucketSort?: boolean +): PaginationInputPaginated => { + const cursorStart = activePage * limit; + return { + activePage, + cursorStart, + fakePossibleCount: 4 <= activePage && activePage > 0 ? limit * (activePage + 2) : limit * 5, + querySize: isBucketSort ? limit : limit + cursorStart, + }; +}; + +export const getInspectResponse = ( + response: StrategyResponseType, + prevResponse: InspectResponse +): InspectResponse => ({ + dsl: response?.inspect?.dsl ?? prevResponse?.dsl ?? [], + response: + response != null ? [JSON.stringify(response.rawResponse, null, 2)] : prevResponse?.response, +}); diff --git a/x-pack/plugins/osquery/public/action_results/translations.ts b/x-pack/plugins/osquery/public/action_results/translations.ts new file mode 100644 index 0000000000000..54c8ecebc60c0 --- /dev/null +++ b/x-pack/plugins/osquery/public/action_results/translations.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ERROR_ALL_RESULTS = i18n.translate('xpack.osquery.results.errorSearchDescription', { + defaultMessage: `An error has occurred on all results search`, +}); + +export const FAIL_ALL_RESULTS = i18n.translate('xpack.osquery.results.failSearchDescription', { + defaultMessage: `Failed to fetch results`, +}); diff --git a/x-pack/plugins/osquery/public/action_results/use_action_results.ts b/x-pack/plugins/osquery/public/action_results/use_action_results.ts new file mode 100644 index 0000000000000..2c54606bf3fbb --- /dev/null +++ b/x-pack/plugins/osquery/public/action_results/use_action_results.ts @@ -0,0 +1,164 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import deepEqual from 'fast-deep-equal'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { createFilter } from '../common/helpers'; +import { useKibana } from '../common/lib/kibana'; +import { + ResultEdges, + PageInfoPaginated, + DocValueFields, + OsqueryQueries, + ResultsRequestOptions, + ResultsStrategyResponse, + Direction, +} from '../../common/search_strategy'; +import { ESTermQuery } from '../../common/typed_json'; + +import * as i18n from './translations'; +import { isCompleteResponse, isErrorResponse } from '../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../src/plugins/kibana_utils/common'; +import { generateTablePaginationOptions, getInspectResponse, InspectResponse } from './helpers'; + +const ID = 'resultsAllQuery'; + +export interface ResultsArgs { + results: ResultEdges; + id: string; + inspect: InspectResponse; + isInspected: boolean; + pageInfo: PageInfoPaginated; + totalCount: number; +} + +interface UseAllResults { + actionId: string; + activePage: number; + direction: Direction; + limit: number; + sortField: string; + docValueFields?: DocValueFields[]; + filterQuery?: ESTermQuery | string; + skip?: boolean; +} + +export const useAllResults = ({ + actionId, + activePage, + direction, + limit, + sortField, + docValueFields, + filterQuery, + skip = false, +}: UseAllResults): [boolean, ResultsArgs] => { + const { data, notifications } = useKibana().services; + + const abortCtrl = useRef(new AbortController()); + const [loading, setLoading] = useState(false); + const [resultsRequest, setHostRequest] = useState(null); + + const [resultsResponse, setResultsResponse] = useState({ + results: [], + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + pageInfo: { + activePage: 0, + fakeTotalCount: 0, + showMorePagesIndicator: false, + }, + totalCount: -1, + }); + + const resultsSearch = useCallback( + (request: ResultsRequestOptions | null) => { + if (request == null || skip) { + return; + } + + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + + const searchSubscription$ = data.search + .search(request, { + strategy: 'osquerySearchStrategy', + abortSignal: abortCtrl.current.signal, + }) + .subscribe({ + next: (response) => { + if (isCompleteResponse(response)) { + if (!didCancel) { + setLoading(false); + setResultsResponse((prevResponse) => ({ + ...prevResponse, + results: response.edges, + inspect: getInspectResponse(response, prevResponse.inspect), + pageInfo: response.pageInfo, + totalCount: response.totalCount, + })); + } + searchSubscription$.unsubscribe(); + } else if (isErrorResponse(response)) { + if (!didCancel) { + setLoading(false); + } + // TODO: Make response error status clearer + notifications.toasts.addWarning(i18n.ERROR_ALL_RESULTS); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ title: i18n.FAIL_ALL_RESULTS, text: msg.message }); + } + }, + }); + }; + abortCtrl.current.abort(); + asyncSearch(); + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts, skip] + ); + + useEffect(() => { + setHostRequest((prevRequest) => { + const myRequest = { + ...(prevRequest ?? {}), + actionId, + docValueFields: docValueFields ?? [], + factoryQueryType: OsqueryQueries.actionResults, + filterQuery: createFilter(filterQuery), + pagination: generateTablePaginationOptions(activePage, limit), + sort: { + direction, + field: sortField, + }, + }; + if (!deepEqual(prevRequest, myRequest)) { + return myRequest; + } + return prevRequest; + }); + }, [actionId, activePage, direction, docValueFields, filterQuery, limit, sortField]); + + useEffect(() => { + resultsSearch(resultsRequest); + }, [resultsRequest, resultsSearch]); + + return [loading, resultsResponse]; +}; diff --git a/x-pack/plugins/osquery/public/actions/actions_table.tsx b/x-pack/plugins/osquery/public/actions/actions_table.tsx new file mode 100644 index 0000000000000..917e915d9d9dc --- /dev/null +++ b/x-pack/plugins/osquery/public/actions/actions_table.tsx @@ -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 { isEmpty, isEqual, keys, map } from 'lodash/fp'; +import { EuiDataGrid, EuiDataGridProps, EuiDataGridColumn, EuiDataGridSorting } from '@elastic/eui'; +import React, { createContext, useEffect, useState, useCallback, useContext, useMemo } from 'react'; + +import { useAllActions } from './use_all_actions'; +import { ActionEdges, Direction } from '../../common/search_strategy'; + +const DataContext = createContext([]); + +const ActionsTableComponent = () => { + const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 50 }); + const onChangeItemsPerPage = useCallback( + (pageSize) => + setPagination((currentPagination) => ({ + ...currentPagination, + pageSize, + pageIndex: 0, + })), + [setPagination] + ); + const onChangePage = useCallback( + (pageIndex) => setPagination((currentPagination) => ({ ...currentPagination, pageIndex })), + [setPagination] + ); + + const [columns, setColumns] = useState([]); + + // ** Sorting config + const [sortingColumns, setSortingColumns] = useState([]); + + const [, { actions, totalCount }] = useAllActions({ + activePage: pagination.pageIndex, + limit: pagination.pageSize, + direction: Direction.asc, + sortField: '@timestamp', + }); + + // Column visibility + const [visibleColumns, setVisibleColumns] = useState([]); // initialize to the full set of columns + + const columnVisibility = useMemo(() => ({ visibleColumns, setVisibleColumns }), [ + visibleColumns, + setVisibleColumns, + ]); + + const renderCellValue: EuiDataGridProps['renderCellValue'] = useMemo(() => { + return ({ rowIndex, columnId, setCellProps }) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const data = useContext(DataContext); + const value = data[rowIndex].fields[columnId]; + + return !isEmpty(value) ? value : '-'; + }; + }, []); + + const tableSorting: EuiDataGridSorting = useMemo( + () => ({ columns: sortingColumns, onSort: setSortingColumns }), + [setSortingColumns, sortingColumns] + ); + + const tablePagination = useMemo( + () => ({ + ...pagination, + pageSizeOptions: [10, 50, 100], + onChangeItemsPerPage, + onChangePage, + }), + [onChangeItemsPerPage, onChangePage, pagination] + ); + + useEffect(() => { + const newColumns = keys(actions[0]?.fields) + .sort() + .map((fieldName) => ({ + id: fieldName, + displayAsText: fieldName.split('.')[1], + defaultSortDirection: Direction.asc, + })); + + if (!isEqual(columns, newColumns)) { + setColumns(newColumns); + setVisibleColumns(map('id', newColumns)); + } + }, [columns, actions]); + + return ( + + + + ); +}; + +export const ActionsTable = React.memo(ActionsTableComponent); diff --git a/x-pack/plugins/osquery/public/actions/helpers.ts b/x-pack/plugins/osquery/public/actions/helpers.ts new file mode 100644 index 0000000000000..9f908e16c2eb2 --- /dev/null +++ b/x-pack/plugins/osquery/public/actions/helpers.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 { + PaginationInputPaginated, + FactoryQueryTypes, + StrategyResponseType, + Inspect, +} from '../../common/search_strategy'; + +export type InspectResponse = Inspect & { response: string[] }; + +export const generateTablePaginationOptions = ( + activePage: number, + limit: number, + isBucketSort?: boolean +): PaginationInputPaginated => { + const cursorStart = activePage * limit; + return { + activePage, + cursorStart, + fakePossibleCount: 4 <= activePage && activePage > 0 ? limit * (activePage + 2) : limit * 5, + querySize: isBucketSort ? limit : limit + cursorStart, + }; +}; + +export const getInspectResponse = ( + response: StrategyResponseType, + prevResponse: InspectResponse +): InspectResponse => ({ + dsl: response?.inspect?.dsl ?? prevResponse?.dsl ?? [], + response: + response != null ? [JSON.stringify(response.rawResponse, null, 2)] : prevResponse?.response, +}); diff --git a/x-pack/plugins/osquery/public/actions/translations.ts b/x-pack/plugins/osquery/public/actions/translations.ts new file mode 100644 index 0000000000000..3bf2d81e5e092 --- /dev/null +++ b/x-pack/plugins/osquery/public/actions/translations.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ERROR_ALL_ACTIONS = i18n.translate('xpack.osquery.actions.errorSearchDescription', { + defaultMessage: `An error has occurred on all actions search`, +}); + +export const FAIL_ALL_ACTIONS = i18n.translate('xpack.osquery.actions.failSearchDescription', { + defaultMessage: `Failed to fetch actions`, +}); + +export const ERROR_ACTION_DETAILS = i18n.translate( + 'xpack.osquery.actionDetails.errorSearchDescription', + { + defaultMessage: `An error has occurred on action details search`, + } +); + +export const FAIL_ACTION_DETAILS = i18n.translate( + 'xpack.osquery.actionDetails.failSearchDescription', + { + defaultMessage: `Failed to fetch action details`, + } +); + +export const ERROR_ACTION_RESULTS = i18n.translate( + 'xpack.osquery.actionResults.errorSearchDescription', + { + defaultMessage: `An error has occurred on action results search`, + } +); + +export const FAIL_ACTION_RESULTS = i18n.translate( + 'xpack.osquery.actionResults.failSearchDescription', + { + defaultMessage: `Failed to fetch action results`, + } +); diff --git a/x-pack/plugins/osquery/public/actions/use_action_details.ts b/x-pack/plugins/osquery/public/actions/use_action_details.ts new file mode 100644 index 0000000000000..3112d7cbf221e --- /dev/null +++ b/x-pack/plugins/osquery/public/actions/use_action_details.ts @@ -0,0 +1,141 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import deepEqual from 'fast-deep-equal'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { createFilter } from '../common/helpers'; +import { useKibana } from '../common/lib/kibana'; +import { + DocValueFields, + OsqueryQueries, + ActionDetailsRequestOptions, + ActionDetailsStrategyResponse, +} from '../../common/search_strategy'; +import { ESTermQuery } from '../../common/typed_json'; + +import * as i18n from './translations'; +import { isCompleteResponse, isErrorResponse } from '../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../src/plugins/kibana_utils/common'; +import { getInspectResponse, InspectResponse } from './helpers'; + +const ID = 'actionDetailsQuery'; + +export interface ActionDetailsArgs { + actionDetails: Record; + id: string; + inspect: InspectResponse; + isInspected: boolean; +} + +interface UseActionDetails { + actionId: string; + docValueFields?: DocValueFields[]; + filterQuery?: ESTermQuery | string; + skip?: boolean; +} + +export const useActionDetails = ({ + actionId, + docValueFields, + filterQuery, + skip = false, +}: UseActionDetails): [boolean, ActionDetailsArgs] => { + const { data, notifications } = useKibana().services; + + const abortCtrl = useRef(new AbortController()); + const [loading, setLoading] = useState(false); + const [actionDetailsRequest, setHostRequest] = useState(null); + + const [actionDetailsResponse, setActionDetailsResponse] = useState({ + actionDetails: {}, + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + }); + + const actionDetailsSearch = useCallback( + (request: ActionDetailsRequestOptions | null) => { + if (request == null || skip) { + return; + } + + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + + const searchSubscription$ = data.search + .search(request, { + strategy: 'osquerySearchStrategy', + abortSignal: abortCtrl.current.signal, + }) + .subscribe({ + next: (response) => { + if (isCompleteResponse(response)) { + if (!didCancel) { + setLoading(false); + setActionDetailsResponse((prevResponse) => ({ + ...prevResponse, + actionDetails: response.actionDetails, + inspect: getInspectResponse(response, prevResponse.inspect), + })); + } + searchSubscription$.unsubscribe(); + } else if (isErrorResponse(response)) { + if (!didCancel) { + setLoading(false); + } + // TODO: Make response error status clearer + notifications.toasts.addWarning(i18n.ERROR_ACTION_DETAILS); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ + title: i18n.FAIL_ACTION_DETAILS, + text: msg.message, + }); + } + }, + }); + }; + abortCtrl.current.abort(); + asyncSearch(); + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts, skip] + ); + + useEffect(() => { + setHostRequest((prevRequest) => { + const myRequest = { + ...(prevRequest ?? {}), + actionId, + docValueFields: docValueFields ?? [], + factoryQueryType: OsqueryQueries.actionDetails, + filterQuery: createFilter(filterQuery), + }; + if (!deepEqual(prevRequest, myRequest)) { + return myRequest; + } + return prevRequest; + }); + }, [actionId, docValueFields, filterQuery]); + + useEffect(() => { + actionDetailsSearch(actionDetailsRequest); + }, [actionDetailsRequest, actionDetailsSearch]); + + return [loading, actionDetailsResponse]; +}; diff --git a/x-pack/plugins/osquery/public/actions/use_all_actions.ts b/x-pack/plugins/osquery/public/actions/use_all_actions.ts new file mode 100644 index 0000000000000..192f5b1eb410c --- /dev/null +++ b/x-pack/plugins/osquery/public/actions/use_all_actions.ts @@ -0,0 +1,161 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import deepEqual from 'fast-deep-equal'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { createFilter } from '../common/helpers'; +import { useKibana } from '../common/lib/kibana'; +import { + ActionEdges, + PageInfoPaginated, + DocValueFields, + OsqueryQueries, + ActionsRequestOptions, + ActionsStrategyResponse, + Direction, +} from '../../common/search_strategy'; +import { ESTermQuery } from '../../common/typed_json'; + +import * as i18n from './translations'; +import { isCompleteResponse, isErrorResponse } from '../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../src/plugins/kibana_utils/common'; +import { generateTablePaginationOptions, getInspectResponse, InspectResponse } from './helpers'; + +const ID = 'actionsAllQuery'; + +export interface ActionsArgs { + actions: ActionEdges; + id: string; + inspect: InspectResponse; + isInspected: boolean; + pageInfo: PageInfoPaginated; + totalCount: number; +} + +interface UseAllActions { + activePage: number; + direction: Direction; + limit: number; + sortField: string; + docValueFields?: DocValueFields[]; + filterQuery?: ESTermQuery | string; + skip?: boolean; +} + +export const useAllActions = ({ + activePage, + direction, + limit, + sortField, + docValueFields, + filterQuery, + skip = false, +}: UseAllActions): [boolean, ActionsArgs] => { + const { data, notifications } = useKibana().services; + + const abortCtrl = useRef(new AbortController()); + const [loading, setLoading] = useState(false); + const [actionsRequest, setHostRequest] = useState(null); + + const [actionsResponse, setActionsResponse] = useState({ + actions: [], + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + pageInfo: { + activePage: 0, + fakeTotalCount: 0, + showMorePagesIndicator: false, + }, + totalCount: -1, + }); + + const actionsSearch = useCallback( + (request: ActionsRequestOptions | null) => { + if (request == null || skip) { + return; + } + + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + + const searchSubscription$ = data.search + .search(request, { + strategy: 'osquerySearchStrategy', + abortSignal: abortCtrl.current.signal, + }) + .subscribe({ + next: (response) => { + if (isCompleteResponse(response)) { + if (!didCancel) { + setLoading(false); + setActionsResponse((prevResponse) => ({ + ...prevResponse, + actions: response.edges, + inspect: getInspectResponse(response, prevResponse.inspect), + pageInfo: response.pageInfo, + totalCount: response.totalCount, + })); + } + searchSubscription$.unsubscribe(); + } else if (isErrorResponse(response)) { + if (!didCancel) { + setLoading(false); + } + // TODO: Make response error status clearer + notifications.toasts.addWarning(i18n.ERROR_ALL_ACTIONS); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ title: i18n.FAIL_ALL_ACTIONS, text: msg.message }); + } + }, + }); + }; + abortCtrl.current.abort(); + asyncSearch(); + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts, skip] + ); + + useEffect(() => { + setHostRequest((prevRequest) => { + const myRequest = { + ...(prevRequest ?? {}), + docValueFields: docValueFields ?? [], + factoryQueryType: OsqueryQueries.actions, + filterQuery: createFilter(filterQuery), + pagination: generateTablePaginationOptions(activePage, limit), + sort: { + direction, + field: sortField, + }, + }; + if (!deepEqual(prevRequest, myRequest)) { + return myRequest; + } + return prevRequest; + }); + }, [activePage, direction, docValueFields, filterQuery, limit, sortField]); + + useEffect(() => { + actionsSearch(actionsRequest); + }, [actionsRequest, actionsSearch]); + + return [loading, actionsResponse]; +}; diff --git a/x-pack/plugins/osquery/public/agents/agents_table.tsx b/x-pack/plugins/osquery/public/agents/agents_table.tsx new file mode 100644 index 0000000000000..1c0083b8252e8 --- /dev/null +++ b/x-pack/plugins/osquery/public/agents/agents_table.tsx @@ -0,0 +1,149 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { find } from 'lodash/fp'; +import React, { useCallback, useEffect, useMemo, useState, useRef } from 'react'; +import { + EuiBasicTable, + EuiBasicTableColumn, + EuiBasicTableProps, + EuiTableSelectionType, + EuiHealth, +} from '@elastic/eui'; + +import { useAllAgents } from './use_all_agents'; +import { Direction } from '../../common/search_strategy'; +import { Agent } from '../../common/shared_imports'; + +interface AgentsTableProps { + selectedAgents: string[]; + onChange: (payload: string[]) => void; +} + +const AgentsTableComponent: React.FC = ({ selectedAgents, onChange }) => { + const [pageIndex, setPageIndex] = useState(0); + const [pageSize, setPageSize] = useState(5); + const [sortField, setSortField] = useState('id'); + const [sortDirection, setSortDirection] = useState(Direction.asc); + const [selectedItems, setSelectedItems] = useState([]); + const tableRef = useRef>(null); + + const onTableChange: EuiBasicTableProps['onChange'] = useCallback( + ({ page = {}, sort = {} }) => { + const { index: newPageIndex, size: newPageSize } = page; + + const { field: newSortField, direction: newSortDirection } = sort; + + setPageIndex(newPageIndex); + setPageSize(newPageSize); + setSortField(newSortField); + setSortDirection(newSortDirection); + }, + [] + ); + + const onSelectionChange: EuiTableSelectionType<{}>['onSelectionChange'] = useCallback( + (newSelectedItems) => { + setSelectedItems(newSelectedItems); + // @ts-expect-error + onChange(newSelectedItems.map((item) => item._id)); + }, + [onChange] + ); + + const renderStatus = (online: string) => { + const color = online ? 'success' : 'danger'; + const label = online ? 'Online' : 'Offline'; + return {label}; + }; + + const [, { agents, totalCount }] = useAllAgents({ + activePage: pageIndex, + limit: pageSize, + direction: sortDirection, + sortField, + }); + + const columns: Array> = useMemo( + () => [ + { + field: 'local_metadata.elastic.agent.id', + name: 'id', + sortable: true, + truncateText: true, + }, + { + field: 'local_metadata.host.name', + name: 'hostname', + truncateText: true, + }, + + { + field: 'active', + name: 'Online', + dataType: 'boolean', + render: (active: string) => renderStatus(active), + }, + ], + [] + ); + + const pagination = useMemo( + () => ({ + pageIndex, + pageSize, + totalItemCount: totalCount, + pageSizeOptions: [3, 5, 8], + }), + [pageIndex, pageSize, totalCount] + ); + + const sorting = useMemo( + () => ({ + sort: { + field: sortField, + direction: sortDirection, + }, + }), + [sortDirection, sortField] + ); + + const selection: EuiBasicTableProps['selection'] = useMemo( + () => ({ + selectable: (agent: Agent) => agent.active, + selectableMessage: (selectable: boolean) => (!selectable ? 'User is currently offline' : ''), + onSelectionChange, + initialSelected: selectedItems, + }), + [onSelectionChange, selectedItems] + ); + + useEffect(() => { + if (selectedAgents?.length && agents.length && selectedItems.length !== selectedAgents.length) { + tableRef?.current?.setSelection( + // @ts-expect-error + selectedAgents.map((agentId) => find({ _id: agentId }, agents)) + ); + } + }, [selectedAgents, agents, selectedItems.length]); + + return ( + + ref={tableRef} + items={agents} + itemId="_id" + columns={columns} + pagination={pagination} + sorting={sorting} + isSelectable={true} + selection={selection} + onChange={onTableChange} + rowHeader="firstName" + /> + ); +}; + +export const AgentsTable = React.memo(AgentsTableComponent); diff --git a/x-pack/plugins/osquery/public/agents/helpers.ts b/x-pack/plugins/osquery/public/agents/helpers.ts new file mode 100644 index 0000000000000..9f908e16c2eb2 --- /dev/null +++ b/x-pack/plugins/osquery/public/agents/helpers.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 { + PaginationInputPaginated, + FactoryQueryTypes, + StrategyResponseType, + Inspect, +} from '../../common/search_strategy'; + +export type InspectResponse = Inspect & { response: string[] }; + +export const generateTablePaginationOptions = ( + activePage: number, + limit: number, + isBucketSort?: boolean +): PaginationInputPaginated => { + const cursorStart = activePage * limit; + return { + activePage, + cursorStart, + fakePossibleCount: 4 <= activePage && activePage > 0 ? limit * (activePage + 2) : limit * 5, + querySize: isBucketSort ? limit : limit + cursorStart, + }; +}; + +export const getInspectResponse = ( + response: StrategyResponseType, + prevResponse: InspectResponse +): InspectResponse => ({ + dsl: response?.inspect?.dsl ?? prevResponse?.dsl ?? [], + response: + response != null ? [JSON.stringify(response.rawResponse, null, 2)] : prevResponse?.response, +}); diff --git a/x-pack/plugins/osquery/public/agents/translations.ts b/x-pack/plugins/osquery/public/agents/translations.ts new file mode 100644 index 0000000000000..a95ad5e4ce163 --- /dev/null +++ b/x-pack/plugins/osquery/public/agents/translations.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ERROR_ALL_AGENTS = i18n.translate('xpack.osquery.agents.errorSearchDescription', { + defaultMessage: `An error has occurred on all agents search`, +}); + +export const FAIL_ALL_AGENTS = i18n.translate('xpack.osquery.agents.failSearchDescription', { + defaultMessage: `Failed to fetch agents`, +}); diff --git a/x-pack/plugins/osquery/public/agents/use_all_agents.ts b/x-pack/plugins/osquery/public/agents/use_all_agents.ts new file mode 100644 index 0000000000000..ad1a09486961a --- /dev/null +++ b/x-pack/plugins/osquery/public/agents/use_all_agents.ts @@ -0,0 +1,161 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import deepEqual from 'fast-deep-equal'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { createFilter } from '../common/helpers'; +import { useKibana } from '../common/lib/kibana'; +import { + PageInfoPaginated, + DocValueFields, + OsqueryQueries, + AgentsRequestOptions, + AgentsStrategyResponse, + Direction, +} from '../../common/search_strategy'; +import { ESTermQuery } from '../../common/typed_json'; +import { Agent } from '../../common/shared_imports'; + +import * as i18n from './translations'; +import { isCompleteResponse, isErrorResponse } from '../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../src/plugins/kibana_utils/common'; +import { generateTablePaginationOptions, getInspectResponse, InspectResponse } from './helpers'; + +const ID = 'agentsAllQuery'; + +export interface AgentsArgs { + agents: Agent[]; + id: string; + inspect: InspectResponse; + isInspected: boolean; + pageInfo: PageInfoPaginated; + totalCount: number; +} + +interface UseAllAgents { + activePage: number; + direction: Direction; + limit: number; + sortField: string; + docValueFields?: DocValueFields[]; + filterQuery?: ESTermQuery | string; + skip?: boolean; +} + +export const useAllAgents = ({ + activePage, + direction, + limit, + sortField, + docValueFields, + filterQuery, + skip = false, +}: UseAllAgents): [boolean, AgentsArgs] => { + const { data, notifications } = useKibana().services; + + const abortCtrl = useRef(new AbortController()); + const [loading, setLoading] = useState(false); + const [agentsRequest, setHostRequest] = useState(null); + + const [agentsResponse, setAgentsResponse] = useState({ + agents: [], + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + pageInfo: { + activePage: 0, + fakeTotalCount: 0, + showMorePagesIndicator: false, + }, + totalCount: -1, + }); + + const agentsSearch = useCallback( + (request: AgentsRequestOptions | null) => { + if (request == null || skip) { + return; + } + + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + + const searchSubscription$ = data.search + .search(request, { + strategy: 'osquerySearchStrategy', + abortSignal: abortCtrl.current.signal, + }) + .subscribe({ + next: (response) => { + if (isCompleteResponse(response)) { + if (!didCancel) { + setLoading(false); + setAgentsResponse((prevResponse) => ({ + ...prevResponse, + agents: response.edges, + inspect: getInspectResponse(response, prevResponse.inspect), + pageInfo: response.pageInfo, + totalCount: response.totalCount, + })); + } + searchSubscription$.unsubscribe(); + } else if (isErrorResponse(response)) { + if (!didCancel) { + setLoading(false); + } + // TODO: Make response error status clearer + notifications.toasts.addWarning(i18n.ERROR_ALL_AGENTS); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ title: i18n.FAIL_ALL_AGENTS, text: msg.message }); + } + }, + }); + }; + abortCtrl.current.abort(); + asyncSearch(); + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts, skip] + ); + + useEffect(() => { + setHostRequest((prevRequest) => { + const myRequest = { + ...(prevRequest ?? {}), + docValueFields: docValueFields ?? [], + factoryQueryType: OsqueryQueries.agents, + filterQuery: createFilter(filterQuery), + pagination: generateTablePaginationOptions(activePage, limit), + sort: { + direction, + field: sortField, + }, + }; + if (!deepEqual(prevRequest, myRequest)) { + return myRequest; + } + return prevRequest; + }); + }, [activePage, direction, docValueFields, filterQuery, limit, sortField]); + + useEffect(() => { + agentsSearch(agentsRequest); + }, [agentsRequest, agentsSearch]); + + return [loading, agentsResponse]; +}; diff --git a/x-pack/plugins/osquery/public/application.tsx b/x-pack/plugins/osquery/public/application.tsx new file mode 100644 index 0000000000000..1a5c826df3310 --- /dev/null +++ b/x-pack/plugins/osquery/public/application.tsx @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiErrorBoundary } from '@elastic/eui'; +import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; +import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import React, { useMemo } from 'react'; +import ReactDOM from 'react-dom'; +import { Router } from 'react-router-dom'; +import { I18nProvider } from '@kbn/i18n/react'; +import { ThemeProvider } from 'styled-components'; + +import { useUiSetting$ } from '../../../../src/plugins/kibana_react/public'; +import { Storage } from '../../../../src/plugins/kibana_utils/public'; +import { AppMountParameters, CoreStart } from '../../../../src/core/public'; +import { AppPluginStartDependencies } from './types'; +import { OsqueryApp } from './components/app'; +import { DEFAULT_DARK_MODE, PLUGIN_NAME } from '../common'; +import { KibanaContextProvider } from './common/lib/kibana'; + +const OsqueryAppContext = () => { + const [darkMode] = useUiSetting$(DEFAULT_DARK_MODE); + const theme = useMemo( + () => ({ + eui: darkMode ? euiDarkVars : euiLightVars, + darkMode, + }), + [darkMode] + ); + + return ( + + + + ); +}; + +export const renderApp = ( + core: CoreStart, + services: AppPluginStartDependencies, + { element, history }: AppMountParameters, + storage: Storage, + kibanaVersion: string +) => { + ReactDOM.render( + + + + + + + + + , + element + ); + + return () => ReactDOM.unmountComponentAtNode(element); +}; diff --git a/x-pack/plugins/osquery/public/common/helpers.test.ts b/x-pack/plugins/osquery/public/common/helpers.test.ts new file mode 100644 index 0000000000000..5d378d79acc7a --- /dev/null +++ b/x-pack/plugins/osquery/public/common/helpers.test.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ESQuery } from '../../common/typed_json'; + +import { createFilter } from './helpers'; + +describe('Helpers', () => { + describe('#createFilter', () => { + test('if it is a string it returns untouched', () => { + const filter = createFilter('even invalid strings return the same'); + expect(filter).toBe('even invalid strings return the same'); + }); + + test('if it is an ESQuery object it will be returned as a string', () => { + const query: ESQuery = { term: { 'host.id': 'host-value' } }; + const filter = createFilter(query); + expect(filter).toBe(JSON.stringify(query)); + }); + + test('if it is undefined, then undefined is returned', () => { + const filter = createFilter(undefined); + expect(filter).toBe(undefined); + }); + }); +}); diff --git a/x-pack/plugins/osquery/public/common/helpers.ts b/x-pack/plugins/osquery/public/common/helpers.ts new file mode 100644 index 0000000000000..e922e030c9330 --- /dev/null +++ b/x-pack/plugins/osquery/public/common/helpers.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { isString } from 'lodash/fp'; + +import { ESQuery } from '../../common/typed_json'; + +export const createFilter = (filterQuery: ESQuery | string | undefined) => + isString(filterQuery) ? filterQuery : JSON.stringify(filterQuery); diff --git a/x-pack/plugins/osquery/public/common/index.ts b/x-pack/plugins/osquery/public/common/index.ts new file mode 100644 index 0000000000000..d805555791e2a --- /dev/null +++ b/x-pack/plugins/osquery/public/common/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './helpers'; diff --git a/x-pack/plugins/osquery/public/common/lib/kibana/index.ts b/x-pack/plugins/osquery/public/common/lib/kibana/index.ts new file mode 100644 index 0000000000000..b9cb71d4adb47 --- /dev/null +++ b/x-pack/plugins/osquery/public/common/lib/kibana/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './kibana_react'; diff --git a/x-pack/plugins/osquery/public/common/lib/kibana/kibana_react.ts b/x-pack/plugins/osquery/public/common/lib/kibana/kibana_react.ts new file mode 100644 index 0000000000000..b4fb307a62b6c --- /dev/null +++ b/x-pack/plugins/osquery/public/common/lib/kibana/kibana_react.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + KibanaContextProvider, + KibanaReactContextValue, + useKibana, + useUiSetting, + useUiSetting$, + withKibana, +} from '../../../../../../../src/plugins/kibana_react/public'; +import { StartServices } from '../../../types'; + +export type KibanaContext = KibanaReactContextValue; +export interface WithKibanaProps { + kibana: KibanaContext; +} + +const useTypedKibana = () => useKibana(); + +export { + KibanaContextProvider, + useTypedKibana as useKibana, + useUiSetting, + useUiSetting$, + withKibana, +}; diff --git a/x-pack/plugins/osquery/public/components/app.tsx b/x-pack/plugins/osquery/public/components/app.tsx new file mode 100644 index 0000000000000..49ff7e2bfb4da --- /dev/null +++ b/x-pack/plugins/osquery/public/components/app.tsx @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { Switch, Route } from 'react-router-dom'; + +import { + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiPageHeader, + EuiTitle, + EuiSpacer, +} from '@elastic/eui'; + +import { PLUGIN_NAME } from '../../common'; +import { LiveQuery } from '../live_query'; + +export const OsqueryAppComponent = () => { + return ( + + + + +

+ +

+
+
+ + + + + + + + + + + + + +
+
+ ); +}; + +export const OsqueryApp = React.memo(OsqueryAppComponent); diff --git a/x-pack/plugins/osquery/public/editor/index.tsx b/x-pack/plugins/osquery/public/editor/index.tsx new file mode 100644 index 0000000000000..a0e549e77467b --- /dev/null +++ b/x-pack/plugins/osquery/public/editor/index.tsx @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useCallback } from 'react'; +import { EuiCodeEditor } from '@elastic/eui'; +import 'brace/mode/sql'; +import 'brace/theme/tomorrow'; +import 'brace/ext/language_tools'; + +const EDITOR_SET_OPTIONS = { + enableBasicAutocompletion: true, + enableLiveAutocompletion: true, +}; + +const EDITOR_PROPS = { + $blockScrolling: true, +}; + +interface OsqueryEditorProps { + defaultValue: string; + onChange: (newValue: string) => void; +} + +const OsqueryEditorComponent: React.FC = ({ defaultValue, onChange }) => { + const handleChange = useCallback( + (newValue) => { + onChange(newValue); + }, + [onChange] + ); + + return ( + + ); +}; + +export const OsqueryEditor = React.memo(OsqueryEditorComponent); diff --git a/x-pack/plugins/osquery/public/index.ts b/x-pack/plugins/osquery/public/index.ts new file mode 100644 index 0000000000000..32b0a30c24e7a --- /dev/null +++ b/x-pack/plugins/osquery/public/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializerContext } from 'src/core/public'; +import { OsqueryPlugin } from './plugin'; + +// This exports static code and TypeScript types, +// as well as, Kibana Platform `plugin()` initializer. +export function plugin(initializerContext: PluginInitializerContext) { + return new OsqueryPlugin(initializerContext); +} +export { OsqueryPluginSetup, OsqueryPluginStart } from './types'; diff --git a/x-pack/plugins/osquery/public/live_query/edit/index.tsx b/x-pack/plugins/osquery/public/live_query/edit/index.tsx new file mode 100644 index 0000000000000..5626e78069d01 --- /dev/null +++ b/x-pack/plugins/osquery/public/live_query/edit/index.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { isEmpty } from 'lodash/fp'; +import { EuiSpacer } from '@elastic/eui'; +import React, { useCallback } from 'react'; +import { useParams } from 'react-router-dom'; + +import { useActionDetails } from '../../actions/use_action_details'; +import { ResultTabs } from './tabs'; +import { LiveQueryForm } from '../form'; + +const EditLiveQueryPageComponent = () => { + const { actionId } = useParams<{ actionId: string }>(); + const [loading, { actionDetails }] = useActionDetails({ actionId }); + + const handleSubmit = useCallback(() => Promise.resolve(), []); + + if (loading) { + return <>{'Loading...'}; + } + + return ( + <> + {!isEmpty(actionDetails) && ( + + )} + + + + ); +}; + +export const EditLiveQueryPage = React.memo(EditLiveQueryPageComponent); diff --git a/x-pack/plugins/osquery/public/live_query/edit/tabs.tsx b/x-pack/plugins/osquery/public/live_query/edit/tabs.tsx new file mode 100644 index 0000000000000..564b91873e11d --- /dev/null +++ b/x-pack/plugins/osquery/public/live_query/edit/tabs.tsx @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiTabbedContent, EuiSpacer } from '@elastic/eui'; +import React, { useCallback, useMemo } from 'react'; +import { useParams } from 'react-router-dom'; + +import { ResultsTable } from '../../results/results_table'; +import { ActionResultsTable } from '../../action_results/action_results_table'; + +const ResultTabsComponent = () => { + const { actionId } = useParams<{ actionId: string }>(); + const tabs = useMemo( + () => [ + { + id: 'status', + name: 'Status', + content: ( + <> + + + + ), + }, + { + id: 'results', + name: 'Results', + content: ( + <> + + + + ), + }, + ], + [actionId] + ); + + const handleTabClick = useCallback((tab) => { + // eslint-disable-next-line no-console + console.log('clicked tab', tab); + }, []); + + return ( + + ); +}; + +export const ResultTabs = React.memo(ResultTabsComponent); diff --git a/x-pack/plugins/osquery/public/live_query/form/agents_table_field.tsx b/x-pack/plugins/osquery/public/live_query/form/agents_table_field.tsx new file mode 100644 index 0000000000000..a6d7fbd404321 --- /dev/null +++ b/x-pack/plugins/osquery/public/live_query/form/agents_table_field.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useCallback } from 'react'; +import { FieldHook } from '../../shared_imports'; +import { AgentsTable } from '../../agents/agents_table'; + +interface AgentsTableFieldProps { + field: FieldHook; +} + +const AgentsTableFieldComponent: React.FC = ({ field }) => { + const { value, setValue } = field; + const handleChange = useCallback( + (props) => { + if (props !== value) { + return setValue(props); + } + }, + [value, setValue] + ); + + return ; +}; + +export const AgentsTableField = React.memo(AgentsTableFieldComponent); diff --git a/x-pack/plugins/osquery/public/live_query/form/code_editor_field.tsx b/x-pack/plugins/osquery/public/live_query/form/code_editor_field.tsx new file mode 100644 index 0000000000000..458d2263fe9c2 --- /dev/null +++ b/x-pack/plugins/osquery/public/live_query/form/code_editor_field.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useCallback } from 'react'; + +import { OsqueryEditor } from '../../editor'; +import { FieldHook } from '../../shared_imports'; + +interface CodeEditorFieldProps { + field: FieldHook<{ query: string }>; +} + +const CodeEditorFieldComponent: React.FC = ({ field }) => { + const { value, setValue } = field; + const handleChange = useCallback( + (newQuery) => { + setValue({ + ...value, + query: newQuery, + }); + }, + [value, setValue] + ); + + return ; +}; + +export const CodeEditorField = React.memo(CodeEditorFieldComponent); diff --git a/x-pack/plugins/osquery/public/live_query/form/index.tsx b/x-pack/plugins/osquery/public/live_query/form/index.tsx new file mode 100644 index 0000000000000..23aa94b46a569 --- /dev/null +++ b/x-pack/plugins/osquery/public/live_query/form/index.tsx @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiButton, EuiSpacer } from '@elastic/eui'; +import React, { useCallback } from 'react'; + +import { UseField, Form, useForm } from '../../shared_imports'; +import { AgentsTableField } from './agents_table_field'; +import { CodeEditorField } from './code_editor_field'; + +const FORM_ID = 'liveQueryForm'; + +interface LiveQueryFormProps { + actionDetails?: Record; + onSubmit: (payload: Record) => Promise; +} + +const LiveQueryFormComponent: React.FC = ({ actionDetails, onSubmit }) => { + const handleSubmit = useCallback( + (payload) => { + onSubmit(payload); + return Promise.resolve(); + }, + [onSubmit] + ); + + const { form } = useForm({ + id: FORM_ID, + // schema: formSchema, + onSubmit: handleSubmit, + options: { + stripEmptyFields: false, + }, + defaultValue: actionDetails, + deserializer: ({ fields, _source }) => ({ + agents: fields?.agents, + command: _source?.data?.commands[0], + }), + }); + + const { submit } = form; + + return ( +
+ + + + Send query + + ); +}; + +export const LiveQueryForm = React.memo(LiveQueryFormComponent); diff --git a/x-pack/plugins/osquery/public/live_query/form/schema.ts b/x-pack/plugins/osquery/public/live_query/form/schema.ts new file mode 100644 index 0000000000000..e55b3d6cd6a5b --- /dev/null +++ b/x-pack/plugins/osquery/public/live_query/form/schema.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FIELD_TYPES, FormSchema } from '../../shared_imports'; + +export const formSchema: FormSchema = { + agents: { + type: FIELD_TYPES.MULTI_SELECT, + }, + command: { + type: FIELD_TYPES.TEXTAREA, + validations: [], + }, +}; diff --git a/x-pack/plugins/osquery/public/live_query/index.tsx b/x-pack/plugins/osquery/public/live_query/index.tsx new file mode 100644 index 0000000000000..646d2637a4c40 --- /dev/null +++ b/x-pack/plugins/osquery/public/live_query/index.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { Switch, Route, useRouteMatch } from 'react-router-dom'; + +import { QueriesPage } from './queries'; +import { NewLiveQueryPage } from './new'; +import { EditLiveQueryPage } from './edit'; + +const LiveQueryComponent = () => { + const match = useRouteMatch(); + + return ( + + + + + + + + + + + + ); +}; + +export const LiveQuery = React.memo(LiveQueryComponent); diff --git a/x-pack/plugins/osquery/public/live_query/new/index.tsx b/x-pack/plugins/osquery/public/live_query/new/index.tsx new file mode 100644 index 0000000000000..40f934b4690f9 --- /dev/null +++ b/x-pack/plugins/osquery/public/live_query/new/index.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useCallback } from 'react'; +import { useHistory } from 'react-router-dom'; + +import { useKibana } from '../../common/lib/kibana'; +import { LiveQueryForm } from '../form'; + +const NewLiveQueryPageComponent = () => { + const { http } = useKibana().services; + const history = useHistory(); + + const handleSubmit = useCallback( + async (props) => { + const response = await http.post('/api/osquery/queries', { body: JSON.stringify(props) }); + const requestParamsActionId = JSON.parse(response.meta.request.params.body).action_id; + history.push(`/live_query/queries/${requestParamsActionId}`); + }, + [history, http] + ); + + return ; +}; + +export const NewLiveQueryPage = React.memo(NewLiveQueryPageComponent); diff --git a/x-pack/plugins/osquery/public/live_query/queries/index.tsx b/x-pack/plugins/osquery/public/live_query/queries/index.tsx new file mode 100644 index 0000000000000..5600284b8c147 --- /dev/null +++ b/x-pack/plugins/osquery/public/live_query/queries/index.tsx @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiSpacer, EuiTitle } from '@elastic/eui'; +import React from 'react'; + +import { ActionsTable } from '../../actions/actions_table'; + +const QueriesPageComponent = () => { + return ( + <> + +

{'Queries'}

+
+ + + + ); +}; + +export const QueriesPage = React.memo(QueriesPageComponent); diff --git a/x-pack/plugins/osquery/public/plugin.ts b/x-pack/plugins/osquery/public/plugin.ts new file mode 100644 index 0000000000000..41698d3a1740d --- /dev/null +++ b/x-pack/plugins/osquery/public/plugin.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + AppMountParameters, + CoreSetup, + Plugin, + PluginInitializerContext, + CoreStart, +} from 'src/core/public'; +import { Storage } from '../../../../src/plugins/kibana_utils/public'; +import { OsqueryPluginSetup, OsqueryPluginStart, AppPluginStartDependencies } from './types'; +import { PLUGIN_NAME } from '../common'; + +export class OsqueryPlugin implements Plugin { + private kibanaVersion: string; + private storage = new Storage(localStorage); + + constructor(private readonly initializerContext: PluginInitializerContext) { + this.kibanaVersion = this.initializerContext.env.packageInfo.version; + } + + public setup(core: CoreSetup): OsqueryPluginSetup { + const config = this.initializerContext.config.get<{ enabled: boolean }>(); + + if (!config.enabled) { + return {}; + } + + const storage = this.storage; + const kibanaVersion = this.kibanaVersion; + // Register an application into the side navigation menu + core.application.register({ + id: 'osquery', + title: PLUGIN_NAME, + async mount(params: AppMountParameters) { + // Get start services as specified in kibana.json + const [coreStart, depsStart] = await core.getStartServices(); + // Load application bundle + const { renderApp } = await import('./application'); + // Render the application + return renderApp( + coreStart, + depsStart as AppPluginStartDependencies, + params, + storage, + kibanaVersion + ); + }, + }); + + // Return methods that should be available to other plugins + return {}; + } + + public start(core: CoreStart): OsqueryPluginStart { + return {}; + } + + public stop() {} +} diff --git a/x-pack/plugins/osquery/public/results/helpers.ts b/x-pack/plugins/osquery/public/results/helpers.ts new file mode 100644 index 0000000000000..9f908e16c2eb2 --- /dev/null +++ b/x-pack/plugins/osquery/public/results/helpers.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 { + PaginationInputPaginated, + FactoryQueryTypes, + StrategyResponseType, + Inspect, +} from '../../common/search_strategy'; + +export type InspectResponse = Inspect & { response: string[] }; + +export const generateTablePaginationOptions = ( + activePage: number, + limit: number, + isBucketSort?: boolean +): PaginationInputPaginated => { + const cursorStart = activePage * limit; + return { + activePage, + cursorStart, + fakePossibleCount: 4 <= activePage && activePage > 0 ? limit * (activePage + 2) : limit * 5, + querySize: isBucketSort ? limit : limit + cursorStart, + }; +}; + +export const getInspectResponse = ( + response: StrategyResponseType, + prevResponse: InspectResponse +): InspectResponse => ({ + dsl: response?.inspect?.dsl ?? prevResponse?.dsl ?? [], + response: + response != null ? [JSON.stringify(response.rawResponse, null, 2)] : prevResponse?.response, +}); diff --git a/x-pack/plugins/osquery/public/results/results_table.tsx b/x-pack/plugins/osquery/public/results/results_table.tsx new file mode 100644 index 0000000000000..69b350e461a5c --- /dev/null +++ b/x-pack/plugins/osquery/public/results/results_table.tsx @@ -0,0 +1,119 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * 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, isEqual, keys, map } from 'lodash/fp'; +import { EuiDataGrid, EuiDataGridProps, EuiDataGridColumn } from '@elastic/eui'; +import React, { createContext, useEffect, useState, useCallback, useContext, useMemo } from 'react'; + +import { EuiDataGridSorting } from '@elastic/eui'; +import { useAllResults } from './use_all_results'; +import { Direction, ResultEdges } from '../../common/search_strategy'; + +const DataContext = createContext([]); + +interface ResultsTableComponentProps { + actionId: string; +} + +const ResultsTableComponent: React.FC = ({ actionId }) => { + const [pagination, setPagination] = useState({ pageIndex: 0, pageSize: 50 }); + const onChangeItemsPerPage = useCallback( + (pageSize) => + setPagination((currentPagination) => ({ + ...currentPagination, + pageSize, + pageIndex: 0, + })), + [setPagination] + ); + const onChangePage = useCallback( + (pageIndex) => setPagination((currentPagination) => ({ ...currentPagination, pageIndex })), + [setPagination] + ); + + const [columns, setColumns] = useState([]); + + // ** Sorting config + const [sortingColumns, setSortingColumns] = useState([]); + const onSort = useCallback( + (newSortingColumns) => { + setSortingColumns(newSortingColumns); + }, + [setSortingColumns] + ); + + const [, { results, totalCount }] = useAllResults({ + actionId, + activePage: pagination.pageIndex, + limit: pagination.pageSize, + direction: Direction.asc, + sortField: '@timestamp', + }); + + const [visibleColumns, setVisibleColumns] = useState([]); + const columnVisibility = useMemo(() => ({ visibleColumns, setVisibleColumns }), [ + visibleColumns, + setVisibleColumns, + ]); + + const renderCellValue: EuiDataGridProps['renderCellValue'] = useMemo( + () => ({ rowIndex, columnId, setCellProps }) => { + // eslint-disable-next-line react-hooks/rules-of-hooks + const data = useContext(DataContext); + + const value = data[rowIndex].fields[columnId]; + + return !isEmpty(value) ? value : '-'; + }, + [] + ); + + const tableSorting = useMemo(() => ({ columns: sortingColumns, onSort }), [ + onSort, + sortingColumns, + ]); + + const tablePagination = useMemo( + () => ({ + ...pagination, + pageSizeOptions: [10, 50, 100], + onChangeItemsPerPage, + onChangePage, + }), + [onChangeItemsPerPage, onChangePage, pagination] + ); + + useEffect(() => { + const newColumns: EuiDataGridColumn[] = keys(results[0]?.fields) + .sort() + .map((fieldName) => ({ + id: fieldName, + displayAsText: fieldName.split('.')[1], + defaultSortDirection: 'asc', + })); + + if (!isEqual(columns, newColumns)) { + setColumns(newColumns); + setVisibleColumns(map('id', newColumns)); + } + }, [columns, results]); + + return ( + + + + ); +}; + +export const ResultsTable = React.memo(ResultsTableComponent); diff --git a/x-pack/plugins/osquery/public/results/translations.ts b/x-pack/plugins/osquery/public/results/translations.ts new file mode 100644 index 0000000000000..54c8ecebc60c0 --- /dev/null +++ b/x-pack/plugins/osquery/public/results/translations.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const ERROR_ALL_RESULTS = i18n.translate('xpack.osquery.results.errorSearchDescription', { + defaultMessage: `An error has occurred on all results search`, +}); + +export const FAIL_ALL_RESULTS = i18n.translate('xpack.osquery.results.failSearchDescription', { + defaultMessage: `Failed to fetch results`, +}); diff --git a/x-pack/plugins/osquery/public/results/use_all_results.ts b/x-pack/plugins/osquery/public/results/use_all_results.ts new file mode 100644 index 0000000000000..2fc5f9ae869a7 --- /dev/null +++ b/x-pack/plugins/osquery/public/results/use_all_results.ts @@ -0,0 +1,164 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import deepEqual from 'fast-deep-equal'; +import { useCallback, useEffect, useRef, useState } from 'react'; + +import { createFilter } from '../common/helpers'; +import { useKibana } from '../common/lib/kibana'; +import { + ResultEdges, + PageInfoPaginated, + DocValueFields, + OsqueryQueries, + ResultsRequestOptions, + ResultsStrategyResponse, + Direction, +} from '../../common/search_strategy'; +import { ESTermQuery } from '../../common/typed_json'; + +import * as i18n from './translations'; +import { isCompleteResponse, isErrorResponse } from '../../../../../src/plugins/data/common'; +import { AbortError } from '../../../../../src/plugins/kibana_utils/common'; +import { generateTablePaginationOptions, getInspectResponse, InspectResponse } from './helpers'; + +const ID = 'resultsAllQuery'; + +export interface ResultsArgs { + results: ResultEdges; + id: string; + inspect: InspectResponse; + isInspected: boolean; + pageInfo: PageInfoPaginated; + totalCount: number; +} + +interface UseAllResults { + actionId: string; + activePage: number; + direction: Direction; + limit: number; + sortField: string; + docValueFields?: DocValueFields[]; + filterQuery?: ESTermQuery | string; + skip?: boolean; +} + +export const useAllResults = ({ + actionId, + activePage, + direction, + limit, + sortField, + docValueFields, + filterQuery, + skip = false, +}: UseAllResults): [boolean, ResultsArgs] => { + const { data, notifications } = useKibana().services; + + const abortCtrl = useRef(new AbortController()); + const [loading, setLoading] = useState(false); + const [resultsRequest, setHostRequest] = useState(null); + + const [resultsResponse, setResultsResponse] = useState({ + results: [], + id: ID, + inspect: { + dsl: [], + response: [], + }, + isInspected: false, + pageInfo: { + activePage: 0, + fakeTotalCount: 0, + showMorePagesIndicator: false, + }, + totalCount: -1, + }); + + const resultsSearch = useCallback( + (request: ResultsRequestOptions | null) => { + if (request == null || skip) { + return; + } + + let didCancel = false; + const asyncSearch = async () => { + abortCtrl.current = new AbortController(); + setLoading(true); + + const searchSubscription$ = data.search + .search(request, { + strategy: 'osquerySearchStrategy', + abortSignal: abortCtrl.current.signal, + }) + .subscribe({ + next: (response) => { + if (isCompleteResponse(response)) { + if (!didCancel) { + setLoading(false); + setResultsResponse((prevResponse) => ({ + ...prevResponse, + results: response.edges, + inspect: getInspectResponse(response, prevResponse.inspect), + pageInfo: response.pageInfo, + totalCount: response.totalCount, + })); + } + searchSubscription$.unsubscribe(); + } else if (isErrorResponse(response)) { + if (!didCancel) { + setLoading(false); + } + // TODO: Make response error status clearer + notifications.toasts.addWarning(i18n.ERROR_ALL_RESULTS); + searchSubscription$.unsubscribe(); + } + }, + error: (msg) => { + if (!(msg instanceof AbortError)) { + notifications.toasts.addDanger({ title: i18n.FAIL_ALL_RESULTS, text: msg.message }); + } + }, + }); + }; + abortCtrl.current.abort(); + asyncSearch(); + return () => { + didCancel = true; + abortCtrl.current.abort(); + }; + }, + [data.search, notifications.toasts, skip] + ); + + useEffect(() => { + setHostRequest((prevRequest) => { + const myRequest = { + ...(prevRequest ?? {}), + actionId, + docValueFields: docValueFields ?? [], + factoryQueryType: OsqueryQueries.results, + filterQuery: createFilter(filterQuery), + pagination: generateTablePaginationOptions(activePage, limit), + sort: { + direction, + field: sortField, + }, + }; + if (!deepEqual(prevRequest, myRequest)) { + return myRequest; + } + return prevRequest; + }); + }, [actionId, activePage, direction, docValueFields, filterQuery, limit, sortField]); + + useEffect(() => { + resultsSearch(resultsRequest); + }, [resultsRequest, resultsSearch]); + + return [loading, resultsResponse]; +}; diff --git a/x-pack/plugins/osquery/public/shared_imports.ts b/x-pack/plugins/osquery/public/shared_imports.ts new file mode 100644 index 0000000000000..5f7503a00702c --- /dev/null +++ b/x-pack/plugins/osquery/public/shared_imports.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { + getUseField, + getFieldValidityAndErrorMessage, + FieldHook, + FieldValidateResponse, + FIELD_TYPES, + Form, + FormData, + FormDataProvider, + FormHook, + FormSchema, + UseField, + UseMultiFields, + useForm, + useFormContext, + useFormData, + ValidationError, + ValidationFunc, + VALIDATION_TYPES, +} from '../../../../src/plugins/es_ui_shared/static/forms/hook_form_lib'; +export { Field, SelectField } from '../../../../src/plugins/es_ui_shared/static/forms/components'; +export { fieldValidators } from '../../../../src/plugins/es_ui_shared/static/forms/helpers'; +export { ERROR_CODE } from '../../../../src/plugins/es_ui_shared/static/forms/helpers/field_validators/types'; diff --git a/x-pack/plugins/osquery/public/types.ts b/x-pack/plugins/osquery/public/types.ts new file mode 100644 index 0000000000000..faaccfc29d5f1 --- /dev/null +++ b/x-pack/plugins/osquery/public/types.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { DataPublicPluginStart } from '../../../../src/plugins/data/public'; +import { FleetStart } from '../../fleet/public'; +import { CoreStart } from '../../../../src/core/public'; +import { NavigationPublicPluginStart } from '../../../../src/plugins/navigation/public'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface OsqueryPluginSetup {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface OsqueryPluginStart {} + +export interface AppPluginStartDependencies { + navigation: NavigationPublicPluginStart; +} + +export interface StartPlugins { + data: DataPublicPluginStart; + fleet?: FleetStart; +} + +export type StartServices = CoreStart & StartPlugins; diff --git a/x-pack/plugins/osquery/server/config.ts b/x-pack/plugins/osquery/server/config.ts new file mode 100644 index 0000000000000..633a95b8f91a7 --- /dev/null +++ b/x-pack/plugins/osquery/server/config.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 { TypeOf, schema } from '@kbn/config-schema'; + +export const ConfigSchema = schema.object({ + enabled: schema.boolean({ defaultValue: false }), +}); + +export type ConfigType = TypeOf; diff --git a/x-pack/plugins/osquery/server/create_config.ts b/x-pack/plugins/osquery/server/create_config.ts new file mode 100644 index 0000000000000..e46c71798eb9f --- /dev/null +++ b/x-pack/plugins/osquery/server/create_config.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { map } from 'rxjs/operators'; +import { PluginInitializerContext } from 'kibana/server'; +import { Observable } from 'rxjs'; + +import { ConfigType } from './config'; + +export const createConfig$ = ( + context: PluginInitializerContext +): Observable> => { + return context.config.create().pipe(map((config) => config)); +}; diff --git a/x-pack/plugins/osquery/server/index.ts b/x-pack/plugins/osquery/server/index.ts new file mode 100644 index 0000000000000..c74ef6c95a2e7 --- /dev/null +++ b/x-pack/plugins/osquery/server/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializerContext } from '../../../../src/core/server'; +import { OsqueryPlugin } from './plugin'; +import { ConfigSchema } from './config'; + +export const config = { + schema: ConfigSchema, + exposeToBrowser: { + enabled: true, + }, +}; +export function plugin(initializerContext: PluginInitializerContext) { + return new OsqueryPlugin(initializerContext); +} + +export { OsqueryPluginSetup, OsqueryPluginStart } from './types'; diff --git a/x-pack/plugins/osquery/server/plugin.ts b/x-pack/plugins/osquery/server/plugin.ts new file mode 100644 index 0000000000000..3e59faa55d057 --- /dev/null +++ b/x-pack/plugins/osquery/server/plugin.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { first } from 'rxjs/operators'; +import { + PluginInitializerContext, + CoreSetup, + CoreStart, + Plugin, + Logger, +} from '../../../../src/core/server'; + +import { createConfig$ } from './create_config'; +import { OsqueryPluginSetup, OsqueryPluginStart, SetupPlugins, StartPlugins } from './types'; +import { defineRoutes } from './routes'; +import { osquerySearchStrategyProvider } from './search_strategy/osquery'; + +export class OsqueryPlugin implements Plugin { + private readonly logger: Logger; + + constructor(private readonly initializerContext: PluginInitializerContext) { + this.logger = this.initializerContext.logger.get(); + } + + public async setup(core: CoreSetup, plugins: SetupPlugins) { + this.logger.debug('osquery: Setup'); + const config = await createConfig$(this.initializerContext).pipe(first()).toPromise(); + + if (!config.enabled) { + return {}; + } + + const router = core.http.createRouter(); + + // Register server side APIs + defineRoutes(router); + + core.getStartServices().then(([_, depsStart]) => { + const osquerySearchStrategy = osquerySearchStrategyProvider(depsStart.data); + + plugins.data.search.registerSearchStrategy('osquerySearchStrategy', osquerySearchStrategy); + }); + + return {}; + } + + public start(core: CoreStart) { + this.logger.debug('osquery: Started'); + return {}; + } + + public stop() {} +} diff --git a/x-pack/plugins/osquery/server/routes/index.ts b/x-pack/plugins/osquery/server/routes/index.ts new file mode 100644 index 0000000000000..f975865950a4d --- /dev/null +++ b/x-pack/plugins/osquery/server/routes/index.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import uuid from 'uuid'; +import { schema } from '@kbn/config-schema'; +import moment from 'moment'; + +import { IRouter } from '../../../../../src/core/server'; + +export function defineRoutes(router: IRouter) { + router.post( + { + path: '/api/osquery/queries', + validate: { + params: schema.object({}, { unknowns: 'allow' }), + body: schema.object({}, { unknowns: 'allow' }), + }, + }, + async (context, request, response) => { + const esClient = context.core.elasticsearch.client.asInternalUser; + const query = await esClient.index<{}, {}>({ + index: '.fleet-actions-new', + body: { + action_id: uuid.v4(), + '@timestamp': moment().toISOString(), + expiration: moment().add(2, 'days').toISOString(), + type: 'APP_ACTION', + input_id: 'osquery', + // @ts-expect-error + agents: request.body.agents, + data: { + commands: [ + { + id: uuid.v4(), + // @ts-expect-error + query: request.body.command.query, + }, + ], + }, + }, + }); + return response.ok({ + body: query, + }); + } + ); +} diff --git a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/all/index.ts b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/all/index.ts new file mode 100644 index 0000000000000..75cdb67deed4d --- /dev/null +++ b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/all/index.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; +import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants'; +import { + ActionsStrategyResponse, + ActionsRequestOptions, + OsqueryQueries, +} from '../../../../../../common/search_strategy/osquery'; +import { inspectStringifyObject } from '../../../../../../common/utils/build_query'; +import { OsqueryFactory } from '../../types'; +import { buildActionsQuery } from './query.all_actions.dsl'; + +export const allActions: OsqueryFactory = { + buildDsl: (options: ActionsRequestOptions) => { + if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { + throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); + } + return buildActionsQuery(options); + }, + parse: async ( + options: ActionsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const { activePage } = options.pagination; + const inspect = { + dsl: [inspectStringifyObject(buildActionsQuery(options))], + }; + + return { + ...response, + inspect, + edges: response.rawResponse.hits.hits, + totalCount: response.rawResponse.hits.total, + pageInfo: { + activePage: activePage ?? 0, + fakeTotalCount: 0, + showMorePagesIndicator: false, + }, + }; + }, +}; diff --git a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/all/query.all_actions.dsl.ts b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/all/query.all_actions.dsl.ts new file mode 100644 index 0000000000000..29af1df3a9e0c --- /dev/null +++ b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/all/query.all_actions.dsl.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ISearchRequestParams } from '../../../../../../../../../src/plugins/data/common'; +import { AgentsRequestOptions } from '../../../../../../common/search_strategy'; +import { createQueryFilterClauses } from '../../../../../../common/utils/build_query'; + +export const buildActionsQuery = ({ + docValueFields, + filterQuery, + pagination: { activePage, querySize }, + sort, +}: AgentsRequestOptions): ISearchRequestParams => { + const filter = [...createQueryFilterClauses(filterQuery)]; + + const dslQuery = { + allowNoIndices: true, + index: '.fleet-actions', + ignoreUnavailable: true, + body: { + query: { bool: { filter } }, + from: activePage * querySize, + size: querySize, + track_total_hits: true, + fields: ['*'], + }, + }; + + return dslQuery; +}; diff --git a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/details/index.ts b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/details/index.ts new file mode 100644 index 0000000000000..09e317786e20f --- /dev/null +++ b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/details/index.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; +import { + ActionDetailsStrategyResponse, + ActionDetailsRequestOptions, + OsqueryQueries, +} from '../../../../../../common/search_strategy/osquery'; + +import { inspectStringifyObject } from '../../../../../../common/utils/build_query'; +import { OsqueryFactory } from '../../types'; +import { buildActionDetailsQuery } from './query.action_details.dsl'; + +export const actionDetails: OsqueryFactory = { + buildDsl: (options: ActionDetailsRequestOptions) => { + return buildActionDetailsQuery(options); + }, + parse: async ( + options: ActionDetailsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const inspect = { + dsl: [inspectStringifyObject(buildActionDetailsQuery(options))], + }; + + return { + ...response, + inspect, + actionDetails: response.rawResponse.hits.hits[0], + }; + }, +}; diff --git a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/details/query.action_details.dsl.ts b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/details/query.action_details.dsl.ts new file mode 100644 index 0000000000000..f22066134cbca --- /dev/null +++ b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/details/query.action_details.dsl.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 { ISearchRequestParams } from '../../../../../../../../../src/plugins/data/common'; +import { ActionDetailsRequestOptions } from '../../../../../../common/search_strategy'; +import { createQueryFilterClauses } from '../../../../../../common/utils/build_query'; + +export const buildActionDetailsQuery = ({ + actionId, + docValueFields, + filterQuery, +}: ActionDetailsRequestOptions): ISearchRequestParams => { + const filter = [ + ...createQueryFilterClauses(filterQuery), + { + match_phrase: { + action_id: actionId, + }, + }, + ]; + + const dslQuery = { + allowNoIndices: true, + index: '.fleet-actions', + ignoreUnavailable: true, + body: { + query: { bool: { filter } }, + size: 1, + fields: ['*'], + }, + }; + + return dslQuery; +}; diff --git a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/index.ts b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/index.ts new file mode 100644 index 0000000000000..c5a6342c180ed --- /dev/null +++ b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './all'; +export * from './details'; +export * from './results'; diff --git a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/results/index.ts b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/results/index.ts new file mode 100644 index 0000000000000..4a049ca670cc6 --- /dev/null +++ b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/results/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 { IEsSearchResponse } from '../../../../../../../../../src/plugins/data/common'; +import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../../common/constants'; +import { + ActionResultsStrategyResponse, + ActionResultsRequestOptions, + OsqueryQueries, +} from '../../../../../../common/search_strategy/osquery'; + +import { inspectStringifyObject } from '../../../../../../common/utils/build_query'; +import { OsqueryFactory } from '../../types'; +import { buildActionResultsQuery } from './query.action_results.dsl'; + +export const actionResults: OsqueryFactory = { + buildDsl: (options: ActionResultsRequestOptions) => { + if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { + throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); + } + return buildActionResultsQuery(options); + }, + parse: async ( + options: ActionResultsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const { activePage } = options.pagination; + const inspect = { + dsl: [inspectStringifyObject(buildActionResultsQuery(options))], + }; + + return { + ...response, + inspect, + edges: response.rawResponse.hits.hits, + totalCount: response.rawResponse.hits.total, + pageInfo: { + activePage: activePage ?? 0, + fakeTotalCount: 0, + showMorePagesIndicator: false, + }, + }; + }, +}; diff --git a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/results/query.action_results.dsl.ts b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/results/query.action_results.dsl.ts new file mode 100644 index 0000000000000..8b80427d4d0e0 --- /dev/null +++ b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/actions/results/query.action_results.dsl.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ISearchRequestParams } from '../../../../../../../../../src/plugins/data/common'; +import { ActionResultsRequestOptions } from '../../../../../../common/search_strategy'; +import { createQueryFilterClauses } from '../../../../../../common/utils/build_query'; + +export const buildActionResultsQuery = ({ + actionId, + docValueFields, + filterQuery, + pagination: { activePage, querySize }, + sort, +}: ActionResultsRequestOptions): ISearchRequestParams => { + const filter = [ + ...createQueryFilterClauses(filterQuery), + { + match_phrase: { + action_id: actionId, + }, + }, + ]; + + const dslQuery = { + allowNoIndices: true, + index: '.fleet-actions-results', + ignoreUnavailable: true, + body: { + query: { bool: { filter } }, + from: activePage * querySize, + size: querySize, + track_total_hits: true, + fields: ['*'], + }, + }; + + return dslQuery; +}; diff --git a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/agents/index.ts b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/agents/index.ts new file mode 100644 index 0000000000000..615343c738d78 --- /dev/null +++ b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/agents/index.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEsSearchResponse } from '../../../../../../../../src/plugins/data/common'; +import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../common/constants'; +import { + AgentsStrategyResponse, + AgentsRequestOptions, + OsqueryQueries, +} from '../../../../../common/search_strategy/osquery'; + +import { Agent } from '../../../../../common/shared_imports'; +import { inspectStringifyObject } from '../../../../../common/utils/build_query'; +import { OsqueryFactory } from '../types'; +import { buildAgentsQuery } from './query.all_agents.dsl'; + +export const allAgents: OsqueryFactory = { + buildDsl: (options: AgentsRequestOptions) => { + if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { + throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); + } + return buildAgentsQuery(options); + }, + parse: async ( + options: AgentsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const { activePage } = options.pagination; + const inspect = { + dsl: [inspectStringifyObject(buildAgentsQuery(options))], + }; + + return { + ...response, + inspect, + edges: response.rawResponse.hits.hits.map((hit) => ({ _id: hit._id, ...hit._source })), + totalCount: response.rawResponse.hits.total, + pageInfo: { + activePage: activePage ?? 0, + fakeTotalCount: 0, + showMorePagesIndicator: false, + }, + }; + }, +}; diff --git a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/agents/query.all_agents.dsl.ts b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/agents/query.all_agents.dsl.ts new file mode 100644 index 0000000000000..935a6cd7b215e --- /dev/null +++ b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/agents/query.all_agents.dsl.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { isEmpty } from 'lodash/fp'; +import { ISearchRequestParams } from '../../../../../../../../src/plugins/data/common'; +import { AgentsRequestOptions } from '../../../../../common/search_strategy'; +import { createQueryFilterClauses } from '../../../../../common/utils/build_query'; + +export const buildAgentsQuery = ({ + docValueFields, + filterQuery, + pagination: { querySize }, + sort, +}: AgentsRequestOptions): ISearchRequestParams => { + const filter = [...createQueryFilterClauses(filterQuery)]; + + const dslQuery = { + allowNoIndices: true, + index: '.fleet-agents', + ignoreUnavailable: true, + body: { + ...(!isEmpty(docValueFields) ? { docvalue_fields: docValueFields } : {}), + query: { bool: { filter } }, + track_total_hits: true, + }, + }; + + return dslQuery; +}; diff --git a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/index.ts b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/index.ts new file mode 100644 index 0000000000000..dc2e741dbe3e4 --- /dev/null +++ b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FactoryQueryTypes, OsqueryQueries } from '../../../../common/search_strategy/osquery'; + +import { allActions, actionDetails, actionResults } from './actions'; +import { allAgents } from './agents'; +import { allResults } from './results'; + +import { OsqueryFactory } from './types'; + +export const osqueryFactory: Record> = { + [OsqueryQueries.actions]: allActions, + [OsqueryQueries.actionDetails]: actionDetails, + [OsqueryQueries.actionResults]: actionResults, + [OsqueryQueries.agents]: allAgents, + [OsqueryQueries.results]: allResults, +}; diff --git a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/results/index.ts b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/results/index.ts new file mode 100644 index 0000000000000..1460a0e5d331e --- /dev/null +++ b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/results/index.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IEsSearchResponse } from '../../../../../../../../src/plugins/data/common'; +import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../../../common/constants'; +import { + ResultsStrategyResponse, + ResultsRequestOptions, + OsqueryQueries, +} from '../../../../../common/search_strategy/osquery'; +import { inspectStringifyObject } from '../../../../../common/utils/build_query'; +import { OsqueryFactory } from '../types'; +import { buildResultsQuery } from './query.all_results.dsl'; + +export const allResults: OsqueryFactory = { + buildDsl: (options: ResultsRequestOptions) => { + if (options.pagination && options.pagination.querySize >= DEFAULT_MAX_TABLE_QUERY_SIZE) { + throw new Error(`No query size above ${DEFAULT_MAX_TABLE_QUERY_SIZE}`); + } + return buildResultsQuery(options); + }, + parse: async ( + options: ResultsRequestOptions, + response: IEsSearchResponse + ): Promise => { + const { activePage } = options.pagination; + const inspect = { + dsl: [inspectStringifyObject(buildResultsQuery(options))], + }; + + return { + ...response, + inspect, + edges: response.rawResponse.hits.hits, + totalCount: response.rawResponse.hits.total, + pageInfo: { + activePage: activePage ?? 0, + fakeTotalCount: 0, + showMorePagesIndicator: false, + }, + }; + }, +}; diff --git a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/results/query.all_results.dsl.ts b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/results/query.all_results.dsl.ts new file mode 100644 index 0000000000000..c099e2762d741 --- /dev/null +++ b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/results/query.all_results.dsl.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ISearchRequestParams } from '../../../../../../../../src/plugins/data/common'; +import { ResultsRequestOptions } from '../../../../../common/search_strategy'; +import { createQueryFilterClauses } from '../../../../../common/utils/build_query'; + +export const buildResultsQuery = ({ + actionId, + filterQuery, + pagination: { activePage, querySize }, + sort, +}: ResultsRequestOptions): ISearchRequestParams => { + const filter = [ + ...createQueryFilterClauses(filterQuery), + { + match_phrase: { + action_id: actionId, + }, + }, + ]; + + const dslQuery = { + allowNoIndices: true, + index: 'logs-elastic_agent.osquery*', + ignoreUnavailable: true, + body: { + query: { bool: { filter } }, + from: activePage * querySize, + size: querySize, + track_total_hits: true, + fields: ['agent.*', 'osquery.*'], + }, + }; + + return dslQuery; +}; diff --git a/x-pack/plugins/osquery/server/search_strategy/osquery/factory/types.ts b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/types.ts new file mode 100644 index 0000000000000..bc2bf63958a09 --- /dev/null +++ b/x-pack/plugins/osquery/server/search_strategy/osquery/factory/types.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + IEsSearchResponse, + ISearchRequestParams, +} from '../../../../../../../src/plugins/data/common'; +import { + FactoryQueryTypes, + StrategyRequestType, + StrategyResponseType, +} from '../../../../common/search_strategy/osquery'; + +export interface OsqueryFactory { + buildDsl: (options: StrategyRequestType) => ISearchRequestParams; + parse: ( + options: StrategyRequestType, + response: IEsSearchResponse + ) => Promise>; +} diff --git a/x-pack/plugins/osquery/server/search_strategy/osquery/index.ts b/x-pack/plugins/osquery/server/search_strategy/osquery/index.ts new file mode 100644 index 0000000000000..8d8a255f2fcdd --- /dev/null +++ b/x-pack/plugins/osquery/server/search_strategy/osquery/index.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { map, mergeMap } from 'rxjs/operators'; +import { + ISearchStrategy, + PluginStart, + shimHitsTotal, +} from '../../../../../../src/plugins/data/server'; +import { ENHANCED_ES_SEARCH_STRATEGY } from '../../../../data_enhanced/common'; +import { + FactoryQueryTypes, + StrategyResponseType, + StrategyRequestType, +} from '../../../common/search_strategy/osquery'; +import { osqueryFactory } from './factory'; +import { OsqueryFactory } from './factory/types'; + +export const osquerySearchStrategyProvider = ( + data: PluginStart +): ISearchStrategy, StrategyResponseType> => { + const es = data.search.getSearchStrategy(ENHANCED_ES_SEARCH_STRATEGY); + + return { + search: (request, options, deps) => { + if (request.factoryQueryType == null) { + throw new Error('factoryQueryType is required'); + } + const queryFactory: OsqueryFactory = osqueryFactory[request.factoryQueryType]; + const dsl = queryFactory.buildDsl(request); + return es.search({ ...request, params: dsl }, options, deps).pipe( + map((response) => { + return { + ...response, + ...{ + rawResponse: shimHitsTotal(response.rawResponse), + }, + }; + }), + mergeMap((esSearchRes) => queryFactory.parse(request, esSearchRes)) + ); + }, + cancel: async (id, options, deps) => { + if (es.cancel) { + return es.cancel(id, options, deps); + } + }, + }; +}; diff --git a/x-pack/plugins/osquery/server/types.ts b/x-pack/plugins/osquery/server/types.ts new file mode 100644 index 0000000000000..51ef28b4c3478 --- /dev/null +++ b/x-pack/plugins/osquery/server/types.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + PluginSetup as DataPluginSetup, + PluginStart as DataPluginStart, +} from '../../../../src/plugins/data/server'; +import { FleetStartContract } from '../../fleet/server'; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface OsqueryPluginSetup {} +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface OsqueryPluginStart {} + +export interface SetupPlugins { + data: DataPluginSetup; +} + +export interface StartPlugins { + data: DataPluginStart; + fleet?: FleetStartContract; +} diff --git a/x-pack/plugins/reporting/server/plugin.ts b/x-pack/plugins/reporting/server/plugin.ts index 05556f050e213..35101dbaab246 100644 --- a/x-pack/plugins/reporting/server/plugin.ts +++ b/x-pack/plugins/reporting/server/plugin.ts @@ -52,6 +52,7 @@ export class ReportingPlugin description: i18n.translate('xpack.reporting.pdfFooterImageDescription', { defaultMessage: `Custom image to use in the PDF's footer`, }), + sensitive: true, type: 'image', schema: schema.nullable(schema.byteSize({ max: '200kb' })), category: [PLUGIN_ID], diff --git a/x-pack/plugins/security/common/constants.ts b/x-pack/plugins/security/common/constants.ts index c235c296bcbae..372f539d812fd 100644 --- a/x-pack/plugins/security/common/constants.ts +++ b/x-pack/plugins/security/common/constants.ts @@ -29,7 +29,7 @@ export const NEXT_URL_QUERY_STRING_PARAMETER = 'next'; * - Must contain only letters, numbers, spaces, punctuation and printable symbols. * - Must not contain leading or trailing spaces. */ -export const NAME_REGEX = /^(?! )[a-zA-Z0-9 !"#$%&'()*+,\-./\\:;<=>?@\[\]^_`{|}~]+(??@\[\]^_`{|}~]*[a-zA-Z0-9!"#$%&'()*+,\-./\\:;<=>?@\[\]^_`{|}~]$/; /** * Maximum length of usernames and role names. diff --git a/x-pack/plugins/security/public/management/users/edit_user/create_user_page.test.tsx b/x-pack/plugins/security/public/management/users/edit_user/create_user_page.test.tsx index e7e3e1164ae14..99c67201b2b56 100644 --- a/x-pack/plugins/security/public/management/users/edit_user/create_user_page.test.tsx +++ b/x-pack/plugins/security/public/management/users/edit_user/create_user_page.test.tsx @@ -88,6 +88,12 @@ describe('CreateUserPage', () => { await findAllByText(/Username must not contain leading or trailing spaces/i); + fireEvent.change(await findByLabelText('Username'), { + target: { value: 'username_with_trailing_space ' }, + }); + + await findAllByText(/Username must not contain leading or trailing spaces/i); + fireEvent.change(await findByLabelText('Username'), { target: { value: '€' }, }); diff --git a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts index fafd0c2772842..7e2c634b2f1cf 100644 --- a/x-pack/plugins/security_solution/common/endpoint/generate_data.ts +++ b/x-pack/plugins/security_solution/common/endpoint/generate_data.ts @@ -17,7 +17,7 @@ import { PolicyData, SafeEndpointEvent, } from './types'; -import { factory as policyFactory } from './models/policy_config'; +import { policyFactory } from './models/policy_config'; import { ancestryArray, entityIDSafeVersion, diff --git a/x-pack/plugins/security_solution/common/endpoint/index_data.ts b/x-pack/plugins/security_solution/common/endpoint/index_data.ts index b3259b19cf2c0..b9d7f6dfe7a5a 100644 --- a/x-pack/plugins/security_solution/common/endpoint/index_data.ts +++ b/x-pack/plugins/security_solution/common/endpoint/index_data.ts @@ -30,7 +30,7 @@ import { PostAgentAcksResponse, PostAgentAcksRequest, } from '../../../fleet/common'; -import { factory as policyConfigFactory } from './models/policy_config'; +import { policyFactory as policyConfigFactory } from './models/policy_config'; import { HostMetadata } from './types'; import { KbnClientWithApiKeySupport } from '../../scripts/endpoint/kbn_client_with_api_key_support'; diff --git a/x-pack/plugins/security_solution/common/endpoint/models/policy_config.ts b/x-pack/plugins/security_solution/common/endpoint/models/policy_config.ts index 14941b019421b..614aac6ea8041 100644 --- a/x-pack/plugins/security_solution/common/endpoint/models/policy_config.ts +++ b/x-pack/plugins/security_solution/common/endpoint/models/policy_config.ts @@ -7,9 +7,9 @@ import { PolicyConfig, ProtectionModes } from '../types'; /** - * Return a new default `PolicyConfig`. + * Return a new default `PolicyConfig` for platinum and above licenses */ -export const factory = (): PolicyConfig => { +export const policyFactory = (): PolicyConfig => { return { windows: { events: { @@ -24,11 +24,18 @@ export const factory = (): PolicyConfig => { malware: { mode: ProtectionModes.prevent, }, + ransomware: { + mode: ProtectionModes.prevent, + }, popup: { malware: { message: '', enabled: true, }, + ransomware: { + message: '', + enabled: true, + }, }, logging: { file: 'info', @@ -46,11 +53,18 @@ export const factory = (): PolicyConfig => { malware: { mode: ProtectionModes.prevent, }, + ransomware: { + mode: ProtectionModes.prevent, + }, popup: { malware: { message: '', enabled: true, }, + ransomware: { + message: '', + enabled: true, + }, }, logging: { file: 'info', @@ -69,6 +83,51 @@ export const factory = (): PolicyConfig => { }; }; +/** + * Strips paid features from an existing or new `PolicyConfig` for gold and below license + */ +export const policyFactoryWithoutPaidFeatures = ( + policy: PolicyConfig = policyFactory() +): PolicyConfig => { + return { + ...policy, + windows: { + ...policy.windows, + ransomware: { + mode: ProtectionModes.off, + }, + popup: { + ...policy.windows.popup, + malware: { + message: '', + enabled: true, + }, + ransomware: { + message: '', + enabled: false, + }, + }, + }, + mac: { + ...policy.mac, + ransomware: { + mode: ProtectionModes.off, + }, + popup: { + ...policy.mac.popup, + malware: { + message: '', + enabled: true, + }, + ransomware: { + message: '', + enabled: false, + }, + }, + }, + }; +}; + /** * Reflects what string the Endpoint will use when message field is default/empty */ diff --git a/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_11.0.test.ts b/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_11_0.test.ts similarity index 98% rename from x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_11.0.test.ts rename to x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_11_0.test.ts index 1b70a13935b7d..932afc6af3f16 100644 --- a/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_11.0.test.ts +++ b/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_11_0.test.ts @@ -6,7 +6,7 @@ import { SavedObjectMigrationContext, SavedObjectUnsanitizedDoc } from 'kibana/server'; import { PackagePolicy } from '../../../../../fleet/common'; -import { migratePackagePolicyToV7110 } from './to_v7_11.0'; +import { migratePackagePolicyToV7110 } from './to_v7_11_0'; describe('7.11.0 Endpoint Package Policy migration', () => { const migration = migratePackagePolicyToV7110; diff --git a/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_11.0.ts b/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_11_0.ts similarity index 100% rename from x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_11.0.ts rename to x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_11_0.ts diff --git a/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_12_0.test.ts b/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_12_0.test.ts new file mode 100644 index 0000000000000..2666b477921fc --- /dev/null +++ b/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_12_0.test.ts @@ -0,0 +1,199 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObjectMigrationContext, SavedObjectUnsanitizedDoc } from 'kibana/server'; +import { PackagePolicy } from '../../../../../fleet/common'; +import { PolicyData, ProtectionModes } from '../../types'; +import { migratePackagePolicyToV7120 } from './to_v7_12_0'; + +describe('7.12.0 Endpoint Package Policy migration', () => { + const migration = migratePackagePolicyToV7120; + it('adds ransomware option and notification customization', () => { + const doc: SavedObjectUnsanitizedDoc = { + id: 'mock-saved-object-id', + attributes: { + name: 'Some Policy Name', + package: { + name: 'endpoint', + title: '', + version: '', + }, + id: 'endpoint', + policy_id: '', + enabled: true, + namespace: '', + output_id: '', + revision: 0, + updated_at: '', + updated_by: '', + created_at: '', + created_by: '', + inputs: [ + { + type: 'endpoint', + enabled: true, + streams: [], + config: { + policy: { + value: { + windows: { + // @ts-expect-error + popup: { + malware: { + message: '', + enabled: false, + }, + }, + }, + mac: { + // @ts-expect-error + popup: { + malware: { + message: '', + enabled: false, + }, + }, + }, + }, + }, + }, + }, + ], + }, + type: ' nested', + }; + + expect( + migration(doc, {} as SavedObjectMigrationContext) as SavedObjectUnsanitizedDoc + ).toEqual({ + attributes: { + name: 'Some Policy Name', + package: { + name: 'endpoint', + title: '', + version: '', + }, + id: 'endpoint', + policy_id: '', + enabled: true, + namespace: '', + output_id: '', + revision: 0, + updated_at: '', + updated_by: '', + created_at: '', + created_by: '', + inputs: [ + { + type: 'endpoint', + enabled: true, + streams: [], + config: { + policy: { + value: { + windows: { + ransomware: ProtectionModes.off, + popup: { + malware: { + message: '', + enabled: false, + }, + ransomware: { + message: '', + enabled: false, + }, + }, + }, + mac: { + ransomware: ProtectionModes.off, + popup: { + malware: { + message: '', + enabled: false, + }, + ransomware: { + message: '', + enabled: false, + }, + }, + }, + }, + }, + }, + }, + ], + }, + type: ' nested', + id: 'mock-saved-object-id', + }); + }); + + it('does not modify non-endpoint package policies', () => { + const doc: SavedObjectUnsanitizedDoc = { + id: 'mock-saved-object-id', + attributes: { + name: 'Some Policy Name', + package: { + name: 'notEndpoint', + title: '', + version: '', + }, + id: 'notEndpoint', + policy_id: '', + enabled: true, + namespace: '', + output_id: '', + revision: 0, + updated_at: '', + updated_by: '', + created_at: '', + created_by: '', + inputs: [ + { + type: 'notEndpoint', + enabled: true, + streams: [], + config: {}, + }, + ], + }, + type: ' nested', + }; + + expect( + migration(doc, {} as SavedObjectMigrationContext) as SavedObjectUnsanitizedDoc + ).toEqual({ + attributes: { + name: 'Some Policy Name', + package: { + name: 'notEndpoint', + title: '', + version: '', + }, + id: 'notEndpoint', + policy_id: '', + enabled: true, + namespace: '', + output_id: '', + revision: 0, + updated_at: '', + updated_by: '', + created_at: '', + created_by: '', + inputs: [ + { + type: 'notEndpoint', + enabled: true, + streams: [], + config: {}, + }, + ], + }, + type: ' nested', + id: 'mock-saved-object-id', + }); + }); +}); diff --git a/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_12_0.ts b/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_12_0.ts new file mode 100644 index 0000000000000..6004ef533d5ad --- /dev/null +++ b/x-pack/plugins/security_solution/common/endpoint/policy/migrations/to_v7_12_0.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObjectMigrationFn, SavedObjectUnsanitizedDoc } from 'kibana/server'; +import { cloneDeep } from 'lodash'; +import { PackagePolicy } from '../../../../../fleet/common'; +import { ProtectionModes } from '../../types'; + +export const migratePackagePolicyToV7120: SavedObjectMigrationFn = ( + packagePolicyDoc +) => { + const updatedPackagePolicyDoc: SavedObjectUnsanitizedDoc = cloneDeep( + packagePolicyDoc + ); + if (packagePolicyDoc.attributes.package?.name === 'endpoint') { + const input = updatedPackagePolicyDoc.attributes.inputs[0]; + const ransomware = { + message: '', + enabled: false, + }; + if (input && input.config) { + const policy = input.config.policy.value; + + policy.windows.ransomware = ProtectionModes.off; + policy.mac.ransomware = ProtectionModes.off; + policy.windows.popup.ransomware = ransomware; + policy.mac.popup.ransomware = ransomware; + } + } + + return updatedPackagePolicyDoc; +}; diff --git a/x-pack/plugins/security_solution/common/endpoint/types/index.ts b/x-pack/plugins/security_solution/common/endpoint/types/index.ts index fab5bd9daae00..f72373a6544a0 100644 --- a/x-pack/plugins/security_solution/common/endpoint/types/index.ts +++ b/x-pack/plugins/security_solution/common/endpoint/types/index.ts @@ -816,7 +816,8 @@ export interface PolicyConfig { registry: boolean; security: boolean; }; - malware: MalwareFields; + malware: ProtectionFields; + ransomware: ProtectionFields; logging: { file: string; }; @@ -825,6 +826,10 @@ export interface PolicyConfig { message: string; enabled: boolean; }; + ransomware: { + message: string; + enabled: boolean; + }; }; antivirus_registration: { enabled: boolean; @@ -837,12 +842,17 @@ export interface PolicyConfig { process: boolean; network: boolean; }; - malware: MalwareFields; + malware: ProtectionFields; + ransomware: ProtectionFields; popup: { malware: { message: string; enabled: boolean; }; + ransomware: { + message: string; + enabled: boolean; + }; }; logging: { file: string; @@ -870,20 +880,20 @@ export interface UIPolicyConfig { */ windows: Pick< PolicyConfig['windows'], - 'events' | 'malware' | 'popup' | 'antivirus_registration' | 'advanced' + 'events' | 'malware' | 'ransomware' | 'popup' | 'antivirus_registration' | 'advanced' >; /** * Mac-specific policy configuration that is supported via the UI */ - mac: Pick; + mac: Pick; /** * Linux-specific policy configuration that is supported via the UI */ linux: Pick; } -/** Policy: Malware protection fields */ -export interface MalwareFields { +/** Policy: Protection fields */ +export interface ProtectionFields { mode: ProtectionModes; } diff --git a/x-pack/plugins/security_solution/common/license/policy_config.test.ts b/x-pack/plugins/security_solution/common/license/policy_config.test.ts index 6923bf00055f6..ef10bba7428ee 100644 --- a/x-pack/plugins/security_solution/common/license/policy_config.test.ts +++ b/x-pack/plugins/security_solution/common/license/policy_config.test.ts @@ -8,8 +8,13 @@ import { isEndpointPolicyValidForLicense, unsetPolicyFeaturesAboveLicenseLevel, } from './policy_config'; -import { DefaultMalwareMessage, factory } from '../endpoint/models/policy_config'; +import { + DefaultMalwareMessage, + policyFactory, + policyFactoryWithoutPaidFeatures, +} from '../endpoint/models/policy_config'; import { licenseMock } from '../../../licensing/common/licensing.mock'; +import { ProtectionModes } from '../endpoint/types'; describe('policy_config and licenses', () => { const Platinum = licenseMock.createLicense({ license: { type: 'platinum', mode: 'platinum' } }); @@ -18,13 +23,13 @@ describe('policy_config and licenses', () => { describe('isEndpointPolicyValidForLicense', () => { it('allows malware notification to be disabled with a Platinum license', () => { - const policy = factory(); + const policy = policyFactory(); policy.windows.popup.malware.enabled = false; // make policy change const valid = isEndpointPolicyValidForLicense(policy, Platinum); expect(valid).toBeTruthy(); }); it('blocks windows malware notification changes below Platinum licenses', () => { - const policy = factory(); + const policy = policyFactory(); policy.windows.popup.malware.enabled = false; // make policy change let valid = isEndpointPolicyValidForLicense(policy, Gold); expect(valid).toBeFalsy(); @@ -34,7 +39,7 @@ describe('policy_config and licenses', () => { }); it('blocks mac malware notification changes below Platinum licenses', () => { - const policy = factory(); + const policy = policyFactory(); policy.mac.popup.malware.enabled = false; // make policy change let valid = isEndpointPolicyValidForLicense(policy, Gold); expect(valid).toBeFalsy(); @@ -44,13 +49,13 @@ describe('policy_config and licenses', () => { }); it('allows malware notification message changes with a Platinum license', () => { - const policy = factory(); + const policy = policyFactory(); policy.windows.popup.malware.message = 'BOOM'; // make policy change const valid = isEndpointPolicyValidForLicense(policy, Platinum); expect(valid).toBeTruthy(); }); it('blocks windows malware notification message changes below Platinum licenses', () => { - const policy = factory(); + const policy = policyFactory(); policy.windows.popup.malware.message = 'BOOM'; // make policy change let valid = isEndpointPolicyValidForLicense(policy, Gold); expect(valid).toBeFalsy(); @@ -59,7 +64,7 @@ describe('policy_config and licenses', () => { expect(valid).toBeFalsy(); }); it('blocks mac malware notification message changes below Platinum licenses', () => { - const policy = factory(); + const policy = policyFactory(); policy.mac.popup.malware.message = 'BOOM'; // make policy change let valid = isEndpointPolicyValidForLicense(policy, Gold); expect(valid).toBeFalsy(); @@ -68,16 +73,71 @@ describe('policy_config and licenses', () => { expect(valid).toBeFalsy(); }); + it('allows ransomware to be turned on for Platinum licenses', () => { + const policy = policyFactoryWithoutPaidFeatures(); + policy.windows.ransomware.mode = ProtectionModes.prevent; + policy.mac.ransomware.mode = ProtectionModes.prevent; + + const valid = isEndpointPolicyValidForLicense(policy, Platinum); + expect(valid).toBeTruthy(); + }); + it('blocks ransomware to be turned on for Gold and below licenses', () => { + const policy = policyFactoryWithoutPaidFeatures(); + policy.windows.ransomware.mode = ProtectionModes.prevent; + policy.mac.ransomware.mode = ProtectionModes.prevent; + + let valid = isEndpointPolicyValidForLicense(policy, Gold); + expect(valid).toBeFalsy(); + valid = isEndpointPolicyValidForLicense(policy, Basic); + expect(valid).toBeFalsy(); + }); + + it('allows ransomware notification to be turned on with a Platinum license', () => { + const policy = policyFactoryWithoutPaidFeatures(); + policy.windows.popup.ransomware.enabled = true; + policy.mac.popup.ransomware.enabled = true; + const valid = isEndpointPolicyValidForLicense(policy, Platinum); + expect(valid).toBeTruthy(); + }); + it('blocks ransomware notification to be turned on for Gold and below licenses', () => { + const policy = policyFactoryWithoutPaidFeatures(); + policy.windows.popup.ransomware.enabled = true; + policy.mac.popup.ransomware.enabled = true; + let valid = isEndpointPolicyValidForLicense(policy, Gold); + expect(valid).toBeFalsy(); + + valid = isEndpointPolicyValidForLicense(policy, Basic); + expect(valid).toBeFalsy(); + }); + + it('allows ransomware notification message changes with a Platinum license', () => { + const policy = policyFactory(); + policy.windows.popup.ransomware.message = 'BOOM'; + policy.mac.popup.ransomware.message = 'BOOM'; + const valid = isEndpointPolicyValidForLicense(policy, Platinum); + expect(valid).toBeTruthy(); + }); + it('blocks ransomware notification message changes for Gold and below licenses', () => { + const policy = policyFactory(); + policy.windows.popup.ransomware.message = 'BOOM'; + policy.mac.popup.ransomware.message = 'BOOM'; + let valid = isEndpointPolicyValidForLicense(policy, Gold); + expect(valid).toBeFalsy(); + + valid = isEndpointPolicyValidForLicense(policy, Basic); + expect(valid).toBeFalsy(); + }); + it('allows default policyConfig with Basic', () => { - const policy = factory(); + const policy = policyFactoryWithoutPaidFeatures(); const valid = isEndpointPolicyValidForLicense(policy, Basic); expect(valid).toBeTruthy(); }); }); describe('unsetPolicyFeaturesAboveLicenseLevel', () => { - it('does not change any fields with a Platinum license', () => { - const policy = factory(); + it('does not change any malware fields with a Platinum license', () => { + const policy = policyFactory(); const popupMessage = 'WOOP WOOP'; policy.windows.popup.malware.message = popupMessage; policy.mac.popup.malware.message = popupMessage; @@ -88,14 +148,37 @@ describe('policy_config and licenses', () => { expect(retPolicy.windows.popup.malware.message).toEqual(popupMessage); expect(retPolicy.mac.popup.malware.message).toEqual(popupMessage); }); - it('resets Platinum-paid fields for lower license tiers', () => { - const defaults = factory(); // reference - const policy = factory(); // what we will modify, and should be reset + + it('does not change any ransomware fields with a Platinum license', () => { + const policy = policyFactory(); + const popupMessage = 'WOOP WOOP'; + policy.windows.ransomware.mode = ProtectionModes.detect; + policy.mac.ransomware.mode = ProtectionModes.detect; + policy.windows.popup.ransomware.enabled = false; + policy.mac.popup.ransomware.enabled = false; + policy.windows.popup.ransomware.message = popupMessage; + policy.mac.popup.ransomware.message = popupMessage; + + const retPolicy = unsetPolicyFeaturesAboveLicenseLevel(policy, Platinum); + expect(retPolicy.windows.ransomware.mode).toEqual(ProtectionModes.detect); + expect(retPolicy.mac.ransomware.mode).toEqual(ProtectionModes.detect); + expect(retPolicy.windows.popup.ransomware.enabled).toBeFalsy(); + expect(retPolicy.mac.popup.ransomware.enabled).toBeFalsy(); + expect(retPolicy.windows.popup.ransomware.message).toEqual(popupMessage); + expect(retPolicy.mac.popup.ransomware.message).toEqual(popupMessage); + }); + + it('resets Platinum-paid malware fields for lower license tiers', () => { + const defaults = policyFactory(); // reference + const policy = policyFactory(); // what we will modify, and should be reset const popupMessage = 'WOOP WOOP'; policy.windows.popup.malware.message = popupMessage; policy.mac.popup.malware.message = popupMessage; policy.windows.popup.malware.enabled = false; + policy.windows.popup.ransomware.message = popupMessage; + policy.mac.popup.ransomware.message = popupMessage; + policy.windows.popup.ransomware.enabled = false; const retPolicy = unsetPolicyFeaturesAboveLicenseLevel(policy, Gold); expect(retPolicy.windows.popup.malware.enabled).toEqual( defaults.windows.popup.malware.enabled @@ -106,5 +189,37 @@ describe('policy_config and licenses', () => { // need to invert the test, since it could be either value expect(['', DefaultMalwareMessage]).toContain(retPolicy.windows.popup.malware.message); }); + + it('resets Platinum-paid ransomware fields for lower license tiers', () => { + const defaults = policyFactoryWithoutPaidFeatures(); // reference + const policy = policyFactory(); // what we will modify, and should be reset + const popupMessage = 'WOOP WOOP'; + policy.windows.popup.ransomware.message = popupMessage; + policy.mac.popup.ransomware.message = popupMessage; + + const retPolicy = unsetPolicyFeaturesAboveLicenseLevel(policy, Gold); + + expect(retPolicy.windows.ransomware.mode).toEqual(defaults.windows.ransomware.mode); + expect(retPolicy.mac.ransomware.mode).toEqual(defaults.mac.ransomware.mode); + expect(retPolicy.windows.popup.ransomware.enabled).toEqual( + defaults.windows.popup.ransomware.enabled + ); + expect(retPolicy.mac.popup.ransomware.enabled).toEqual(defaults.mac.popup.ransomware.enabled); + expect(retPolicy.windows.popup.ransomware.message).not.toEqual(popupMessage); + expect(retPolicy.mac.popup.ransomware.message).not.toEqual(popupMessage); + + // need to invert the test, since it could be either value + expect(['', DefaultMalwareMessage]).toContain(retPolicy.windows.popup.ransomware.message); + expect(['', DefaultMalwareMessage]).toContain(retPolicy.mac.popup.ransomware.message); + }); + }); + + describe('policyFactoryWithoutPaidFeatures for gold and below license', () => { + it('preserves non license-gated features', () => { + const policy = policyFactory(); // what we will modify, and should be reset + policy.windows.events.file = false; + const retPolicy = policyFactoryWithoutPaidFeatures(policy); + expect(retPolicy.windows.events.file).toBeFalsy(); + }); }); }); diff --git a/x-pack/plugins/security_solution/common/license/policy_config.ts b/x-pack/plugins/security_solution/common/license/policy_config.ts index da2260ad55e8b..e791b68f12f40 100644 --- a/x-pack/plugins/security_solution/common/license/policy_config.ts +++ b/x-pack/plugins/security_solution/common/license/policy_config.ts @@ -7,7 +7,10 @@ import { ILicense } from '../../../licensing/common/types'; import { isAtLeast } from './license'; import { PolicyConfig } from '../endpoint/types'; -import { DefaultMalwareMessage, factory } from '../endpoint/models/policy_config'; +import { + DefaultMalwareMessage, + policyFactoryWithoutPaidFeatures, +} from '../endpoint/models/policy_config'; /** * Given an endpoint package policy, verifies that all enabled features that @@ -21,7 +24,7 @@ export const isEndpointPolicyValidForLicense = ( return true; // currently, platinum allows all features } - const defaults = factory(); + const defaults = policyFactoryWithoutPaidFeatures(); // only platinum or higher may disable malware notification if ( @@ -40,6 +43,32 @@ export const isEndpointPolicyValidForLicense = ( return false; } + // only platinum or higher may enable ransomware + if ( + policy.windows.ransomware.mode !== defaults.windows.ransomware.mode || + policy.mac.ransomware.mode !== defaults.mac.ransomware.mode + ) { + return false; + } + + // only platinum or higher may enable ransomware notification + if ( + policy.windows.popup.ransomware.enabled !== defaults.windows.popup.ransomware.enabled || + policy.mac.popup.ransomware.enabled !== defaults.mac.popup.ransomware.enabled + ) { + return false; + } + + // Only Platinum or higher may change the ransomware message (which can be blank or what Endpoint defaults) + if ( + [policy.windows, policy.mac].some( + (p) => + p.popup.ransomware.message !== '' && p.popup.ransomware.message !== DefaultMalwareMessage + ) + ) { + return false; + } + return true; }; @@ -55,12 +84,6 @@ export const unsetPolicyFeaturesAboveLicenseLevel = ( return policy; } - const defaults = factory(); // set any license-gated features back to the defaults - policy.windows.popup.malware.enabled = defaults.windows.popup.malware.enabled; - policy.mac.popup.malware.enabled = defaults.mac.popup.malware.enabled; - policy.windows.popup.malware.message = defaults.windows.popup.malware.message; - policy.mac.popup.malware.message = defaults.mac.popup.malware.message; - - return policy; + return policyFactoryWithoutPaidFeatures(policy); }; diff --git a/x-pack/plugins/security_solution/common/shared_exports.ts b/x-pack/plugins/security_solution/common/shared_exports.ts index fb457933f4b54..92a736ae601df 100644 --- a/x-pack/plugins/security_solution/common/shared_exports.ts +++ b/x-pack/plugins/security_solution/common/shared_exports.ts @@ -16,4 +16,5 @@ export { exactCheck } from './exact_check'; export { getPaths, foldLeftRight, removeExternalLinkText } from './test_utils'; export { validate, validateEither } from './validate'; export { formatErrors } from './format_errors'; -export { migratePackagePolicyToV7110 } from './endpoint/policy/migrations/to_v7_11.0'; +export { migratePackagePolicyToV7110 } from './endpoint/policy/migrations/to_v7_11_0'; +export { migratePackagePolicyToV7120 } from './endpoint/policy/migrations/to_v7_12_0'; diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.test.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.test.ts index 8451eb0dfbe6c..716585071c3f6 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.test.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.test.ts @@ -99,7 +99,7 @@ describe('helpers', () => { { ...mockThreat, tactic: { ...mockThreat.tactic, name: 'none' } }, ]; const result = filterEmptyThreats(threat); - const expected = [mockThreat]; + const expected: Threats = [mockThreat]; expect(result).toEqual(expected); }); }); @@ -112,8 +112,8 @@ describe('helpers', () => { }); test('returns formatted object as DefineStepRuleJson', () => { - const result: DefineStepRuleJson = formatDefineStepData(mockData); - const expected = { + const result = formatDefineStepData(mockData); + const expected: DefineStepRuleJson = { language: 'kuery', filters: mockQueryBar.filters, query: 'test query', @@ -128,15 +128,15 @@ describe('helpers', () => { }); test('returns formatted object with no saved_id if no savedId provided', () => { - const mockStepData = { + const mockStepData: DefineStepRule = { ...mockData, queryBar: { ...mockData.queryBar, saved_id: '', }, }; - const result: DefineStepRuleJson = formatDefineStepData(mockStepData); - const expected = { + const result = formatDefineStepData(mockStepData); + const expected: DefineStepRuleJson = { language: 'kuery', filters: mockQueryBar.filters, query: 'test query', @@ -151,15 +151,15 @@ describe('helpers', () => { }); test('returns formatted object without timeline_id and timeline_title if timeline.id is null', () => { - const mockStepData = { + const mockStepData: DefineStepRule = { ...mockData, }; // @ts-expect-error delete mockStepData.timeline.id; - const result: DefineStepRuleJson = formatDefineStepData(mockStepData); + const result = formatDefineStepData(mockStepData); - const expected = { + const expected: DefineStepRuleJson = { language: 'kuery', filters: mockQueryBar.filters, query: 'test query', @@ -172,16 +172,16 @@ describe('helpers', () => { }); test('returns formatted object with timeline_id and timeline_title if timeline.id is "', () => { - const mockStepData = { + const mockStepData: DefineStepRule = { ...mockData, timeline: { ...mockData.timeline, id: '', }, }; - const result: DefineStepRuleJson = formatDefineStepData(mockStepData); + const result = formatDefineStepData(mockStepData); - const expected = { + const expected: DefineStepRuleJson = { language: 'kuery', filters: mockQueryBar.filters, query: 'test query', @@ -196,7 +196,7 @@ describe('helpers', () => { }); test('returns formatted object without timeline_id and timeline_title if timeline.title is null', () => { - const mockStepData = { + const mockStepData: DefineStepRule = { ...mockData, timeline: { ...mockData.timeline, @@ -205,9 +205,9 @@ describe('helpers', () => { }; // @ts-expect-error delete mockStepData.timeline.title; - const result: DefineStepRuleJson = formatDefineStepData(mockStepData); + const result = formatDefineStepData(mockStepData); - const expected = { + const expected: DefineStepRuleJson = { language: 'kuery', filters: mockQueryBar.filters, query: 'test query', @@ -220,16 +220,16 @@ describe('helpers', () => { }); test('returns formatted object with timeline_id and timeline_title if timeline.title is "', () => { - const mockStepData = { + const mockStepData: DefineStepRule = { ...mockData, timeline: { ...mockData.timeline, title: '', }, }; - const result: DefineStepRuleJson = formatDefineStepData(mockStepData); + const result = formatDefineStepData(mockStepData); - const expected = { + const expected: DefineStepRuleJson = { language: 'kuery', filters: mockQueryBar.filters, query: 'test query', @@ -250,9 +250,9 @@ describe('helpers', () => { anomalyThreshold: 44, machineLearningJobId: 'some_jobert_id', }; - const result: DefineStepRuleJson = formatDefineStepData(mockStepData); + const result = formatDefineStepData(mockStepData); - const expected = { + const expected: DefineStepRuleJson = { type: 'machine_learning', anomaly_threshold: 44, machine_learning_job_id: 'some_jobert_id', @@ -276,9 +276,9 @@ describe('helpers', () => { }, }, }; - const result: DefineStepRuleJson = formatDefineStepData(mockStepData); + const result = formatDefineStepData(mockStepData); - const expected = { + const expected: DefineStepRuleJson = { filters: mockStepData.queryBar.filters, index: mockStepData.index, language: 'eql', @@ -288,6 +288,70 @@ describe('helpers', () => { expect(result).toEqual(expect.objectContaining(expected)); }); + + test('returns expected indicator matching rule type if all fields are filled out', () => { + const threatFilters: DefineStepRule['threatQueryBar']['filters'] = [ + { + meta: { alias: '', disabled: false, negate: false }, + query: { + bool: { + filter: [ + { + bool: { + minimum_should_match: 1, + should: [{ exists: { field: 'host.name' } }], + }, + }, + {}, + ], + must: [], + must_not: [], + should: [], + }, + }, + }, + ]; + const threatMapping: DefineStepRule['threatMapping'] = [ + { + entries: [ + { + field: 'host.name', + type: 'mapping', + value: 'host.name', + }, + ], + }, + ]; + const mockStepData: DefineStepRule = { + ...mockData, + ruleType: 'threat_match', + threatIndex: ['index_1', 'index_2'], + threatQueryBar: { + query: { language: 'kql', query: 'threat_host: *' }, + filters: threatFilters, + }, + threatMapping, + }; + const result = formatDefineStepData(mockStepData); + + const expected: DefineStepRuleJson = { + language: 'kuery', + query: 'test query', + saved_id: 'test123', + type: 'threat_match', + threat_query: 'threat_host: *', + timeline_id: '86aa74d0-2136-11ea-9864-ebc8cc1cb8c2', + timeline_title: 'Titled timeline', + threat_mapping: threatMapping, + threat_language: mockStepData.threatQueryBar.query.language, + filters: mockStepData.queryBar.filters, + threat_index: mockStepData.threatIndex, + index: mockStepData.index, + threat_filters: threatFilters, + }; + + expect(result).toEqual(expected); + }); }); describe('formatScheduleStepData', () => { @@ -298,8 +362,8 @@ describe('helpers', () => { }); test('returns formatted object as ScheduleStepRuleJson', () => { - const result: ScheduleStepRuleJson = formatScheduleStepData(mockData); - const expected = { + const result = formatScheduleStepData(mockData); + const expected: ScheduleStepRuleJson = { from: 'now-660s', to: 'now', interval: '5m', @@ -312,12 +376,12 @@ describe('helpers', () => { }); test('returns formatted object with "to" as "now" if "to" not supplied', () => { - const mockStepData = { + const mockStepData: ScheduleStepRule = { ...mockData, }; delete mockStepData.to; - const result: ScheduleStepRuleJson = formatScheduleStepData(mockStepData); - const expected = { + const result = formatScheduleStepData(mockStepData); + const expected: ScheduleStepRuleJson = { from: 'now-660s', to: 'now', interval: '5m', @@ -330,12 +394,12 @@ describe('helpers', () => { }); test('returns formatted object with "to" as "now" if "to" random string', () => { - const mockStepData = { + const mockStepData: ScheduleStepRule = { ...mockData, to: 'random', }; - const result: ScheduleStepRuleJson = formatScheduleStepData(mockStepData); - const expected = { + const result = formatScheduleStepData(mockStepData); + const expected: ScheduleStepRuleJson = { from: 'now-660s', to: 'now', interval: '5m', @@ -348,12 +412,12 @@ describe('helpers', () => { }); test('returns formatted object if "from" random string', () => { - const mockStepData = { + const mockStepData: ScheduleStepRule = { ...mockData, from: 'random', }; - const result: ScheduleStepRuleJson = formatScheduleStepData(mockStepData); - const expected = { + const result = formatScheduleStepData(mockStepData); + const expected: ScheduleStepRuleJson = { from: 'now-300s', to: 'now', interval: '5m', @@ -366,12 +430,12 @@ describe('helpers', () => { }); test('returns formatted object if "interval" random string', () => { - const mockStepData = { + const mockStepData: ScheduleStepRule = { ...mockData, interval: 'random', }; - const result: ScheduleStepRuleJson = formatScheduleStepData(mockStepData); - const expected = { + const result = formatScheduleStepData(mockStepData); + const expected: ScheduleStepRuleJson = { from: 'now-360s', to: 'now', interval: 'random', @@ -392,8 +456,8 @@ describe('helpers', () => { }); test('returns formatted object as AboutStepRuleJson', () => { - const result: AboutStepRuleJson = formatAboutStepData(mockData); - const expected = { + const result = formatAboutStepData(mockData); + const expected: AboutStepRuleJson = { author: ['Elastic'], description: '24/7', false_positives: ['test'], @@ -413,7 +477,7 @@ describe('helpers', () => { }); test('returns formatted object with endpoint exceptions_list', () => { - const result: AboutStepRuleJson = formatAboutStepData( + const result = formatAboutStepData( { ...mockData, isAssociatedToEndpointList: true, @@ -424,12 +488,12 @@ describe('helpers', () => { }); test('returns formatted object with detections exceptions_list', () => { - const result: AboutStepRuleJson = formatAboutStepData(mockData, [getListMock()]); + const result = formatAboutStepData(mockData, [getListMock()]); expect(result.exceptions_list).toEqual([getListMock()]); }); test('returns formatted object with both exceptions_lists', () => { - const result: AboutStepRuleJson = formatAboutStepData( + const result = formatAboutStepData( { ...mockData, isAssociatedToEndpointList: true, @@ -441,7 +505,7 @@ describe('helpers', () => { test('returns formatted object with pre-existing exceptions lists', () => { const exceptionsLists: List[] = [getEndpointListMock(), getListMock()]; - const result: AboutStepRuleJson = formatAboutStepData( + const result = formatAboutStepData( { ...mockData, isAssociatedToEndpointList: true, @@ -453,18 +517,18 @@ describe('helpers', () => { test('returns formatted object with pre-existing endpoint exceptions list disabled', () => { const exceptionsLists: List[] = [getEndpointListMock(), getListMock()]; - const result: AboutStepRuleJson = formatAboutStepData(mockData, exceptionsLists); + const result = formatAboutStepData(mockData, exceptionsLists); expect(result.exceptions_list).toEqual([getListMock()]); }); test('returns formatted object with empty falsePositive and references filtered out', () => { - const mockStepData = { + const mockStepData: AboutStepRule = { ...mockData, falsePositives: ['', 'test', ''], references: ['www.test.co', ''], }; - const result: AboutStepRuleJson = formatAboutStepData(mockStepData); - const expected = { + const result = formatAboutStepData(mockStepData); + const expected: AboutStepRuleJson = { author: ['Elastic'], description: '24/7', false_positives: ['test'], @@ -484,12 +548,12 @@ describe('helpers', () => { }); test('returns formatted object without note if note is empty string', () => { - const mockStepData = { + const mockStepData: AboutStepRule = { ...mockData, note: '', }; - const result: AboutStepRuleJson = formatAboutStepData(mockStepData); - const expected = { + const result = formatAboutStepData(mockStepData); + const expected: AboutStepRuleJson = { author: ['Elastic'], description: '24/7', false_positives: ['test'], @@ -508,7 +572,7 @@ describe('helpers', () => { }); test('returns formatted object with threats filtered out where tactic.name is "none"', () => { - const mockStepData = { + const mockStepData: AboutStepRule = { ...mockData, threat: [ ...getThreatMock(), @@ -530,8 +594,8 @@ describe('helpers', () => { }, ], }; - const result: AboutStepRuleJson = formatAboutStepData(mockStepData); - const expected = { + const result = formatAboutStepData(mockStepData); + const expected: AboutStepRuleJson = { author: ['Elastic'], license: 'Elastic License', description: '24/7', @@ -551,7 +615,7 @@ describe('helpers', () => { }); test('returns formatted object with threats that contains no subtechniques', () => { - const mockStepData = { + const mockStepData: AboutStepRule = { ...mockData, threat: [ ...getThreatMock(), @@ -573,8 +637,8 @@ describe('helpers', () => { }, ], }; - const result: AboutStepRuleJson = formatAboutStepData(mockStepData); - const expected = { + const result = formatAboutStepData(mockStepData); + const expected: AboutStepRuleJson = { author: ['Elastic'], license: 'Elastic License', description: '24/7', @@ -611,8 +675,8 @@ describe('helpers', () => { }); test('returns formatted object as ActionsStepRuleJson', () => { - const result: ActionsStepRuleJson = formatActionsStepData(mockData); - const expected = { + const result = formatActionsStepData(mockData); + const expected: ActionsStepRuleJson = { actions: [], enabled: false, meta: { @@ -625,12 +689,12 @@ describe('helpers', () => { }); test('returns proper throttle value for no_actions', () => { - const mockStepData = { + const mockStepData: ActionsStepRule = { ...mockData, throttle: 'no_actions', }; - const result: ActionsStepRuleJson = formatActionsStepData(mockStepData); - const expected = { + const result = formatActionsStepData(mockStepData); + const expected: ActionsStepRuleJson = { actions: [], enabled: false, meta: { @@ -643,7 +707,7 @@ describe('helpers', () => { }); test('returns proper throttle value for rule', () => { - const mockStepData = { + const mockStepData: ActionsStepRule = { ...mockData, throttle: 'rule', actions: [ @@ -655,8 +719,8 @@ describe('helpers', () => { }, ], }; - const result: ActionsStepRuleJson = formatActionsStepData(mockStepData); - const expected = { + const result = formatActionsStepData(mockStepData); + const expected: ActionsStepRuleJson = { actions: [ { group: mockStepData.actions[0].group, @@ -676,7 +740,7 @@ describe('helpers', () => { }); test('returns proper throttle value for interval', () => { - const mockStepData = { + const mockStepData: ActionsStepRule = { ...mockData, throttle: '1d', actions: [ @@ -688,8 +752,8 @@ describe('helpers', () => { }, ], }; - const result: ActionsStepRuleJson = formatActionsStepData(mockStepData); - const expected = { + const result = formatActionsStepData(mockStepData); + const expected: ActionsStepRuleJson = { actions: [ { group: mockStepData.actions[0].group, @@ -716,12 +780,12 @@ describe('helpers', () => { actionTypeId: '.slack', }; - const mockStepData = { + const mockStepData: ActionsStepRule = { ...mockData, actions: [mockAction], }; - const result: ActionsStepRuleJson = formatActionsStepData(mockStepData); - const expected = { + const result = formatActionsStepData(mockStepData); + const expected: ActionsStepRuleJson = { actions: [ { group: mockAction.group, @@ -755,20 +819,20 @@ describe('helpers', () => { }); test('returns rule with type of saved_query when saved_id exists', () => { - const result: Rule = formatRule(mockDefine, mockAbout, mockSchedule, mockActions); + const result = formatRule(mockDefine, mockAbout, mockSchedule, mockActions); expect(result.type).toEqual('saved_query'); }); test('returns rule with type of query when saved_id does not exist', () => { - const mockDefineStepRuleWithoutSavedId = { + const mockDefineStepRuleWithoutSavedId: DefineStepRule = { ...mockDefine, queryBar: { ...mockDefine.queryBar, saved_id: '', }, }; - const result: CreateRulesSchema = formatRule( + const result = formatRule( mockDefineStepRuleWithoutSavedId, mockAbout, mockSchedule, @@ -779,7 +843,7 @@ describe('helpers', () => { }); test('returns rule without id if ruleId does not exist', () => { - const result: CreateRulesSchema = formatRule( + const result = formatRule( mockDefine, mockAbout, mockSchedule, diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts index 7952bd396b72a..34818e7f0e4e2 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/create/helpers.ts @@ -232,6 +232,7 @@ export const formatDefineStepData = (defineStepData: DefineStepRule): DefineStep saved_id: ruleFields.queryBar?.saved_id, threat_index: ruleFields.threatIndex, threat_query: ruleFields.threatQueryBar?.query?.query as string, + threat_filters: ruleFields.threatQueryBar?.filters, threat_mapping: ruleFields.threatMapping, threat_language: ruleFields.threatQueryBar?.query?.language, } diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts index 59cc7fba017e2..00206279be229 100644 --- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts +++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/types.ts @@ -159,6 +159,11 @@ export interface DefineStepRuleJson { field: string; value: number; }; + threat_query?: string; + threat_mapping?: ThreatMapping; + threat_language?: string; + threat_index?: string[]; + threat_filters?: Filter[]; timeline_id?: string; timeline_title?: string; type: Type; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts index 70ffc1f8a9fc4..bda268c1fad00 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/index.test.ts @@ -8,7 +8,7 @@ import { PolicyDetailsState } from '../../types'; import { applyMiddleware, createStore, Dispatch, Store } from 'redux'; import { policyDetailsReducer, PolicyDetailsAction, policyDetailsMiddlewareFactory } from './index'; import { policyConfig } from './selectors'; -import { factory as policyConfigFactory } from '../../../../../../common/endpoint/models/policy_config'; +import { policyFactory } from '../../../../../../common/endpoint/models/policy_config'; import { PolicyData } from '../../../../../../common/endpoint/types'; import { createSpyMiddleware, @@ -54,7 +54,7 @@ describe('policy details: ', () => { }, }, policy: { - value: policyConfigFactory(), + value: policyFactory(), }, }, }, @@ -254,6 +254,7 @@ describe('policy details: ', () => { http.put.mock.calls.length - 1 ] as unknown) as [string, HttpFetchOptions])[1]; + // license is below platinum in this test, paid features are off expect(JSON.parse(lastPutCallPayload.body as string)).toEqual({ name: '', description: '', @@ -282,11 +283,16 @@ describe('policy details: ', () => { security: true, }, malware: { mode: 'prevent' }, + ransomware: { mode: 'off' }, popup: { malware: { enabled: true, message: '', }, + ransomware: { + enabled: false, + message: '', + }, }, logging: { file: 'info' }, antivirus_registration: { @@ -296,11 +302,16 @@ describe('policy details: ', () => { mac: { events: { process: true, file: true, network: true }, malware: { mode: 'prevent' }, + ransomware: { mode: 'off' }, popup: { malware: { enabled: true, message: '', }, + ransomware: { + enabled: false, + message: '', + }, }, logging: { file: 'info' }, }, diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware.ts b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware.ts index cc286b4c478d3..4d54acb5eae13 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/store/policy_details/middleware.ts @@ -41,6 +41,10 @@ export const policyDetailsMiddlewareFactory: ImmutableMiddlewareFactory { if (policyData) { - unsetPolicyFeaturesAboveLicenseLevel( - policyData?.inputs[0]?.config.policy.value, + const policyValue = unsetPolicyFeaturesAboveLicenseLevel( + policyData.inputs[0].config.policy.value, license as ILicense ); + const newPolicyData: Immutable = { + ...policyData, + inputs: [ + { + ...policyData.inputs[0], + config: { + ...policyData.inputs[0].config, + policy: { + ...policyData.inputs[0].config.policy, + value: policyValue, + }, + }, + }, + ], + }; + return newPolicyData; } return policyData; } @@ -167,6 +183,7 @@ export const policyConfig: (s: PolicyDetailsState) => UIPolicyConfig = createSel advanced: windows.advanced, events: windows.events, malware: windows.malware, + ransomware: windows.ransomware, popup: windows.popup, antivirus_registration: windows.antivirus_registration, }, @@ -174,6 +191,7 @@ export const policyConfig: (s: PolicyDetailsState) => UIPolicyConfig = createSel advanced: mac.advanced, events: mac.events, malware: mac.malware, + ransomware: mac.ransomware, popup: mac.popup, }, linux: { diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/types.ts b/x-pack/plugins/security_solution/public/management/pages/policy/types.ts index 228e8cc1c4385..a37e404cdc522 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/types.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/types.ts @@ -8,7 +8,7 @@ import { ILicense } from '../../../../../licensing/common/types'; import { AppLocation, Immutable, - MalwareFields, + ProtectionFields, PolicyData, UIPolicyConfig, } from '../../../../common/endpoint/types'; @@ -108,7 +108,16 @@ export type KeysByValueCriteria = { }[keyof O]; /** Returns an array of the policy OSes that have a malware protection field */ -export type MalwareProtectionOSes = KeysByValueCriteria; +export type MalwareProtectionOSes = KeysByValueCriteria< + UIPolicyConfig, + { malware: ProtectionFields } +>; + +/** Returns an array of the policy OSes that have a ransomware protection field */ +export type RansomwareProtectionOSes = KeysByValueCriteria< + UIPolicyConfig, + { ransomware: ProtectionFields } +>; export interface GetPolicyListResponse extends GetPackagePoliciesResponse { items: PolicyData[]; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/components/config_form/index.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/components/config_form/index.tsx index ce5eb03d60cd0..aea4df5b2a6fd 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/components/config_form/index.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/components/config_form/index.tsx @@ -58,7 +58,7 @@ export const ConfigForm: FC = memo( {TITLES.type} {type} - + {TITLES.os} {supportedOss.map((os) => OS_TITLES[os]).join(', ')} diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx index 1280f1c351c2b..55fc7703de44b 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details.test.tsx @@ -301,11 +301,16 @@ describe('Policy Details', () => { const userNotificationCustomMessageTextArea = policyView.find( 'EuiTextArea[data-test-subj="malwareUserNotificationCustomMessage"]' ); - const tooltip = policyView.find('EuiIconTip'); + const tooltip = policyView.find('EuiIconTip[data-test-subj="malwareTooltip"]'); expect(userNotificationCheckbox).toHaveLength(1); expect(userNotificationCustomMessageTextArea).toHaveLength(1); expect(tooltip).toHaveLength(1); }); + + it('ransomware card is shown', () => { + const ransomware = policyView.find('EuiPanel[data-test-subj="ransomwareProtectionsForm"]'); + expect(ransomware).toHaveLength(1); + }); }); describe('when the subscription tier is gold or lower', () => { beforeEach(() => { @@ -325,6 +330,11 @@ describe('Policy Details', () => { expect(userNotificationCustomMessageTextArea).toHaveLength(0); expect(tooltip).toHaveLength(0); }); + + it('ransomware card is hidden', () => { + const ransomware = policyView.find('EuiPanel[data-test-subj="ransomwareProtectionsForm"]'); + expect(ransomware).toHaveLength(0); + }); }); }); }); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details_form.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details_form.tsx index a0bf2b37e8a12..8710f696fad41 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details_form.tsx +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_details_form.tsx @@ -11,12 +11,15 @@ import { MalwareProtections } from './policy_forms/protections/malware'; import { LinuxEvents, MacEvents, WindowsEvents } from './policy_forms/events'; import { AdvancedPolicyForms } from './policy_advanced'; import { AntivirusRegistrationForm } from './components/antivirus_registration_form'; +import { Ransomware } from './policy_forms/protections/ransomware'; +import { useLicense } from '../../../../common/hooks/use_license'; export const PolicyDetailsForm = memo(() => { const [showAdvancedPolicy, setShowAdvancedPolicy] = useState(false); const handleAdvancedPolicyClick = useCallback(() => { setShowAdvancedPolicy(!showAdvancedPolicy); }, [showAdvancedPolicy]); + const isPlatinumPlus = useLicense().isPlatinumPlus(); return ( <> @@ -31,6 +34,8 @@ export const PolicyDetailsForm = memo(() => { + + {isPlatinumPlus && } @@ -44,14 +49,14 @@ export const PolicyDetailsForm = memo(() => { - + - + - + - + props.theme.eui.euiSizeXXL}; +export const RadioFlexGroup = styled(EuiFlexGroup)` + .no-right-margin-radio { + margin-right: 0; + } + .no-horizontal-margin-radio { + margin: ${(props) => props.theme.eui.ruleMargins.marginSmall} 0; } `; const OSes: Immutable = [OS.windows, OS.mac]; const protection = 'malware'; -const ProtectionRadio = React.memo(({ id, label }: { id: ProtectionModes; label: string }) => { - const policyDetailsConfig = usePolicyDetailsSelector(policyConfig); - const dispatch = useDispatch(); - const radioButtonId = useMemo(() => htmlIdGenerator()(), []); - // currently just taking windows.malware, but both windows.malware and mac.malware should be the same value - const selected = policyDetailsConfig && policyDetailsConfig.windows.malware.mode; - const isPlatinumPlus = useLicense().isPlatinumPlus(); +const ProtectionRadio = React.memo( + ({ protectionMode, label }: { protectionMode: ProtectionModes; label: string }) => { + const policyDetailsConfig = usePolicyDetailsSelector(policyConfig); + const dispatch = useDispatch<(action: AppAction) => void>(); + const radioButtonId = useMemo(() => htmlIdGenerator()(), []); + // currently just taking windows.malware, but both windows.malware and mac.malware should be the same value + const selected = policyDetailsConfig && policyDetailsConfig.windows.malware.mode; + const isPlatinumPlus = useLicense().isPlatinumPlus(); - const handleRadioChange = useCallback(() => { - if (policyDetailsConfig) { - const newPayload = cloneDeep(policyDetailsConfig); - for (const os of OSes) { - newPayload[os][protection].mode = id; - if (isPlatinumPlus) { - if (id === ProtectionModes.prevent) { - newPayload[os].popup[protection].enabled = true; - } else { - newPayload[os].popup[protection].enabled = false; + const handleRadioChange = useCallback(() => { + if (policyDetailsConfig) { + const newPayload = cloneDeep(policyDetailsConfig); + for (const os of OSes) { + newPayload[os][protection].mode = protectionMode; + if (isPlatinumPlus) { + if (protectionMode === ProtectionModes.prevent) { + newPayload[os].popup[protection].enabled = true; + } else { + newPayload[os].popup[protection].enabled = false; + } } } + dispatch({ + type: 'userChangedPolicyConfig', + payload: { policyConfig: newPayload }, + }); } - dispatch({ - type: 'userChangedPolicyConfig', - payload: { policyConfig: newPayload }, - }); - } - }, [dispatch, id, policyDetailsConfig, isPlatinumPlus]); + }, [dispatch, protectionMode, policyDetailsConfig, isPlatinumPlus]); - /** - * Passing an arbitrary id because EuiRadio - * requires an id if label is passed - */ + /** + * Passing an arbitrary id because EuiRadio + * requires an id if label is passed + */ - return ( - - ); -}); - -ProtectionRadio.displayName = 'ProtectionRadio'; - -const SupportedVersionNotice = ({ optionName }: { optionName: string }) => { - const version = popupVersionsMap.get(optionName); - if (!version) { - return null; + return ( + + ); } +); - return ( - - - - - - ); -}; +ProtectionRadio.displayName = 'ProtectionRadio'; /** The Malware Protections form for policy details * which will configure for all relevant OSes. */ export const MalwareProtections = React.memo(() => { const policyDetailsConfig = usePolicyDetailsSelector(policyConfig); - const dispatch = useDispatch(); + const dispatch = useDispatch<(action: AppAction) => void>(); // currently just taking windows.malware, but both windows.malware and mac.malware should be the same value const selected = policyDetailsConfig && policyDetailsConfig.windows.malware.mode; const userNotificationSelected = @@ -224,19 +209,25 @@ export const MalwareProtections = React.memo(() => { /> - - {radios.map((radio) => { - return ( - - ); - })} - + + + + + + + + {isPlatinumPlus && ( <> + { @@ -327,7 +319,7 @@ export const MalwareProtections = React.memo(() => { label={i18n.translate( 'xpack.securitySolution.endpoint.policy.details.malwareProtectionsEnabled', { - defaultMessage: 'Malware Protections {mode, select, true {Enabled} false {Disabled}}', + defaultMessage: 'Malware protections {mode, select, true {enabled} false {disabled}}', values: { mode: selected !== ProtectionModes.off, }, diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/popup_options_to_versions.ts b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/popup_options_to_versions.ts index d4c7d0102ebd4..795f7dda52499 100644 --- a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/popup_options_to_versions.ts +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/popup_options_to_versions.ts @@ -4,6 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -const popupVersions: Array<[string, string]> = [['malware', '7.11+']]; +const popupVersions: Array<[string, string]> = [ + ['malware', '7.11+'], + ['ransomware', '7.12+'], +]; export const popupVersionsMap: ReadonlyMap = new Map(popupVersions); diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/ransomware.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/ransomware.tsx new file mode 100644 index 0000000000000..eb2dd4b2fe8d8 --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/ransomware.tsx @@ -0,0 +1,346 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useCallback, useMemo } from 'react'; +import { useDispatch } from 'react-redux'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiCallOut, + EuiCheckbox, + EuiRadio, + EuiSpacer, + EuiSwitch, + EuiText, + EuiTextArea, + htmlIdGenerator, + EuiFlexGroup, + EuiFlexItem, + EuiIconTip, + EuiCheckboxProps, + EuiRadioProps, + EuiSwitchProps, +} from '@elastic/eui'; +import { cloneDeep } from 'lodash'; +import { APP_ID } from '../../../../../../../common/constants'; +import { SecurityPageName } from '../../../../../../app/types'; +import { + Immutable, + OperatingSystem, + ProtectionModes, +} from '../../../../../../../common/endpoint/types'; +import { RansomwareProtectionOSes, OS } from '../../../types'; +import { ConfigForm, ConfigFormHeading } from '../../components/config_form'; +import { policyConfig } from '../../../store/policy_details/selectors'; +import { usePolicyDetailsSelector } from '../../policy_hooks'; +import { LinkToApp } from '../../../../../../common/components/endpoint/link_to_app'; +import { AppAction } from '../../../../../../common/store/actions'; +import { SupportedVersionNotice } from './supported_version'; +import { RadioFlexGroup } from './malware'; + +const OSes: Immutable = [OS.windows, OS.mac]; +const protection = 'ransomware'; + +const ProtectionRadio = React.memo( + ({ protectionMode, label }: { protectionMode: ProtectionModes; label: string }) => { + const policyDetailsConfig = usePolicyDetailsSelector(policyConfig); + const dispatch = useDispatch<(action: AppAction) => void>(); + const radioButtonId = useMemo(() => htmlIdGenerator()(), []); + // currently just taking windows.ransomware, but both windows.ransomware and mac.ransomware should be the same value + const selected = policyDetailsConfig && policyDetailsConfig.windows.ransomware.mode; + + const handleRadioChange: EuiRadioProps['onChange'] = useCallback(() => { + if (policyDetailsConfig) { + const newPayload = cloneDeep(policyDetailsConfig); + for (const os of OSes) { + newPayload[os][protection].mode = protectionMode; + if (protectionMode === ProtectionModes.prevent) { + newPayload[os].popup[protection].enabled = true; + } else { + newPayload[os].popup[protection].enabled = false; + } + } + dispatch({ + type: 'userChangedPolicyConfig', + payload: { policyConfig: newPayload }, + }); + } + }, [dispatch, protectionMode, policyDetailsConfig]); + + /** + * Passing an arbitrary id because EuiRadio + * requires an id if label is passed + */ + + return ( + + ); + } +); + +ProtectionRadio.displayName = 'ProtectionRadio'; + +/** The Ransomware Protections form for policy details + * which will configure for all relevant OSes. + */ +export const Ransomware = React.memo(() => { + const policyDetailsConfig = usePolicyDetailsSelector(policyConfig); + const dispatch = useDispatch<(action: AppAction) => void>(); + // currently just taking windows.ransomware, but both windows.ransomware and mac.ransomware should be the same value + const selected = policyDetailsConfig && policyDetailsConfig.windows.ransomware.mode; + const userNotificationSelected = + policyDetailsConfig && policyDetailsConfig.windows.popup.ransomware.enabled; + const userNotificationMessage = + policyDetailsConfig && policyDetailsConfig.windows.popup.ransomware.message; + + const radios: Immutable< + Array<{ + id: ProtectionModes; + label: string; + protection: 'ransomware'; + }> + > = useMemo(() => { + return [ + { + id: ProtectionModes.detect, + label: i18n.translate('xpack.securitySolution.endpoint.policy.details.detect', { + defaultMessage: 'Detect', + }), + protection: 'ransomware', + }, + { + id: ProtectionModes.prevent, + label: i18n.translate('xpack.securitySolution.endpoint.policy.details.prevent', { + defaultMessage: 'Prevent', + }), + protection: 'ransomware', + }, + ]; + }, []); + + const handleSwitchChange: EuiSwitchProps['onChange'] = useCallback( + (event) => { + if (policyDetailsConfig) { + const newPayload = cloneDeep(policyDetailsConfig); + if (event.target.checked === false) { + for (const os of OSes) { + newPayload[os][protection].mode = ProtectionModes.off; + newPayload[os].popup[protection].enabled = event.target.checked; + } + } else { + for (const os of OSes) { + newPayload[os][protection].mode = ProtectionModes.prevent; + newPayload[os].popup[protection].enabled = event.target.checked; + } + } + dispatch({ + type: 'userChangedPolicyConfig', + payload: { policyConfig: newPayload }, + }); + } + }, + [dispatch, policyDetailsConfig] + ); + + const handleUserNotificationCheckbox: EuiCheckboxProps['onChange'] = useCallback( + (event) => { + if (policyDetailsConfig) { + const newPayload = cloneDeep(policyDetailsConfig); + for (const os of OSes) { + newPayload[os].popup[protection].enabled = event.target.checked; + } + dispatch({ + type: 'userChangedPolicyConfig', + payload: { policyConfig: newPayload }, + }); + } + }, + [policyDetailsConfig, dispatch] + ); + + const handleCustomUserNotification = useCallback( + (event) => { + if (policyDetailsConfig) { + const newPayload = cloneDeep(policyDetailsConfig); + for (const os of OSes) { + newPayload[os].popup[protection].message = event.target.value; + } + dispatch({ + type: 'userChangedPolicyConfig', + payload: { policyConfig: newPayload }, + }); + } + }, + [policyDetailsConfig, dispatch] + ); + + const radioButtons = useMemo(() => { + return ( + <> + + + + + + + + + + + + + + + + + + + + {userNotificationSelected && ( + <> + + + + +

+ +

+
+
+ + + + + + + } + /> + +
+ + + + )} + + ); + }, [ + radios, + selected, + handleUserNotificationCheckbox, + userNotificationSelected, + userNotificationMessage, + handleCustomUserNotification, + ]); + + const protectionSwitch = useMemo(() => { + return ( + + ); + }, [handleSwitchChange, selected]); + + return ( + + {radioButtons} + + + + + + ), + }} + /> + + + ); +}); + +Ransomware.displayName = 'RansomwareProtections'; diff --git a/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/supported_version.tsx b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/supported_version.tsx new file mode 100644 index 0000000000000..dee6418b4f3ff --- /dev/null +++ b/x-pack/plugins/security_solution/public/management/pages/policy/view/policy_forms/protections/supported_version.tsx @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiText } from '@elastic/eui'; +import { popupVersionsMap } from './popup_options_to_versions'; + +export const SupportedVersionNotice = ({ optionName }: { optionName: string }) => { + const version = popupVersionsMap.get(optionName); + if (!version) { + return null; + } + + return ( + + + + + + ); +}; diff --git a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts index ec3d35cbb6585..7f9e8b42490fa 100644 --- a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts +++ b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts @@ -124,6 +124,7 @@ export class EndpointAppContextService { dependencies.config.maxTimelineImportExportSize, dependencies.security, dependencies.alerts, + dependencies.licenseService, dependencies.exceptionListsClient ) ); diff --git a/x-pack/plugins/security_solution/server/endpoint/ingest_integration.test.ts b/x-pack/plugins/security_solution/server/endpoint/ingest_integration.test.ts index d287ada74eebc..2710e4afb5968 100644 --- a/x-pack/plugins/security_solution/server/endpoint/ingest_integration.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/ingest_integration.test.ts @@ -6,7 +6,10 @@ import { httpServerMock, loggingSystemMock } from 'src/core/server/mocks'; import { createNewPackagePolicyMock } from '../../../fleet/common/mocks'; -import { factory as policyConfigFactory } from '../../common/endpoint/models/policy_config'; +import { + policyFactory, + policyFactoryWithoutPaidFeatures, +} from '../../common/endpoint/models/policy_config'; import { getManifestManagerMock, ManifestManagerMockType, @@ -55,6 +58,9 @@ describe('ingest_integration tests ', () => { }); describe('ingest_integration sanity checks', () => { + beforeEach(() => { + licenseEmitter.next(Platinum); // set license level to platinum + }); test('policy is updated with initial manifest', async () => { const logger = loggingSystemMock.create().get('ingest_integration.test'); const manifestManager = getManifestManagerMock({ @@ -68,13 +74,14 @@ describe('ingest_integration tests ', () => { maxTimelineImportExportSize, endpointAppContextMock.security, endpointAppContextMock.alerts, + licenseService, exceptionListClient ); const policyConfig = createNewPackagePolicyMock(); // policy config without manifest const newPolicyConfig = await callback(policyConfig, ctx, req); // policy config WITH manifest expect(newPolicyConfig.inputs[0]!.type).toEqual('endpoint'); - expect(newPolicyConfig.inputs[0]!.config!.policy.value).toEqual(policyConfigFactory()); + expect(newPolicyConfig.inputs[0]!.config!.policy.value).toEqual(policyFactory()); expect(newPolicyConfig.inputs[0]!.config!.artifact_manifest.value).toEqual({ artifacts: { 'endpoint-exceptionlist-macos-v1': { @@ -146,13 +153,14 @@ describe('ingest_integration tests ', () => { maxTimelineImportExportSize, endpointAppContextMock.security, endpointAppContextMock.alerts, + licenseService, exceptionListClient ); const policyConfig = createNewPackagePolicyMock(); const newPolicyConfig = await callback(policyConfig, ctx, req); expect(newPolicyConfig.inputs[0]!.type).toEqual('endpoint'); - expect(newPolicyConfig.inputs[0]!.config!.policy.value).toEqual(policyConfigFactory()); + expect(newPolicyConfig.inputs[0]!.config!.policy.value).toEqual(policyFactory()); expect(newPolicyConfig.inputs[0]!.config!.artifact_manifest.value).toEqual( lastComputed!.toEndpointFormat() ); @@ -174,13 +182,14 @@ describe('ingest_integration tests ', () => { maxTimelineImportExportSize, endpointAppContextMock.security, endpointAppContextMock.alerts, + licenseService, exceptionListClient ); const policyConfig = createNewPackagePolicyMock(); const newPolicyConfig = await callback(policyConfig, ctx, req); expect(newPolicyConfig.inputs[0]!.type).toEqual('endpoint'); - expect(newPolicyConfig.inputs[0]!.config!.policy.value).toEqual(policyConfigFactory()); + expect(newPolicyConfig.inputs[0]!.config!.policy.value).toEqual(policyFactory()); }); test('subsequent policy creations succeed', async () => { @@ -196,13 +205,14 @@ describe('ingest_integration tests ', () => { maxTimelineImportExportSize, endpointAppContextMock.security, endpointAppContextMock.alerts, + licenseService, exceptionListClient ); const policyConfig = createNewPackagePolicyMock(); const newPolicyConfig = await callback(policyConfig, ctx, req); expect(newPolicyConfig.inputs[0]!.type).toEqual('endpoint'); - expect(newPolicyConfig.inputs[0]!.config!.policy.value).toEqual(policyConfigFactory()); + expect(newPolicyConfig.inputs[0]!.config!.policy.value).toEqual(policyFactory()); expect(newPolicyConfig.inputs[0]!.config!.artifact_manifest.value).toEqual( lastComputed!.toEndpointFormat() ); @@ -221,6 +231,7 @@ describe('ingest_integration tests ', () => { maxTimelineImportExportSize, endpointAppContextMock.security, endpointAppContextMock.alerts, + licenseService, exceptionListClient ); const policyConfig = createNewPackagePolicyMock(); @@ -228,7 +239,7 @@ describe('ingest_integration tests ', () => { expect(exceptionListClient.createEndpointList).toHaveBeenCalled(); expect(newPolicyConfig.inputs[0]!.type).toEqual('endpoint'); - expect(newPolicyConfig.inputs[0]!.config!.policy.value).toEqual(policyConfigFactory()); + expect(newPolicyConfig.inputs[0]!.config!.policy.value).toEqual(policyFactory()); expect(newPolicyConfig.inputs[0]!.config!.artifact_manifest.value).toEqual( lastComputed!.toEndpointFormat() ); @@ -239,8 +250,7 @@ describe('ingest_integration tests ', () => { licenseEmitter.next(Gold); // set license level to gold }); it('returns an error if paid features are turned on in the policy', async () => { - const mockPolicy = policyConfigFactory(); - mockPolicy.windows.popup.malware.message = 'paid feature'; + const mockPolicy = policyFactory(); // defaults with paid features on const logger = loggingSystemMock.create().get('ingest_integration.test'); const callback = getPackagePolicyUpdateCallback(logger, licenseService); const policyConfig = generator.generatePolicyPackagePolicy(); @@ -250,7 +260,7 @@ describe('ingest_integration tests ', () => { ); }); it('updates successfully if no paid features are turned on in the policy', async () => { - const mockPolicy = policyConfigFactory(); + const mockPolicy = policyFactoryWithoutPaidFeatures(); mockPolicy.windows.malware.mode = ProtectionModes.detect; const logger = loggingSystemMock.create().get('ingest_integration.test'); const callback = getPackagePolicyUpdateCallback(logger, licenseService); @@ -265,7 +275,7 @@ describe('ingest_integration tests ', () => { licenseEmitter.next(Platinum); // set license level to platinum }); it('updates successfully when paid features are turned on', async () => { - const mockPolicy = policyConfigFactory(); + const mockPolicy = policyFactory(); mockPolicy.windows.popup.malware.message = 'paid feature'; const logger = loggingSystemMock.create().get('ingest_integration.test'); const callback = getPackagePolicyUpdateCallback(logger, licenseService); diff --git a/x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts b/x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts index 1e7f440ed6788..114c6ba969227 100644 --- a/x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts +++ b/x-pack/plugins/security_solution/server/endpoint/ingest_integration.ts @@ -10,7 +10,10 @@ import { SecurityPluginSetup } from '../../../security/server'; import { ExternalCallback } from '../../../fleet/server'; import { KibanaRequest, Logger, RequestHandlerContext } from '../../../../../src/core/server'; import { NewPackagePolicy, UpdatePackagePolicy } from '../../../fleet/common/types/models'; -import { factory as policyConfigFactory } from '../../common/endpoint/models/policy_config'; +import { + policyFactory as policyConfigFactory, + policyFactoryWithoutPaidFeatures as policyConfigFactoryWithoutPaidFeatures, +} from '../../common/endpoint/models/policy_config'; import { NewPolicyData } from '../../common/endpoint/types'; import { ManifestManager } from './services/artifacts'; import { Manifest } from './lib/artifacts'; @@ -22,7 +25,7 @@ import { createDetectionIndex } from '../lib/detection_engine/routes/index/creat import { createPrepackagedRules } from '../lib/detection_engine/routes/rules/add_prepackaged_rules_route'; import { buildFrameworkRequest } from '../lib/timeline/routes/utils/common'; import { isEndpointPolicyValidForLicense } from '../../common/license/policy_config'; -import { LicenseService } from '../../common/license/license'; +import { isAtLeast, LicenseService } from '../../common/license/license'; const getManifest = async (logger: Logger, manifestManager: ManifestManager): Promise => { let manifest: Manifest | null = null; @@ -86,6 +89,7 @@ export const getPackagePolicyCreateCallback = ( maxTimelineImportExportSize: number, securitySetup: SecurityPluginSetup, alerts: AlertsStartContract, + licenseService: LicenseService, exceptionsClient: ExceptionListClient | undefined ): ExternalCallback[1] => { const handlePackagePolicyCreate = async ( @@ -151,6 +155,12 @@ export const getPackagePolicyCreateCallback = ( // Until we get the Default Policy Configuration in the Endpoint package, // we will add it here manually at creation time. + + // generate the correct default policy depending on the license + const defaultPolicy = isAtLeast(licenseService.getLicenseInformation(), 'platinum') + ? policyConfigFactory() + : policyConfigFactoryWithoutPaidFeatures(); + updatedPackagePolicy = { ...newPackagePolicy, inputs: [ @@ -163,7 +173,7 @@ export const getPackagePolicyCreateCallback = ( value: serializedManifest, }, policy: { - value: policyConfigFactory(), + value: defaultPolicy, }, }, }, diff --git a/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts b/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts index 225592fa8e686..8e7c4d2d4daf5 100644 --- a/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts +++ b/x-pack/plugins/security_solution/server/endpoint/lib/policy/license_watch.test.ts @@ -18,7 +18,7 @@ import { licenseMock } from '../../../../../licensing/common/licensing.mock'; import { PackagePolicyServiceInterface } from '../../../../../fleet/server'; import { PackagePolicy } from '../../../../../fleet/common'; import { createPackagePolicyMock } from '../../../../../fleet/common/mocks'; -import { factory } from '../../../../common/endpoint/models/policy_config'; +import { policyFactory } from '../../../../common/endpoint/models/policy_config'; import { PolicyConfig } from '../../../../common/endpoint/types'; const MockPPWithEndpointPolicy = (cb?: (p: PolicyConfig) => PolicyConfig): PackagePolicy => { @@ -27,7 +27,7 @@ const MockPPWithEndpointPolicy = (cb?: (p: PolicyConfig) => PolicyConfig): Packa // eslint-disable-next-line no-param-reassign cb = (p) => p; } - const policyConfig = cb(factory()); + const policyConfig = cb(policyFactory()); packagePolicy.inputs[0].config = { policy: { value: policyConfig } }; return packagePolicy; }; diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/endpoint.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/endpoint.json index 711050e1f136a..acc5f69358798 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/endpoint.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/endpoint.json @@ -1 +1 @@ -{"savedObjectId":null,"version":null,"columns":[{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"@timestamp","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"signal.rule.description","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"event.action","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.name","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"The working directory of the process.","columnHeaderType":"not-filtered","id":"process.working_directory","category":"process","type":"string","searchable":null,"example":"/home/alice"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","searchable":null,"example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.pid","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Absolute path to the process executable.","columnHeaderType":"not-filtered","id":"process.parent.executable","category":"process","type":"string","searchable":null,"example":"/usr/bin/ssh"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.parent.args","category":"process","type":"string","searchable":null,"example":"[\"ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"aggregatable":true,"name":null,"description":"Process id.","columnHeaderType":"not-filtered","id":"process.parent.pid","category":"process","type":"number","searchable":null,"example":"4242"},{"indexes":null,"aggregatable":true,"name":null,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","searchable":null,"example":"albert"},{"indexes":null,"aggregatable":true,"name":null,"description":"Name of the host.\n\nIt can contain what `hostname` returns on Unix systems, the fully qualified\ndomain name, or a name specified by the user. The sender decides which value\nto use.","columnHeaderType":"not-filtered","id":"host.name","category":"host","type":"string","searchable":null}],"dataProviders":[{"excluded":false,"and":[],"kqlQuery":"","name":"{process.name}","queryMatch":{"displayValue":"{agent.type}","field":"agent.type","displayField":"agent.type","value":"{agent.type}","operator":":*"},"id":"timeline-1-8622010a-61fb-490d-b162-beac9c36a853","type":"template","enabled":true},{"excluded":false,"and":[],"kqlQuery":"","name":"","queryMatch":{"displayValue":"endpoint","field":"agent.type","displayField":"agent.type","value":"endpoint","operator":":"},"id":"timeline-1-4685da24-35c1-43f3-892d-1f926dbf5568","type":"default","enabled":true}],"description":"","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"title":"Generic Endpoint Timeline","timelineType":"template","templateTimelineVersion":1,"templateTimelineId":"db366523-f1c6-4c1f-8731-6ce5ed9e5717","dateRange":{"start":1588161020848,"end":1588162280848},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"created":1594735857110,"createdBy":"Elastic","updated":1594736314036,"updatedBy":"Elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} +{"savedObjectId":null,"version":null,"columns":[{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"@timestamp","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"signal.rule.description","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"event.action","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.name","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"The working directory of the process.","columnHeaderType":"not-filtered","id":"process.working_directory","category":"process","type":"string","searchable":null,"example":"/home/alice"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","searchable":null,"example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.pid","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Absolute path to the process executable.","columnHeaderType":"not-filtered","id":"process.parent.executable","category":"process","type":"string","searchable":null,"example":"/usr/bin/ssh"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.parent.args","category":"process","type":"string","searchable":null,"example":"[\"ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"aggregatable":true,"name":null,"description":"Process id.","columnHeaderType":"not-filtered","id":"process.parent.pid","category":"process","type":"number","searchable":null,"example":"4242"},{"indexes":null,"aggregatable":true,"name":null,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","searchable":null,"example":"albert"},{"indexes":null,"aggregatable":true,"name":null,"description":"Name of the host.\n\nIt can contain what `hostname` returns on Unix systems, the fully qualified\ndomain name, or a name specified by the user. The sender decides which value\nto use.","columnHeaderType":"not-filtered","id":"host.name","category":"host","type":"string","searchable":null}],"dataProviders":[{"excluded":false,"and":[],"kqlQuery":"","name":"","queryMatch":{"displayValue":"endpoint","field":"agent.type","displayField":"agent.type","value":"endpoint","operator":":"},"id":"timeline-1-4685da24-35c1-43f3-892d-1f926dbf5568","type":"default","enabled":true}],"description":"","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"title":"Generic Endpoint Timeline","timelineType":"template","templateTimelineVersion":2,"templateTimelineId":"db366523-f1c6-4c1f-8731-6ce5ed9e5717","dateRange":{"start":1588161020848,"end":1588162280848},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"created":1594735857110,"createdBy":"Elastic","updated":1611609999115,"updatedBy":"Elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/index.ndjson b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/index.ndjson index 7c074242c39d1..522430205c25a 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/index.ndjson +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/index.ndjson @@ -7,6 +7,6 @@ // Auto generated file from scripts/regen_prepackage_timelines_index.sh // Do not hand edit. Run that script to regenerate package information instead -{"savedObjectId":null,"version":null,"columns":[{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"@timestamp","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"signal.rule.description","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"event.action","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.name","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"The working directory of the process.","columnHeaderType":"not-filtered","id":"process.working_directory","category":"process","type":"string","searchable":null,"example":"/home/alice"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","searchable":null,"example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.pid","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Absolute path to the process executable.","columnHeaderType":"not-filtered","id":"process.parent.executable","category":"process","type":"string","searchable":null,"example":"/usr/bin/ssh"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.parent.args","category":"process","type":"string","searchable":null,"example":"[\"ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"aggregatable":true,"name":null,"description":"Process id.","columnHeaderType":"not-filtered","id":"process.parent.pid","category":"process","type":"number","searchable":null,"example":"4242"},{"indexes":null,"aggregatable":true,"name":null,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","searchable":null,"example":"albert"},{"indexes":null,"aggregatable":true,"name":null,"description":"Name of the host.\n\nIt can contain what `hostname` returns on Unix systems, the fully qualified\ndomain name, or a name specified by the user. The sender decides which value\nto use.","columnHeaderType":"not-filtered","id":"host.name","category":"host","type":"string","searchable":null}],"dataProviders":[{"excluded":false,"and":[],"kqlQuery":"","name":"{process.name}","queryMatch":{"displayValue":"{agent.type}","field":"agent.type","displayField":"agent.type","value":"{agent.type}","operator":":*"},"id":"timeline-1-8622010a-61fb-490d-b162-beac9c36a853","type":"template","enabled":true},{"excluded":false,"and":[],"kqlQuery":"","name":"","queryMatch":{"displayValue":"endpoint","field":"agent.type","displayField":"agent.type","value":"endpoint","operator":":"},"id":"timeline-1-4685da24-35c1-43f3-892d-1f926dbf5568","type":"default","enabled":true}],"description":"","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"title":"Generic Endpoint Timeline","timelineType":"template","templateTimelineVersion":1,"templateTimelineId":"db366523-f1c6-4c1f-8731-6ce5ed9e5717","dateRange":{"start":1588161020848,"end":1588162280848},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"created":1594735857110,"createdBy":"Elastic","updated":1594736314036,"updatedBy":"Elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} -{"savedObjectId":null,"version":null,"columns":[{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"@timestamp","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"signal.rule.description","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"The action captured by the event.\n\nThis describes the information in the event. It is more specific than `event.category`.\nExamples are `group-add`, `process-started`, `file-created`. The value is\nnormally defined by the implementer.","columnHeaderType":"not-filtered","id":"event.action","category":"event","type":"string","searchable":null,"example":"user-password-change"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","searchable":null,"example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.pid","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"IP address of the source (IPv4 or IPv6).","columnHeaderType":"not-filtered","id":"source.ip","category":"source","type":"ip","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Port of the source.","columnHeaderType":"not-filtered","id":"source.port","category":"source","type":"number","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"IP address of the destination (IPv4 or IPv6).","columnHeaderType":"not-filtered","id":"destination.ip","category":"destination","type":"ip","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"destination.port","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","searchable":null,"example":"albert"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"host.name","searchable":null}],"dataProviders":[{"excluded":false,"and":[],"kqlQuery":"","name":"{source.ip}","queryMatch":{"displayValue":null,"field":"source.ip","displayField":null,"value":"{source.ip}","operator":":*"},"id":"timeline-1-ec778f01-1802-40f0-9dfb-ed8de1f656cb","type":"template","enabled":true},{"excluded":false,"and":[],"kqlQuery":"","name":"{destination.ip}","queryMatch":{"displayValue":null,"field":"destination.ip","displayField":null,"value":"{destination.ip}","operator":":*"},"id":"timeline-1-e37e37c5-a6e7-4338-af30-47bfbc3c0e1e","type":"template","enabled":true}],"description":"","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"title":"Generic Network Timeline","timelineType":"template","templateTimelineVersion":1,"templateTimelineId":"91832785-286d-4ebe-b884-1a208d111a70","dateRange":{"start":1588255858373,"end":1588256218373},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"created":1594735573866,"createdBy":"Elastic","updated":1594736099397,"updatedBy":"Elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} -{"savedObjectId":null,"version":null,"columns":[{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"@timestamp","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"signal.rule.description","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"event.action","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.name","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"The working directory of the process.","columnHeaderType":"not-filtered","id":"process.working_directory","category":"process","type":"string","searchable":null,"example":"/home/alice"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","searchable":null,"example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.pid","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Absolute path to the process executable.","columnHeaderType":"not-filtered","id":"process.parent.executable","category":"process","type":"string","searchable":null,"example":"/usr/bin/ssh"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.parent.args","category":"process","type":"string","searchable":null,"example":"[\"ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"aggregatable":true,"name":null,"description":"Process id.","columnHeaderType":"not-filtered","id":"process.parent.pid","category":"process","type":"number","searchable":null,"example":"4242"},{"indexes":null,"aggregatable":true,"name":null,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","searchable":null,"example":"albert"},{"indexes":null,"aggregatable":true,"name":null,"description":"Name of the host.\n\nIt can contain what `hostname` returns on Unix systems, the fully qualified\ndomain name, or a name specified by the user. The sender decides which value\nto use.","columnHeaderType":"not-filtered","id":"host.name","category":"host","type":"string","searchable":null}],"dataProviders":[{"excluded":false,"and":[],"kqlQuery":"","name":"{process.name}","queryMatch":{"displayValue":null,"field":"process.name","displayField":null,"value":"{process.name}","operator":":"},"id":"timeline-1-8622010a-61fb-490d-b162-beac9c36a853","type":"template","enabled":true},{"excluded":false,"and":[],"kqlQuery":"","name":"{event.type}","queryMatch":{"displayValue":null,"field":"event.type","displayField":null,"value":"{event.type}","operator":":*"},"id":"timeline-1-4685da24-35c1-43f3-892d-1f926dbf5568","type":"template","enabled":true}],"description":"","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"title":"Generic Process Timeline","timelineType":"template","templateTimelineVersion":1,"templateTimelineId":"76e52245-7519-4251-91ab-262fb1a1728c","dateRange":{"start":1588161020848,"end":1588162280848},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"created":1594735629389,"createdBy":"Elastic","updated":1594736083598,"updatedBy":"Elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} +{"savedObjectId":null,"version":null,"columns":[{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"@timestamp","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"signal.rule.description","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"event.action","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.name","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"The working directory of the process.","columnHeaderType":"not-filtered","id":"process.working_directory","category":"process","type":"string","searchable":null,"example":"/home/alice"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","searchable":null,"example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.pid","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Absolute path to the process executable.","columnHeaderType":"not-filtered","id":"process.parent.executable","category":"process","type":"string","searchable":null,"example":"/usr/bin/ssh"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.parent.args","category":"process","type":"string","searchable":null,"example":"[\"ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"aggregatable":true,"name":null,"description":"Process id.","columnHeaderType":"not-filtered","id":"process.parent.pid","category":"process","type":"number","searchable":null,"example":"4242"},{"indexes":null,"aggregatable":true,"name":null,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","searchable":null,"example":"albert"},{"indexes":null,"aggregatable":true,"name":null,"description":"Name of the host.\n\nIt can contain what `hostname` returns on Unix systems, the fully qualified\ndomain name, or a name specified by the user. The sender decides which value\nto use.","columnHeaderType":"not-filtered","id":"host.name","category":"host","type":"string","searchable":null}],"dataProviders":[{"excluded":false,"and":[],"kqlQuery":"","name":"","queryMatch":{"displayValue":"endpoint","field":"agent.type","displayField":"agent.type","value":"endpoint","operator":":"},"id":"timeline-1-4685da24-35c1-43f3-892d-1f926dbf5568","type":"default","enabled":true}],"description":"","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"title":"Generic Endpoint Timeline","timelineType":"template","templateTimelineVersion":2,"templateTimelineId":"db366523-f1c6-4c1f-8731-6ce5ed9e5717","dateRange":{"start":1588161020848,"end":1588162280848},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"created":1594735857110,"createdBy":"Elastic","updated":1611609999115,"updatedBy":"Elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} +{"savedObjectId":null,"version":null,"columns":[{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"@timestamp","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"signal.rule.description","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"The action captured by the event.\n\nThis describes the information in the event. It is more specific than `event.category`.\nExamples are `group-add`, `process-started`, `file-created`. The value is\nnormally defined by the implementer.","columnHeaderType":"not-filtered","id":"event.action","category":"event","type":"string","searchable":null,"example":"user-password-change"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","searchable":null,"example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.pid","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"IP address of the source (IPv4 or IPv6).","columnHeaderType":"not-filtered","id":"source.ip","category":"source","type":"ip","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Port of the source.","columnHeaderType":"not-filtered","id":"source.port","category":"source","type":"number","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"IP address of the destination (IPv4 or IPv6).","columnHeaderType":"not-filtered","id":"destination.ip","category":"destination","type":"ip","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"destination.port","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","searchable":null,"example":"albert"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"host.name","searchable":null}],"dataProviders":[{"and":[{"enabled":true,"excluded":false,"id":"timeline-1-e37e37c5-a6e7-4338-af30-47bfbc3c0e1e","kqlQuery":"","name":"{destination.ip}","queryMatch":{"displayField":"destination.ip","displayValue":"{destination.ip}","field":"destination.ip","operator":":","value":"{destination.ip}"},"type":"template"}],"enabled":true,"excluded":false,"id":"timeline-1-ec778f01-1802-40f0-9dfb-ed8de1f656cb","kqlQuery":"","name":"{source.ip}","queryMatch":{"displayField":"source.ip","displayValue":"{source.ip}","field":"source.ip","operator":":","value":"{source.ip}"},"type":"template"}],"description":"","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"title":"Generic Network Timeline","timelineType":"template","templateTimelineVersion":2,"templateTimelineId":"91832785-286d-4ebe-b884-1a208d111a70","dateRange":{"start":1588255858373,"end":1588256218373},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"created":1594735573866,"createdBy":"Elastic","updated":1611609960850,"updatedBy":"Elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} +{"savedObjectId":null,"version":null,"columns":[{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"@timestamp","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"signal.rule.description","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"event.action","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.name","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"The working directory of the process.","columnHeaderType":"not-filtered","id":"process.working_directory","category":"process","type":"string","searchable":null,"example":"/home/alice"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","searchable":null,"example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.pid","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Absolute path to the process executable.","columnHeaderType":"not-filtered","id":"process.parent.executable","category":"process","type":"string","searchable":null,"example":"/usr/bin/ssh"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.parent.args","category":"process","type":"string","searchable":null,"example":"[\"ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"aggregatable":true,"name":null,"description":"Process id.","columnHeaderType":"not-filtered","id":"process.parent.pid","category":"process","type":"number","searchable":null,"example":"4242"},{"indexes":null,"aggregatable":true,"name":null,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","searchable":null,"example":"albert"},{"indexes":null,"aggregatable":true,"name":null,"description":"Name of the host.\n\nIt can contain what `hostname` returns on Unix systems, the fully qualified\ndomain name, or a name specified by the user. The sender decides which value\nto use.","columnHeaderType":"not-filtered","id":"host.name","category":"host","type":"string","searchable":null}],"dataProviders":[{"excluded":false,"and":[],"kqlQuery":"","name":"{process.name}","queryMatch":{"displayValue":null,"field":"process.name","displayField":null,"value":"{process.name}","operator":":"},"id":"timeline-1-8622010a-61fb-490d-b162-beac9c36a853","type":"template","enabled":true}],"description":"","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"title":"Generic Process Timeline","timelineType":"template","templateTimelineVersion":2,"templateTimelineId":"76e52245-7519-4251-91ab-262fb1a1728c","dateRange":{"start":1588161020848,"end":1588162280848},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"created":1594735629389,"createdBy":"Elastic","updated":1611609848602,"updatedBy":"Elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/network.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/network.json index 1634476b4e99d..6e93387579d22 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/network.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/network.json @@ -1 +1 @@ -{"savedObjectId":null,"version":null,"columns":[{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"@timestamp","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"signal.rule.description","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"The action captured by the event.\n\nThis describes the information in the event. It is more specific than `event.category`.\nExamples are `group-add`, `process-started`, `file-created`. The value is\nnormally defined by the implementer.","columnHeaderType":"not-filtered","id":"event.action","category":"event","type":"string","searchable":null,"example":"user-password-change"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","searchable":null,"example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.pid","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"IP address of the source (IPv4 or IPv6).","columnHeaderType":"not-filtered","id":"source.ip","category":"source","type":"ip","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Port of the source.","columnHeaderType":"not-filtered","id":"source.port","category":"source","type":"number","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"IP address of the destination (IPv4 or IPv6).","columnHeaderType":"not-filtered","id":"destination.ip","category":"destination","type":"ip","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"destination.port","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","searchable":null,"example":"albert"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"host.name","searchable":null}],"dataProviders":[{"excluded":false,"and":[],"kqlQuery":"","name":"{source.ip}","queryMatch":{"displayValue":null,"field":"source.ip","displayField":null,"value":"{source.ip}","operator":":*"},"id":"timeline-1-ec778f01-1802-40f0-9dfb-ed8de1f656cb","type":"template","enabled":true},{"excluded":false,"and":[],"kqlQuery":"","name":"{destination.ip}","queryMatch":{"displayValue":null,"field":"destination.ip","displayField":null,"value":"{destination.ip}","operator":":*"},"id":"timeline-1-e37e37c5-a6e7-4338-af30-47bfbc3c0e1e","type":"template","enabled":true}],"description":"","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"title":"Generic Network Timeline","timelineType":"template","templateTimelineVersion":1,"templateTimelineId":"91832785-286d-4ebe-b884-1a208d111a70","dateRange":{"start":1588255858373,"end":1588256218373},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"created":1594735573866,"createdBy":"Elastic","updated":1594736099397,"updatedBy":"Elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} +{"savedObjectId":null,"version":null,"columns":[{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"@timestamp","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"signal.rule.description","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"The action captured by the event.\n\nThis describes the information in the event. It is more specific than `event.category`.\nExamples are `group-add`, `process-started`, `file-created`. The value is\nnormally defined by the implementer.","columnHeaderType":"not-filtered","id":"event.action","category":"event","type":"string","searchable":null,"example":"user-password-change"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","searchable":null,"example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.pid","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"IP address of the source (IPv4 or IPv6).","columnHeaderType":"not-filtered","id":"source.ip","category":"source","type":"ip","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Port of the source.","columnHeaderType":"not-filtered","id":"source.port","category":"source","type":"number","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"IP address of the destination (IPv4 or IPv6).","columnHeaderType":"not-filtered","id":"destination.ip","category":"destination","type":"ip","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"destination.port","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","searchable":null,"example":"albert"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"host.name","searchable":null}],"dataProviders":[{"and":[{"enabled":true,"excluded":false,"id":"timeline-1-e37e37c5-a6e7-4338-af30-47bfbc3c0e1e","kqlQuery":"","name":"{destination.ip}","queryMatch":{"displayField":"destination.ip","displayValue":"{destination.ip}","field":"destination.ip","operator":":","value":"{destination.ip}"},"type":"template"}],"enabled":true,"excluded":false,"id":"timeline-1-ec778f01-1802-40f0-9dfb-ed8de1f656cb","kqlQuery":"","name":"{source.ip}","queryMatch":{"displayField":"source.ip","displayValue":"{source.ip}","field":"source.ip","operator":":","value":"{source.ip}"},"type":"template"}],"description":"","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"title":"Generic Network Timeline","timelineType":"template","templateTimelineVersion":2,"templateTimelineId":"91832785-286d-4ebe-b884-1a208d111a70","dateRange":{"start":1588255858373,"end":1588256218373},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"created":1594735573866,"createdBy":"Elastic","updated":1611609960850,"updatedBy":"Elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} diff --git a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/process.json b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/process.json index 767f38133f263..c25873746a9e9 100644 --- a/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/process.json +++ b/x-pack/plugins/security_solution/server/lib/detection_engine/rules/prepackaged_timelines/process.json @@ -1 +1 @@ -{"savedObjectId":null,"version":null,"columns":[{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"@timestamp","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"signal.rule.description","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"event.action","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.name","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"The working directory of the process.","columnHeaderType":"not-filtered","id":"process.working_directory","category":"process","type":"string","searchable":null,"example":"/home/alice"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","searchable":null,"example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.pid","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Absolute path to the process executable.","columnHeaderType":"not-filtered","id":"process.parent.executable","category":"process","type":"string","searchable":null,"example":"/usr/bin/ssh"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.parent.args","category":"process","type":"string","searchable":null,"example":"[\"ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"aggregatable":true,"name":null,"description":"Process id.","columnHeaderType":"not-filtered","id":"process.parent.pid","category":"process","type":"number","searchable":null,"example":"4242"},{"indexes":null,"aggregatable":true,"name":null,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","searchable":null,"example":"albert"},{"indexes":null,"aggregatable":true,"name":null,"description":"Name of the host.\n\nIt can contain what `hostname` returns on Unix systems, the fully qualified\ndomain name, or a name specified by the user. The sender decides which value\nto use.","columnHeaderType":"not-filtered","id":"host.name","category":"host","type":"string","searchable":null}],"dataProviders":[{"excluded":false,"and":[],"kqlQuery":"","name":"{process.name}","queryMatch":{"displayValue":null,"field":"process.name","displayField":null,"value":"{process.name}","operator":":"},"id":"timeline-1-8622010a-61fb-490d-b162-beac9c36a853","type":"template","enabled":true},{"excluded":false,"and":[],"kqlQuery":"","name":"{event.type}","queryMatch":{"displayValue":null,"field":"event.type","displayField":null,"value":"{event.type}","operator":":*"},"id":"timeline-1-4685da24-35c1-43f3-892d-1f926dbf5568","type":"template","enabled":true}],"description":"","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"title":"Generic Process Timeline","timelineType":"template","templateTimelineVersion":1,"templateTimelineId":"76e52245-7519-4251-91ab-262fb1a1728c","dateRange":{"start":1588161020848,"end":1588162280848},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"created":1594735629389,"createdBy":"Elastic","updated":1594736083598,"updatedBy":"Elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} +{"savedObjectId":null,"version":null,"columns":[{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"@timestamp","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"signal.rule.description","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"event.action","searchable":null},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.name","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"The working directory of the process.","columnHeaderType":"not-filtered","id":"process.working_directory","category":"process","type":"string","searchable":null,"example":"/home/alice"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments, starting with the absolute path to\nthe executable.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.args","category":"process","type":"string","searchable":null,"example":"[\"/usr/bin/ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"name":null,"columnHeaderType":"not-filtered","id":"process.pid","searchable":null},{"indexes":null,"aggregatable":true,"name":null,"description":"Absolute path to the process executable.","columnHeaderType":"not-filtered","id":"process.parent.executable","category":"process","type":"string","searchable":null,"example":"/usr/bin/ssh"},{"indexes":null,"aggregatable":true,"name":null,"description":"Array of process arguments.\n\nMay be filtered to protect sensitive information.","columnHeaderType":"not-filtered","id":"process.parent.args","category":"process","type":"string","searchable":null,"example":"[\"ssh\",\"-l\",\"user\",\"10.0.0.16\"]"},{"indexes":null,"aggregatable":true,"name":null,"description":"Process id.","columnHeaderType":"not-filtered","id":"process.parent.pid","category":"process","type":"number","searchable":null,"example":"4242"},{"indexes":null,"aggregatable":true,"name":null,"description":"Short name or login of the user.","columnHeaderType":"not-filtered","id":"user.name","category":"user","type":"string","searchable":null,"example":"albert"},{"indexes":null,"aggregatable":true,"name":null,"description":"Name of the host.\n\nIt can contain what `hostname` returns on Unix systems, the fully qualified\ndomain name, or a name specified by the user. The sender decides which value\nto use.","columnHeaderType":"not-filtered","id":"host.name","category":"host","type":"string","searchable":null}],"dataProviders":[{"excluded":false,"and":[],"kqlQuery":"","name":"{process.name}","queryMatch":{"displayValue":null,"field":"process.name","displayField":null,"value":"{process.name}","operator":":"},"id":"timeline-1-8622010a-61fb-490d-b162-beac9c36a853","type":"template","enabled":true}],"description":"","eventType":"all","filters":[],"kqlMode":"filter","kqlQuery":{"filterQuery":{"kuery":{"kind":"kuery","expression":""},"serializedQuery":""}},"title":"Generic Process Timeline","timelineType":"template","templateTimelineVersion":2,"templateTimelineId":"76e52245-7519-4251-91ab-262fb1a1728c","dateRange":{"start":1588161020848,"end":1588162280848},"savedQueryId":null,"sort":{"columnId":"@timestamp","sortDirection":"desc"},"created":1594735629389,"createdBy":"Elastic","updated":1611609848602,"updatedBy":"Elastic","eventNotes":[],"globalNotes":[],"pinnedEventIds":[],"status":"immutable"} diff --git a/x-pack/plugins/security_solution/server/ui_settings.ts b/x-pack/plugins/security_solution/server/ui_settings.ts index 548f718e1bc80..0d679cdefb92c 100644 --- a/x-pack/plugins/security_solution/server/ui_settings.ts +++ b/x-pack/plugins/security_solution/server/ui_settings.ts @@ -78,6 +78,8 @@ export const initUiSettings = (uiSettings: CoreSetup['uiSettings']) => { name: i18n.translate('xpack.securitySolution.uiSettings.defaultIndexLabel', { defaultMessage: 'Elasticsearch indices', }), + sensitive: true, + value: DEFAULT_INDEX_PATTERN, description: i18n.translate('xpack.securitySolution.uiSettings.defaultIndexDescription', { defaultMessage: @@ -147,6 +149,7 @@ export const initUiSettings = (uiSettings: CoreSetup['uiSettings']) => { defaultMessage: 'News feed URL', }), value: NEWS_FEED_URL_SETTING_DEFAULT, + sensitive: true, description: i18n.translate('xpack.securitySolution.uiSettings.newsFeedUrlDescription', { defaultMessage: '

News feed content will be retrieved from this URL

', }), @@ -167,6 +170,7 @@ export const initUiSettings = (uiSettings: CoreSetup['uiSettings']) => { 'Array of URL templates to build the list of reputation URLs to be displayed on the IP Details page.', } ), + sensitive: true, category: [APP_ID], requiresPageReload: true, schema: schema.arrayOf( diff --git a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/types.ts b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/types.ts index d1f64c9298f15..c67043106c0d1 100644 --- a/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/types.ts +++ b/x-pack/plugins/stack_alerts/public/alert_types/geo_containment/types.ts @@ -18,7 +18,6 @@ export interface GeoContainmentAlertParams extends AlertTypeParams { boundaryIndexId: string; boundaryGeoField: string; boundaryNameField?: string; - delayOffsetWithUnits?: string; indexQuery?: Query; boundaryIndexQuery?: Query; } diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/alert_type.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/alert_type.ts index 85e02b21cc78c..6b6f17bf4ba2a 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/alert_type.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/alert_type.ts @@ -98,7 +98,6 @@ export const ParamsSchema = schema.object({ boundaryIndexId: schema.string({ minLength: 1 }), boundaryGeoField: schema.string({ minLength: 1 }), boundaryNameField: schema.maybe(schema.string({ minLength: 1 })), - delayOffsetWithUnits: schema.maybe(schema.string({ minLength: 1 })), indexQuery: schema.maybe(schema.any({})), boundaryIndexQuery: schema.maybe(schema.any({})), }); @@ -114,7 +113,6 @@ export interface GeoContainmentParams extends AlertTypeParams { boundaryIndexId: string; boundaryGeoField: string; boundaryNameField?: string; - delayOffsetWithUnits?: string; indexQuery?: Query; boundaryIndexQuery?: Query; } diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/geo_containment.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/geo_containment.ts index 24232e47225f0..1648ad9ad2a62 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/geo_containment.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/geo_containment.ts @@ -24,7 +24,7 @@ export function transformResults( results: SearchResponse | undefined, dateField: string, geoField: string -): Map { +): Map { if (!results) { return new Map(); } @@ -64,12 +64,15 @@ export function transformResults( // Get unique .reduce( ( - accu: Map, + accu: Map, el: LatestEntityLocation & { entityName: string } ) => { const { entityName, ...locationData } = el; - if (!accu.has(entityName)) { - accu.set(entityName, locationData); + if (entityName) { + if (!accu.has(entityName)) { + accu.set(entityName, []); + } + accu.get(entityName)!.push(locationData); } return accu; }, @@ -78,26 +81,9 @@ export function transformResults( return orderedResults; } -function getOffsetTime(delayOffsetWithUnits: string, oldTime: Date): Date { - const timeUnit = delayOffsetWithUnits.slice(-1); - const time: number = +delayOffsetWithUnits.slice(0, -1); - - const adjustedDate = new Date(oldTime.getTime()); - if (timeUnit === 's') { - adjustedDate.setSeconds(adjustedDate.getSeconds() - time); - } else if (timeUnit === 'm') { - adjustedDate.setMinutes(adjustedDate.getMinutes() - time); - } else if (timeUnit === 'h') { - adjustedDate.setHours(adjustedDate.getHours() - time); - } else if (timeUnit === 'd') { - adjustedDate.setDate(adjustedDate.getDate() - time); - } - return adjustedDate; -} - export function getActiveEntriesAndGenerateAlerts( - prevLocationMap: Record, - currLocationMap: Map, + prevLocationMap: Map, + currLocationMap: Map, alertInstanceFactory: AlertServices< GeoContainmentInstanceState, GeoContainmentInstanceContext, @@ -106,32 +92,55 @@ export function getActiveEntriesAndGenerateAlerts( shapesIdsNamesMap: Record, currIntervalEndTime: Date ) { - const allActiveEntriesMap: Map = new Map([ - ...Object.entries(prevLocationMap || {}), + const allActiveEntriesMap: Map = new Map([ + ...prevLocationMap, ...currLocationMap, ]); - allActiveEntriesMap.forEach(({ location, shapeLocationId, dateInShape, docId }, entityName) => { - const containingBoundaryName = shapesIdsNamesMap[shapeLocationId] || shapeLocationId; - const context = { - entityId: entityName, - entityDateTime: dateInShape ? new Date(dateInShape).toISOString() : null, - entityDocumentId: docId, - detectionDateTime: new Date(currIntervalEndTime).toISOString(), - entityLocation: `POINT (${location[0]} ${location[1]})`, - containingBoundaryId: shapeLocationId, - containingBoundaryName, - }; - const alertInstanceId = `${entityName}-${containingBoundaryName}`; - if (shapeLocationId === OTHER_CATEGORY) { + allActiveEntriesMap.forEach((locationsArr, entityName) => { + // Generate alerts + locationsArr.forEach(({ location, shapeLocationId, dateInShape, docId }) => { + const context = { + entityId: entityName, + entityDateTime: dateInShape ? new Date(dateInShape).toISOString() : null, + entityDocumentId: docId, + detectionDateTime: new Date(currIntervalEndTime).toISOString(), + entityLocation: `POINT (${location[0]} ${location[1]})`, + containingBoundaryId: shapeLocationId, + containingBoundaryName: shapesIdsNamesMap[shapeLocationId] || shapeLocationId, + }; + const alertInstanceId = `${entityName}-${context.containingBoundaryName}`; + if (shapeLocationId !== OTHER_CATEGORY) { + alertInstanceFactory(alertInstanceId).scheduleActions(ActionGroupId, context); + } + }); + + if (locationsArr[0].shapeLocationId === OTHER_CATEGORY) { allActiveEntriesMap.delete(entityName); + return; + } + + const otherCatIndex = locationsArr.findIndex( + ({ shapeLocationId }) => shapeLocationId === OTHER_CATEGORY + ); + if (otherCatIndex >= 0) { + const afterOtherLocationsArr = locationsArr.slice(0, otherCatIndex); + allActiveEntriesMap.set(entityName, afterOtherLocationsArr); } else { - alertInstanceFactory(alertInstanceId).scheduleActions(ActionGroupId, context); + allActiveEntriesMap.set(entityName, locationsArr); } }); return allActiveEntriesMap; } + export const getGeoContainmentExecutor = (log: Logger): GeoContainmentAlertType['executor'] => - async function ({ previousStartedAt, startedAt, services, params, alertId, state }) { + async function ({ + previousStartedAt: currIntervalStartTime, + startedAt: currIntervalEndTime, + services, + params, + alertId, + state, + }) { const { shapesFilters, shapesIdsNamesMap } = state.shapesFilters ? state : await getShapesFilters( @@ -147,15 +156,6 @@ export const getGeoContainmentExecutor = (log: Logger): GeoContainmentAlertType[ const executeEsQuery = await executeEsQueryFactory(params, services, log, shapesFilters); - let currIntervalStartTime = previousStartedAt; - let currIntervalEndTime = startedAt; - if (params.delayOffsetWithUnits) { - if (currIntervalStartTime) { - currIntervalStartTime = getOffsetTime(params.delayOffsetWithUnits, currIntervalStartTime); - } - currIntervalEndTime = getOffsetTime(params.delayOffsetWithUnits, currIntervalEndTime); - } - // Start collecting data only on the first cycle let currentIntervalResults: SearchResponse | undefined; if (!currIntervalStartTime) { @@ -169,14 +169,17 @@ export const getGeoContainmentExecutor = (log: Logger): GeoContainmentAlertType[ currentIntervalResults = await executeEsQuery(currIntervalStartTime, currIntervalEndTime); } - const currLocationMap: Map = transformResults( + const currLocationMap: Map = transformResults( currentIntervalResults, params.dateField, params.geoField ); + const prevLocationMap: Map = new Map([ + ...Object.entries((state.prevLocationMap as Record) || {}), + ]); const allActiveEntriesMap = getActiveEntriesAndGenerateAlerts( - state.prevLocationMap as Record, + prevLocationMap, currLocationMap, services.alertInstanceFactory, shapesIdsNamesMap, diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/alert_type.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/alert_type.test.ts index 98842c8cc2cba..1aba7d6fb1010 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/alert_type.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/alert_type.test.ts @@ -38,7 +38,6 @@ describe('alertType', () => { boundaryIndexId: 'testIndex', boundaryGeoField: 'testField', boundaryNameField: 'testField', - delayOffsetWithUnits: 'testOffset', }; expect(alertType.validate?.params?.validate(params)).toBeTruthy(); diff --git a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/geo_containment.test.ts b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/geo_containment.test.ts index 26b51060c2e73..40ad6454c3673 100644 --- a/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/geo_containment.test.ts +++ b/x-pack/plugins/stack_alerts/server/alert_types/geo_containment/tests/geo_containment.test.ts @@ -27,39 +27,47 @@ describe('geo_containment', () => { new Map([ [ '936', - { - dateInShape: '2020-09-28T18:01:41.190Z', - docId: 'N-ng1XQB6yyY-xQxnGSM', - location: [-82.8814151789993, 40.62806099653244], - shapeLocationId: '0DrJu3QB6yyY-xQxv6Ip', - }, + [ + { + dateInShape: '2020-09-28T18:01:41.190Z', + docId: 'N-ng1XQB6yyY-xQxnGSM', + location: [-82.8814151789993, 40.62806099653244], + shapeLocationId: '0DrJu3QB6yyY-xQxv6Ip', + }, + ], ], [ 'AAL2019', - { - dateInShape: '2020-09-28T18:01:41.191Z', - docId: 'iOng1XQB6yyY-xQxnGSM', - location: [-82.22068064846098, 39.006176185794175], - shapeLocationId: '0DrJu3QB6yyY-xQxv6Ip', - }, + [ + { + dateInShape: '2020-09-28T18:01:41.191Z', + docId: 'iOng1XQB6yyY-xQxnGSM', + location: [-82.22068064846098, 39.006176185794175], + shapeLocationId: '0DrJu3QB6yyY-xQxv6Ip', + }, + ], ], [ 'AAL2323', - { - dateInShape: '2020-09-28T18:01:41.191Z', - docId: 'n-ng1XQB6yyY-xQxnGSM', - location: [-84.71324851736426, 41.6677269525826], - shapeLocationId: '0DrJu3QB6yyY-xQxv6Ip', - }, + [ + { + dateInShape: '2020-09-28T18:01:41.191Z', + docId: 'n-ng1XQB6yyY-xQxnGSM', + location: [-84.71324851736426, 41.6677269525826], + shapeLocationId: '0DrJu3QB6yyY-xQxv6Ip', + }, + ], ], [ 'ABD5250', - { - dateInShape: '2020-09-28T18:01:41.192Z', - docId: 'GOng1XQB6yyY-xQxnGWM', - location: [6.073727197945118, 39.07997465226799], - shapeLocationId: '0DrJu3QB6yyY-xQxv6Ip', - }, + [ + { + dateInShape: '2020-09-28T18:01:41.192Z', + docId: 'GOng1XQB6yyY-xQxnGWM', + location: [6.073727197945118, 39.07997465226799], + shapeLocationId: '0DrJu3QB6yyY-xQxv6Ip', + }, + ], ], ]) ); @@ -77,39 +85,47 @@ describe('geo_containment', () => { new Map([ [ '936', - { - dateInShape: '2020-09-28T18:01:41.190Z', - docId: 'N-ng1XQB6yyY-xQxnGSM', - location: [-82.8814151789993, 40.62806099653244], - shapeLocationId: '0DrJu3QB6yyY-xQxv6Ip', - }, + [ + { + dateInShape: '2020-09-28T18:01:41.190Z', + docId: 'N-ng1XQB6yyY-xQxnGSM', + location: [-82.8814151789993, 40.62806099653244], + shapeLocationId: '0DrJu3QB6yyY-xQxv6Ip', + }, + ], ], [ 'AAL2019', - { - dateInShape: '2020-09-28T18:01:41.191Z', - docId: 'iOng1XQB6yyY-xQxnGSM', - location: [-82.22068064846098, 39.006176185794175], - shapeLocationId: '0DrJu3QB6yyY-xQxv6Ip', - }, + [ + { + dateInShape: '2020-09-28T18:01:41.191Z', + docId: 'iOng1XQB6yyY-xQxnGSM', + location: [-82.22068064846098, 39.006176185794175], + shapeLocationId: '0DrJu3QB6yyY-xQxv6Ip', + }, + ], ], [ 'AAL2323', - { - dateInShape: '2020-09-28T18:01:41.191Z', - docId: 'n-ng1XQB6yyY-xQxnGSM', - location: [-84.71324851736426, 41.6677269525826], - shapeLocationId: '0DrJu3QB6yyY-xQxv6Ip', - }, + [ + { + dateInShape: '2020-09-28T18:01:41.191Z', + docId: 'n-ng1XQB6yyY-xQxnGSM', + location: [-84.71324851736426, 41.6677269525826], + shapeLocationId: '0DrJu3QB6yyY-xQxv6Ip', + }, + ], ], [ 'ABD5250', - { - dateInShape: '2020-09-28T18:01:41.192Z', - docId: 'GOng1XQB6yyY-xQxnGWM', - location: [6.073727197945118, 39.07997465226799], - shapeLocationId: '0DrJu3QB6yyY-xQxv6Ip', - }, + [ + { + dateInShape: '2020-09-28T18:01:41.192Z', + docId: 'GOng1XQB6yyY-xQxnGWM', + location: [6.073727197945118, 39.07997465226799], + shapeLocationId: '0DrJu3QB6yyY-xQxv6Ip', + }, + ], ], ]) ); @@ -131,30 +147,36 @@ describe('geo_containment', () => { const currLocationMap = new Map([ [ 'a', - { - location: [0, 0], - shapeLocationId: '123', - dateInShape: 'Wed Dec 09 2020 14:31:31 GMT-0700 (Mountain Standard Time)', - docId: 'docId1', - }, + [ + { + location: [0, 0], + shapeLocationId: '123', + dateInShape: 'Wed Dec 09 2020 14:31:31 GMT-0700 (Mountain Standard Time)', + docId: 'docId1', + }, + ], ], [ 'b', - { - location: [0, 0], - shapeLocationId: '456', - dateInShape: 'Wed Dec 09 2020 15:31:31 GMT-0700 (Mountain Standard Time)', - docId: 'docId2', - }, + [ + { + location: [0, 0], + shapeLocationId: '456', + dateInShape: 'Wed Dec 16 2020 15:31:31 GMT-0700 (Mountain Standard Time)', + docId: 'docId2', + }, + ], ], [ 'c', - { - location: [0, 0], - shapeLocationId: '789', - dateInShape: 'Wed Dec 09 2020 16:31:31 GMT-0700 (Mountain Standard Time)', - docId: 'docId3', - }, + [ + { + location: [0, 0], + shapeLocationId: '789', + dateInShape: 'Wed Dec 23 2020 16:31:31 GMT-0700 (Mountain Standard Time)', + docId: 'docId3', + }, + ], ], ]); @@ -215,7 +237,7 @@ describe('geo_containment', () => { const currentDateTime = new Date(); it('should use currently active entities if no older entity entries', () => { - const emptyPrevLocationMap = {}; + const emptyPrevLocationMap = new Map(); const allActiveEntriesMap = getActiveEntriesAndGenerateAlerts( emptyPrevLocationMap, currLocationMap, @@ -227,14 +249,19 @@ describe('geo_containment', () => { expect(testAlertActionArr).toMatchObject(expectedContext); }); it('should overwrite older identical entity entries', () => { - const prevLocationMapWithIdenticalEntityEntry = { - a: { - location: [0, 0], - shapeLocationId: '999', - dateInShape: 'Wed Dec 09 2020 12:31:31 GMT-0700 (Mountain Standard Time)', - docId: 'docId7', - }, - }; + const prevLocationMapWithIdenticalEntityEntry = new Map([ + [ + 'a', + [ + { + location: [0, 0], + shapeLocationId: '999', + dateInShape: 'Wed Dec 09 2020 12:31:31 GMT-0700 (Mountain Standard Time)', + docId: 'docId7', + }, + ], + ], + ]); const allActiveEntriesMap = getActiveEntriesAndGenerateAlerts( prevLocationMapWithIdenticalEntityEntry, currLocationMap, @@ -246,14 +273,19 @@ describe('geo_containment', () => { expect(testAlertActionArr).toMatchObject(expectedContext); }); it('should preserve older non-identical entity entries', () => { - const prevLocationMapWithNonIdenticalEntityEntry = { - d: { - location: [0, 0], - shapeLocationId: '999', - dateInShape: 'Wed Dec 09 2020 12:31:31 GMT-0700 (Mountain Standard Time)', - docId: 'docId7', - }, - }; + const prevLocationMapWithNonIdenticalEntityEntry = new Map([ + [ + 'd', + [ + { + location: [0, 0], + shapeLocationId: '999', + dateInShape: 'Wed Dec 09 2020 12:31:31 GMT-0700 (Mountain Standard Time)', + docId: 'docId7', + }, + ], + ], + ]); const expectedContextPlusD = [ { actionGroupId: 'Tracked entity contained', @@ -279,14 +311,17 @@ describe('geo_containment', () => { expect(allActiveEntriesMap.has('d')).toBeTruthy(); expect(testAlertActionArr).toMatchObject(expectedContextPlusD); }); + it('should remove "other" entries and schedule the expected number of actions', () => { - const emptyPrevLocationMap = {}; - const currLocationMapWithOther = new Map(currLocationMap).set('d', { - location: [0, 0], - shapeLocationId: OTHER_CATEGORY, - dateInShape: 'Wed Dec 09 2020 14:31:31 GMT-0700 (Mountain Standard Time)', - docId: 'docId1', - }); + const emptyPrevLocationMap = new Map(); + const currLocationMapWithOther = new Map([...currLocationMap]).set('d', [ + { + location: [0, 0], + shapeLocationId: OTHER_CATEGORY, + dateInShape: 'Wed Dec 09 2020 14:31:31 GMT-0700 (Mountain Standard Time)', + docId: 'docId1', + }, + ]); expect(currLocationMapWithOther).not.toEqual(currLocationMap); const allActiveEntriesMap = getActiveEntriesAndGenerateAlerts( emptyPrevLocationMap, @@ -298,5 +333,115 @@ describe('geo_containment', () => { expect(allActiveEntriesMap).toEqual(currLocationMap); expect(testAlertActionArr).toMatchObject(expectedContext); }); + + it('should generate multiple alerts per entity if found in multiple shapes in interval', () => { + const emptyPrevLocationMap = new Map(); + const currLocationMapWithThreeMore = new Map([...currLocationMap]).set('d', [ + { + location: [0, 0], + shapeLocationId: '789', + dateInShape: 'Wed Dec 10 2020 14:31:31 GMT-0700 (Mountain Standard Time)', + docId: 'docId1', + }, + { + location: [0, 0], + shapeLocationId: '123', + dateInShape: 'Wed Dec 08 2020 12:31:31 GMT-0700 (Mountain Standard Time)', + docId: 'docId2', + }, + { + location: [0, 0], + shapeLocationId: '456', + dateInShape: 'Wed Dec 07 2020 10:31:31 GMT-0700 (Mountain Standard Time)', + docId: 'docId3', + }, + ]); + getActiveEntriesAndGenerateAlerts( + emptyPrevLocationMap, + currLocationMapWithThreeMore, + alertInstanceFactory, + emptyShapesIdsNamesMap, + currentDateTime + ); + let numEntitiesInShapes = 0; + currLocationMapWithThreeMore.forEach((v) => { + numEntitiesInShapes += v.length; + }); + expect(testAlertActionArr.length).toEqual(numEntitiesInShapes); + }); + + it('should not return entity as active entry if most recent location is "other"', () => { + const emptyPrevLocationMap = new Map(); + const currLocationMapWithOther = new Map([...currLocationMap]).set('d', [ + { + location: [0, 0], + shapeLocationId: OTHER_CATEGORY, + dateInShape: 'Wed Dec 10 2020 14:31:31 GMT-0700 (Mountain Standard Time)', + docId: 'docId1', + }, + { + location: [0, 0], + shapeLocationId: '123', + dateInShape: 'Wed Dec 08 2020 12:31:31 GMT-0700 (Mountain Standard Time)', + docId: 'docId1', + }, + { + location: [0, 0], + shapeLocationId: '456', + dateInShape: 'Wed Dec 07 2020 10:31:31 GMT-0700 (Mountain Standard Time)', + docId: 'docId1', + }, + ]); + expect(currLocationMapWithOther).not.toEqual(currLocationMap); + const allActiveEntriesMap = getActiveEntriesAndGenerateAlerts( + emptyPrevLocationMap, + currLocationMapWithOther, + alertInstanceFactory, + emptyShapesIdsNamesMap, + currentDateTime + ); + expect(allActiveEntriesMap).toEqual(currLocationMap); + }); + + it('should return entity as active entry if "other" not the latest location but remove "other" and earlier entries', () => { + const emptyPrevLocationMap = new Map(); + const currLocationMapWithOther = new Map([...currLocationMap]).set('d', [ + { + location: [0, 0], + shapeLocationId: '123', + dateInShape: 'Wed Dec 10 2020 14:31:31 GMT-0700 (Mountain Standard Time)', + docId: 'docId1', + }, + { + location: [0, 0], + shapeLocationId: OTHER_CATEGORY, + dateInShape: 'Wed Dec 08 2020 12:31:31 GMT-0700 (Mountain Standard Time)', + docId: 'docId1', + }, + { + location: [0, 0], + shapeLocationId: '456', + dateInShape: 'Wed Dec 07 2020 10:31:31 GMT-0700 (Mountain Standard Time)', + docId: 'docId1', + }, + ]); + const allActiveEntriesMap = getActiveEntriesAndGenerateAlerts( + emptyPrevLocationMap, + currLocationMapWithOther, + alertInstanceFactory, + emptyShapesIdsNamesMap, + currentDateTime + ); + expect(allActiveEntriesMap).toEqual( + new Map([...currLocationMap]).set('d', [ + { + location: [0, 0], + shapeLocationId: '123', + dateInShape: 'Wed Dec 10 2020 14:31:31 GMT-0700 (Mountain Standard Time)', + docId: 'docId1', + }, + ]) + ); + }); }); }); diff --git a/x-pack/plugins/task_manager/server/task_store.test.ts b/x-pack/plugins/task_manager/server/task_store.test.ts index 81d72c68b3a9e..a2a0ee11380ff 100644 --- a/x-pack/plugins/task_manager/server/task_store.test.ts +++ b/x-pack/plugins/task_manager/server/task_store.test.ts @@ -269,12 +269,13 @@ describe('TaskStore', () => { opts = {}, hits = generateFakeTasks(1), claimingOpts, + versionConflicts = 2, }: { opts: Partial; hits?: unknown[]; claimingOpts: OwnershipClaimingOpts; + versionConflicts?: number; }) { - const versionConflicts = 2; const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; esClient.search.mockResolvedValue(asApiResponse({ hits: { hits } })); esClient.updateByQuery.mockResolvedValue( @@ -971,6 +972,77 @@ if (doc['task.runAt'].size()!=0) { ]); }); + test('it returns version_conflicts that do not include conflicts that were proceeded against', async () => { + const taskManagerId = uuid.v1(); + const claimOwnershipUntil = new Date(Date.now()); + const runAt = new Date(); + const tasks = [ + { + _id: 'task:aaa', + _source: { + type: 'task', + task: { + runAt, + taskType: 'foo', + schedule: undefined, + attempts: 0, + status: 'claiming', + params: '{ "hello": "world" }', + state: '{ "baby": "Henhen" }', + user: 'jimbo', + scope: ['reporting'], + ownerId: taskManagerId, + }, + }, + _seq_no: 1, + _primary_term: 2, + sort: ['a', 1], + }, + { + _id: 'task:bbb', + _source: { + type: 'task', + task: { + runAt, + taskType: 'bar', + schedule: { interval: '5m' }, + attempts: 2, + status: 'claiming', + params: '{ "shazm": 1 }', + state: '{ "henry": "The 8th" }', + user: 'dabo', + scope: ['reporting', 'ceo'], + ownerId: taskManagerId, + }, + }, + _seq_no: 3, + _primary_term: 4, + sort: ['b', 2], + }, + ]; + const maxDocs = 10; + const { + result: { stats: { tasksUpdated, tasksConflicted, tasksClaimed } = {} } = {}, + } = await testClaimAvailableTasks({ + opts: { + taskManagerId, + }, + claimingOpts: { + claimOwnershipUntil, + size: maxDocs, + }, + hits: tasks, + // assume there were 20 version conflists, but thanks to `conflicts="proceed"` + // we proceeded to claim tasks + versionConflicts: 20, + }); + + expect(tasksUpdated).toEqual(2); + // ensure we only count conflicts that *may* have counted against max_docs, no more than that + expect(tasksConflicted).toEqual(10 - tasksUpdated!); + expect(tasksClaimed).toEqual(2); + }); + test('pushes error from saved objects client to errors$', async () => { const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; const store = new TaskStore({ diff --git a/x-pack/plugins/task_manager/server/task_store.ts b/x-pack/plugins/task_manager/server/task_store.ts index 5d17c6246088a..4b02e35c61582 100644 --- a/x-pack/plugins/task_manager/server/task_store.ts +++ b/x-pack/plugins/task_manager/server/task_store.ts @@ -529,7 +529,7 @@ export class TaskStore { private async updateByQuery( opts: UpdateByQuerySearchOpts = {}, // eslint-disable-next-line @typescript-eslint/naming-convention - { max_docs }: UpdateByQueryOpts = {} + { max_docs: max_docs }: UpdateByQueryOpts = {} ): Promise { const { query } = ensureQueryOnlyReturnsTaskObjects(opts); try { @@ -548,10 +548,22 @@ export class TaskStore { }, }); + /** + * When we run updateByQuery with conflicts='proceed', it's possible for the `version_conflicts` + * to count against the specified `max_docs`, as per https://github.com/elastic/elasticsearch/issues/63671 + * In order to correct for that happening, we only count `version_conflicts` if we haven't updated as + * many docs as we could have. + * This is still no more than an estimation, as there might have been less docuemnt to update that the + * `max_docs`, but we bias in favour of over zealous `version_conflicts` as that's the best indicator we + * have for an unhealthy cluster distribution of Task Manager polling intervals + */ + const conflictsCorrectedForContinuation = + max_docs && version_conflicts + updated > max_docs ? max_docs - updated : version_conflicts; + return { total, updated, - version_conflicts, + version_conflicts: conflictsCorrectedForContinuation, }; } catch (e) { this.errors$.next(e); diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index e28b41e0ce5e3..1e81795eb2328 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -3032,10 +3032,6 @@ "kibanaOverview.manageData.sectionTitle": "データを管理", "kibanaOverview.more.title": "Elasticではさまざまなことが可能です", "kibanaOverview.news.title": "新機能", - "lensOss.visTypeAlias.description": "ドラッグアンドドロップエディターでビジュアライゼーションを作成します。いつでもビジュアライゼーションタイプを切り替えることができます。ほとんどのビジュアライゼーションに最適です。", - "lensOss.visTypeAlias.note": "ほとんどのユーザーに推奨されます。", - "lensOss.visTypeAlias.promoTooltip.description": "Elastic では Lens を無料でお試しいただけます。詳細情報", - "lensOss.visTypeAlias.title": "レンズ", "management.breadcrumb": "スタック管理", "management.landing.header": "Stack Management {version}へようこそ", "management.landing.subhead": "インデックス、インデックスパターン、保存されたオブジェクト、Kibanaの設定、その他を管理します。", @@ -3089,9 +3085,6 @@ "maps_legacy.wmsOptions.wmsStylesLabel": "WMSスタイル", "maps_legacy.wmsOptions.wmsUrlLabel": "WMS URL", "maps_legacy.wmsOptions.wmsVersionLabel": "WMS バージョン", - "mapsOss.visTypeAlias.description": "マルチレイヤーマップで地理データをプロットしてスタイル設定できます。", - "mapsOss.visTypeAlias.promoTooltip.description": "Elastic では Maps を無料でお試しいただけます。詳細情報", - "mapsOss.visTypeAlias.title": "マップ", "monaco.painlessLanguage.autocomplete.docKeywordDescription": "doc['field_name'] 構文を使用して、スクリプトからフィールド値にアクセスします", "monaco.painlessLanguage.autocomplete.emitKeywordDescription": "戻らずに値を発行します。", "monaco.painlessLanguage.autocomplete.fieldValueDescription": "フィールド「{fieldName}」の値を取得します", @@ -4692,7 +4685,6 @@ "visualizations.initializeWithoutIndexPatternErrorMessage": "インデックスパターンなしで集約を初期化しようとしています", "visualizations.newVisWizard.aggBasedGroupDescription": "クラシック Visualize ライブラリを使用して、アグリゲーションに基づいてグラフを作成します。", "visualizations.newVisWizard.aggBasedGroupTitle": "アグリゲーションに基づく", - "visualizations.newVisWizard.basicTitle": "基本", "visualizations.newVisWizard.chooseSourceTitle": "ソースの選択", "visualizations.newVisWizard.experimentalTitle": "実験的", "visualizations.newVisWizard.experimentalTooltip": "このビジュアライゼーションは今後のリリースで変更または削除される可能性があり、SLA のサポート対象になりません。", @@ -7377,7 +7369,6 @@ "xpack.enterpriseSearch.troubleshooting.standardAuth.title": "標準認証の{productName}はサポートされていません", "xpack.enterpriseSearch.workplaceSearch.activityFeedEmptyDefault.title": "組織には最近のアクティビティがありません", "xpack.enterpriseSearch.workplaceSearch.activityFeedNamedDefault.title": "{name}には最近のアクティビティがありません", - "xpack.enterpriseSearch.workplaceSearch.groups.addGroup.cancel.action": "キャンセル", "xpack.enterpriseSearch.workplaceSearch.groups.addGroup.heading": "グループを追加", "xpack.enterpriseSearch.workplaceSearch.groups.addGroup.submit.action": "グループを追加", "xpack.enterpriseSearch.workplaceSearch.groups.addGroupForm.action": "グループを作成", @@ -7389,7 +7380,6 @@ "xpack.enterpriseSearch.workplaceSearch.groups.filterUsers.buttonText": "ユーザー", "xpack.enterpriseSearch.workplaceSearch.groups.filterUsers.placeholder": "ユーザーをフィルター...", "xpack.enterpriseSearch.workplaceSearch.groups.groupDeleted": "グループ「{groupName}」が正常に削除されました。", - "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerCancel": "キャンセル", "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerHeaderTitle": "{label}を管理", "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerSelectAllToggle": "{action}すべて", "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerSourceEmpty.body": "まだ共有コンテンツソースが追加されていない可能性があります。", @@ -7414,7 +7404,6 @@ "xpack.enterpriseSearch.workplaceSearch.groups.noSourcesMessage": "共有コンテンツソースがありません", "xpack.enterpriseSearch.workplaceSearch.groups.noUsersFound": "ユーザーが見つかりません", "xpack.enterpriseSearch.workplaceSearch.groups.noUsersMessage": "ユーザーがありません", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.cancelRemoveButtonText": "キャンセル", "xpack.enterpriseSearch.workplaceSearch.groups.overview.confirmRemoveButtonText": "{name}を削除", "xpack.enterpriseSearch.workplaceSearch.groups.overview.confirmRemoveDescription": "グループはWorkplace Searchから削除されます。{name}を削除してよろしいですか?", "xpack.enterpriseSearch.workplaceSearch.groups.overview.confirmTitleText": "確認", @@ -9424,9 +9413,7 @@ "xpack.indexLifecycleMgmt.coldPhase.dataTier.defaultAllocationNotAvailableTitle": "コールドティアに割り当てられているノードがありません", "xpack.indexLifecycleMgmt.coldPhase.dataTier.description": "頻度が低い読み取り専用アクセス用に最適化されたノードにデータを移動します。安価なハードウェアのコールドフェーズにデータを格納します。", "xpack.indexLifecycleMgmt.coldPhase.freezeIndexLabel": "インデックスを凍結", - "xpack.indexLifecycleMgmt.coldPhase.numberOfReplicasDescription": "レプリカの数を設定します。デフォルトでは前のフェーズと同じです。", "xpack.indexLifecycleMgmt.coldPhase.numberOfReplicasLabel": "レプリカ数(任意)", - "xpack.indexLifecycleMgmt.coldPhase.replicasTitle": "レプリカ", "xpack.indexLifecycleMgmt.common.dataTier.title": "データ割り当て", "xpack.indexLifecycleMgmt.confirmDelete.cancelButton": "キャンセル", "xpack.indexLifecycleMgmt.confirmDelete.deleteButton": "削除", @@ -9439,11 +9426,8 @@ "xpack.indexLifecycleMgmt.editPolicy.cloudDataTierCallout.title": "コールドティアを作成", "xpack.indexLifecycleMgmt.editPolicy.cold.nodeAttributesMissingDescription": "属性に基づく割り当てを使用するには、elasticsearch.yml でカスタムノード属性を定義します。", "xpack.indexLifecycleMgmt.editPolicy.coldPhase.activateColdPhaseSwitchLabel": "コールドフェーズを有効にする", - "xpack.indexLifecycleMgmt.editPolicy.coldPhase.coldPhaseDescriptionText": "インデックスへのクエリの頻度を減らすことで、大幅に性能が低いハードウェアにシャードを割り当てることができます。クエリが遅いため、複製の数を減らすことができます。", - "xpack.indexLifecycleMgmt.editPolicy.coldPhase.coldPhaseLabel": "コールドフェーズ", "xpack.indexLifecycleMgmt.editPolicy.coldPhase.freezeIndexExplanationText": "インデックスを読み取り専用にし、メモリー消費量を最小化します。", "xpack.indexLifecycleMgmt.editPolicy.coldPhase.freezeText": "凍結", - "xpack.indexLifecycleMgmt.editPolicy.coldPhase.numberOfReplicas.switchLabel": "レプリカを設定", "xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.customOption.helpText": "ノード属性に基づいてデータを移動します。", "xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.customOption.input": "カスタム", "xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.defaultOption.helpText": "コールドティアのノードにデータを移動します。", @@ -9460,13 +9444,6 @@ "xpack.indexLifecycleMgmt.editPolicy.createPolicyMessage": "インデックスライフサイクルポリシーを作成します", "xpack.indexLifecycleMgmt.editPolicy.createSearchableSnapshotLink": "スナップショットリポジドリを作成", "xpack.indexLifecycleMgmt.editPolicy.createSnapshotRepositoryLink": "新しいスナップショットリポジドリを作成", - "xpack.indexLifecycleMgmt.editPolicy.creationDaysOptionLabel": "インデックスの作成からの経過日数", - "xpack.indexLifecycleMgmt.editPolicy.creationHoursOptionLabel": "インデックスの作成からの経過時間数", - "xpack.indexLifecycleMgmt.editPolicy.creationMicroSecondsOptionLabel": "インデックスの作成からの経過時間(マイクロ秒)", - "xpack.indexLifecycleMgmt.editPolicy.creationMilliSecondsOptionLabel": "インデックスの作成からの経過時間(ミリ秒)", - "xpack.indexLifecycleMgmt.editPolicy.creationMinutesOptionLabel": "インデックスの作成からの経過時間(分)", - "xpack.indexLifecycleMgmt.editPolicy.creationNanoSecondsOptionLabel": "インデックスの作成からの経過時間(ナノ秒)", - "xpack.indexLifecycleMgmt.editPolicy.creationSecondsOptionLabel": "インデックスの作成からの経過時間(秒)", "xpack.indexLifecycleMgmt.editPolicy.dataTierAllocation.allocationFieldLabel": "データティアオプション", "xpack.indexLifecycleMgmt.editPolicy.dataTierAllocation.nodeAllocationFieldLabel": "ノード属性を選択", "xpack.indexLifecycleMgmt.editPolicy.dataTierColdLabel": "コールド", @@ -9507,23 +9484,17 @@ "xpack.indexLifecycleMgmt.editPolicy.formErrorsMessage": "このページのエラーを修正してください。", "xpack.indexLifecycleMgmt.editPolicy.hidePolicyJsonButto": "リクエストを非表示", "xpack.indexLifecycleMgmt.editPolicy.hotPhase.enableRolloverTipContent": "現在のインデックスが定義された条件のいずれかを満たすときに、新しいインデックスにロールオーバーします。", - "xpack.indexLifecycleMgmt.editPolicy.hotPhase.hotPhaseDescriptionMessage": "このフェーズは必須です。アクティブにクエリを実行しインデックスに書き込んでいます。 更新を高速化するため、大きくなりすぎたり古くなりすぎたりした際にインデックスをロールオーバーできます。", - "xpack.indexLifecycleMgmt.editPolicy.hotPhase.hotPhaseLabel": "ホットフェーズ", "xpack.indexLifecycleMgmt.editPolicy.hotPhase.learnAboutRolloverLinkText": "詳細", "xpack.indexLifecycleMgmt.editPolicy.hotPhase.rolloverDefaultsTipContent": "インデックスが 30 日経過するか、50 GB に達したらロールオーバーします。", "xpack.indexLifecycleMgmt.editPolicy.hotPhase.rolloverDescriptionMessage": "効率的なストレージと高いパフォーマンスのための時系列データの自動ロールアウト。", "xpack.indexLifecycleMgmt.editPolicy.indexPriorityLabel": "インデックス優先度(任意)", "xpack.indexLifecycleMgmt.editPolicy.indexPriorityText": "インデックスの優先順位", - "xpack.indexLifecycleMgmt.editPolicy.learnAboutIndexLifecycleManagementLinkText": "インデックスライフサイクルの詳細をご覧ください。", "xpack.indexLifecycleMgmt.editPolicy.learnAboutIndexTemplatesLink": "インデックステンプレートの詳細をご覧ください", "xpack.indexLifecycleMgmt.editPolicy.learnAboutShardAllocationLink": "シャード割り当ての詳細をご覧ください", - "xpack.indexLifecycleMgmt.editPolicy.learnAboutTimingText": "タイミングの詳細をご覧ください", "xpack.indexLifecycleMgmt.editPolicy.lifecyclePoliciesLoadingFailedTitle": "既存のライフサイクルポリシーを読み込めません", "xpack.indexLifecycleMgmt.editPolicy.lifecyclePoliciesReloadButton": "再試行", - "xpack.indexLifecycleMgmt.editPolicy.lifecyclePolicyDescriptionText": "インデックスへのアクティブな書き込みから削除までの、インデックスライフサイクルの 4 つのフェーズを自動化するには、インデックスポリシーを使用します。", "xpack.indexLifecycleMgmt.editPolicy.loadSnapshotRepositoriesErrorBody": "このフィールドを更新し、既存のスナップショットリポジトリの名前を入力します。", "xpack.indexLifecycleMgmt.editPolicy.loadSnapshotRepositoriesErrorTitle": "スナップショットリポジトリを読み込めません", - "xpack.indexLifecycleMgmt.editPolicy.nameLabel": "名前", "xpack.indexLifecycleMgmt.editPolicy.nodeAllocation.customOption.description": "ノード属性を使用して、シャード割り当てを制御します。{learnMoreLink}。", "xpack.indexLifecycleMgmt.editPolicy.nodeAllocation.doNotModifyAllocationOption": "割り当て構成を修正しない", "xpack.indexLifecycleMgmt.editPolicy.nodeAttributesLoadingFailedTitle": "ノードデータを読み込めません", @@ -9550,13 +9521,6 @@ "xpack.indexLifecycleMgmt.editPolicy.readonlyDescription": "有効にすると、インデックスおよびインデックスメタデータを読み取り専用にします。無効にすると、書き込みとメタデータの変更を許可します。", "xpack.indexLifecycleMgmt.editPolicy.readonlyTitle": "読み取り専用", "xpack.indexLifecycleMgmt.editPolicy.reloadSnapshotRepositoriesLabel": "スナップショットリポジドリの再読み込み", - "xpack.indexLifecycleMgmt.editPolicy.rolloverDaysOptionLabel": "ロールオーバーからの経過日数", - "xpack.indexLifecycleMgmt.editPolicy.rolloverHoursOptionLabel": "ロールオーバーからの経過時間数", - "xpack.indexLifecycleMgmt.editPolicy.rolloverMicroSecondsOptionLabel": "ロールオーバーからの経過時間(マイクロ秒)", - "xpack.indexLifecycleMgmt.editPolicy.rolloverMilliSecondsOptionLabel": "ロールオーバーからの経過時間(ミリ秒)", - "xpack.indexLifecycleMgmt.editPolicy.rolloverMinutesOptionLabel": "ロールオーバーからの経過時間数(分)", - "xpack.indexLifecycleMgmt.editPolicy.rolloverNanoSecondsOptionLabel": "ロールオーバーからの経過時間(ナノ秒)", - "xpack.indexLifecycleMgmt.editPolicy.rolloverSecondsOptionLabel": "ロールオーバーからの経過時間(秒)", "xpack.indexLifecycleMgmt.editPolicy.saveAsNewButton": "新規ポリシーとして保存します", "xpack.indexLifecycleMgmt.editPolicy.saveAsNewPolicyMessage": "新規ポリシーとして保存します", "xpack.indexLifecycleMgmt.editPolicy.saveButton": "ポリシーを保存", @@ -9581,15 +9545,11 @@ "xpack.indexLifecycleMgmt.editPolicy.warm.nodeAttributesMissingDescription": "属性に基づく割り当てを使用するには、elasticsearch.yml でカスタムノード属性を定義します。", "xpack.indexLifecycleMgmt.editPolicy.warmPhase.activateWarmPhaseSwitchLabel": "ウォームフェーズを有効にする", "xpack.indexLifecycleMgmt.editPolicy.warmPhase.indexPriorityExplanationText": "ノードの再起動後にインデックスを復元する優先順位を設定します。優先順位の高いインデックスは優先順位の低いインデックスよりも先に復元されます。", - "xpack.indexLifecycleMgmt.editPolicy.warmPhase.numberOfReplicas.switchLabel": "レプリカを設定", - "xpack.indexLifecycleMgmt.editPolicy.warmPhase.warmPhaseDescriptionMessage": "まだインデックスにクエリを実行中ですが、読み取り専用です。性能の低いハードウェアにシャードを割り当てることができます。検索を高速化するために、シャードの数を減らしセグメントを結合することができます。", - "xpack.indexLifecycleMgmt.editPolicy.warmPhase.warmPhaseLabel": "ウォームフェーズ", "xpack.indexLifecycleMgmt.featureCatalogueDescription": "ライフサイクルポリシーを定義し、インデックス年齢として自動的に処理を実行します。", "xpack.indexLifecycleMgmt.featureCatalogueTitle": "インデックスライフサイクルを管理", "xpack.indexLifecycleMgmt.forcemerge.bestCompressionLabel": "格納されたフィールドを圧縮", "xpack.indexLifecycleMgmt.forcemerge.enableLabel": "データを強制結合", "xpack.indexLifecycleMgmt.forceMerge.numberOfSegmentsLabel": "セグメントの数", - "xpack.indexLifecycleMgmt.hotPhase.advancedSettingsButton": "高度な設定", "xpack.indexLifecycleMgmt.hotPhase.bytesLabel": "バイト", "xpack.indexLifecycleMgmt.hotPhase.daysLabel": "日", "xpack.indexLifecycleMgmt.hotPhase.enableRolloverLabel": "ロールオーバーを有効にする", @@ -9713,7 +9673,6 @@ "xpack.indexLifecycleMgmt.shrink.indexFieldLabel": "インデックスを縮小", "xpack.indexLifecycleMgmt.shrink.numberOfPrimaryShardsLabel": "プライマリシャードの数", "xpack.indexLifecycleMgmt.templateNotFoundMessage": "テンプレート{name}が見つかりません。", - "xpack.indexLifecycleMgmt.warmPhase.advancedSettingsButton": "高度な設定", "xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotAvailableBody": "ロールに基づく割り当てを使用するには、1つ以上のノードを、ウォームまたはホットティアに割り当てます。使用可能なノードがない場合、ポリシーは割り当てを完了できません。", "xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotAvailableTitle": "ウォームティアに割り当てられているノードがありません", "xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotice.cold": "このポリシーはコールドフェーズのデータを{tier}ティアノードに移動します。", @@ -9721,10 +9680,7 @@ "xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotice.warm": "このポリシーはウォームフェーズのデータを{tier}ティアノードに移動します。", "xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotice.warm.title": "ウォームティアに割り当てられているノードがありません", "xpack.indexLifecycleMgmt.warmPhase.dataTier.description": "頻度が低い読み取り専用アクセス用に最適化されたノードにデータを移動します。", - "xpack.indexLifecycleMgmt.warmPhase.moveToWarmPhaseOnRolloverLabel": "ロールオーバー時にウォームフェーズに変更", - "xpack.indexLifecycleMgmt.warmPhase.numberOfReplicasDescription": "レプリカの数を設定します。デフォルトでは前のフェーズと同じです。", "xpack.indexLifecycleMgmt.warmPhase.numberOfReplicasLabel": "レプリカ数(任意)", - "xpack.indexLifecycleMgmt.warmPhase.replicasTitle": "レプリカ", "xpack.infra.alerting.alertFlyout.groupBy.placeholder": "なし(グループなし)", "xpack.infra.alerting.alertFlyout.groupByLabel": "グループ分けの条件", "xpack.infra.alerting.alertsButton": "アラート", @@ -13114,8 +13070,6 @@ "xpack.ml.featureRegistry.mlFeatureName": "機械学習", "xpack.ml.fieldDataCard.cardBoolean.valuesLabel": "値", "xpack.ml.fieldDataCard.cardDate.summaryTableTitle": "まとめ", - "xpack.ml.fieldDataCard.cardIp.topValuesLabel": "トップの値", - "xpack.ml.fieldDataCard.cardKeyword.topValuesLabel": "トップの値", "xpack.ml.fieldDataCard.cardText.fieldMayBePopulatedDescription": "たとえば、ドキュメントマッピングで {copyToParam} パラメーターを使ったり、{includesParam} と {excludesParam} パラメーターを使用してインデックスした後に {sourceParam} フィールドから切り取ったりして入力される場合があります。", "xpack.ml.fieldDataCard.cardText.fieldNotPresentDescription": "このフィールドはクエリが実行されたドキュメントの {sourceParam} フィールドにありませんでした。", "xpack.ml.fieldDataCard.cardText.noExamplesForFieldsTitle": "このフィールドの例が取得されませんでした", @@ -13133,7 +13087,6 @@ "xpack.ml.fieldDataCardExpandedRow.numberContent.medianLabel": "中間", "xpack.ml.fieldDataCardExpandedRow.numberContent.minLabel": "分", "xpack.ml.fieldDataCardExpandedRow.numberContent.summaryTableTitle": "まとめ", - "xpack.ml.fieldDataCardExpandedRow.numberContent.topValuesTitle": "トップの値", "xpack.ml.fieldTitleBar.documentCountLabel": "ドキュメントカウント", "xpack.ml.fieldTypeIcon.booleanTypeAriaLabel": "ブールタイプ", "xpack.ml.fieldTypeIcon.dateTypeAriaLabel": "日付タイプ", @@ -18773,8 +18726,6 @@ "xpack.securitySolution.endpoint.policyDetails.malware.userNotification.placeholder": "カスタム通知メッセージを入力", "xpack.securitySolution.endpoint.policyDetails.supportedVersion": "エージェントバージョン {version}", "xpack.securitySolution.endpoint.policyDetailsConfig.customizeUserNotification": "通知メッセージをカスタマイズ", - "xpack.securitySolution.endpoint.policyDetailsConfig.customizeUserNotification.tooltip.a": "ユーザー通知オプションを選択すると、マルウェアが防御または検出されたときに、ホストユーザーに通知を表示します。", - "xpack.securitySolution.endpoint.policyDetailsConfig.customizeUserNotification.tooltip.b": " ユーザー通知は、以下のテキストボックスでカスタマイズできます。括弧内のタグを使用すると、該当するアクション(防御または検出など)とファイル名を動的に入力できます。", "xpack.securitySolution.endpoint.policyDetailsConfig.eventingEvents": "イベント", "xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.file": "ファイル", "xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.network": "ネットワーク", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 49fd9ce095f66..4d21a05cab09a 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -3036,10 +3036,6 @@ "kibanaOverview.manageData.sectionTitle": "管理您的数据", "kibanaOverview.more.title": "Elastic 让您事半功倍", "kibanaOverview.news.title": "最新动态", - "lensOss.visTypeAlias.description": "使用我们支持拖放的编辑器来创建可视化。随时在可视化类型之间切换。绝大多数可视化的最佳选择。", - "lensOss.visTypeAlias.note": "适合绝大多数用户。", - "lensOss.visTypeAlias.promoTooltip.description": "免费试用 Elastic 的 Lens。了解详情。", - "lensOss.visTypeAlias.title": "Lens", "management.breadcrumb": "Stack Management", "management.landing.header": "欢迎使用 Stack Management {version}", "management.landing.subhead": "管理您的索引、索引模式、已保存对象、Kibana 设置等等。", @@ -3093,9 +3089,6 @@ "maps_legacy.wmsOptions.wmsStylesLabel": "WMS 样式", "maps_legacy.wmsOptions.wmsUrlLabel": "WMS url", "maps_legacy.wmsOptions.wmsVersionLabel": "WMS 版本", - "mapsOss.visTypeAlias.description": "在多层地图中绘制地理数据并设置其样式。", - "mapsOss.visTypeAlias.promoTooltip.description": "免费试用 Elastic 的地图。了解详情。", - "mapsOss.visTypeAlias.title": "地图", "monaco.painlessLanguage.autocomplete.docKeywordDescription": "使用 doc['field_name'] 语法,从脚本中访问字段值", "monaco.painlessLanguage.autocomplete.emitKeywordDescription": "发出值,而不返回值。", "monaco.painlessLanguage.autocomplete.fieldValueDescription": "检索字段“{fieldName}”的值", @@ -4697,7 +4690,6 @@ "visualizations.initializeWithoutIndexPatternErrorMessage": "正在尝试在不使用索引模式的情况下初始化聚合", "visualizations.newVisWizard.aggBasedGroupDescription": "使用我们的经典可视化库,基于聚合创建图表。", "visualizations.newVisWizard.aggBasedGroupTitle": "基于聚合", - "visualizations.newVisWizard.basicTitle": "基本级", "visualizations.newVisWizard.chooseSourceTitle": "选择源", "visualizations.newVisWizard.experimentalTitle": "实验性", "visualizations.newVisWizard.experimentalTooltip": "未来版本可能会更改或删除此可视化,其不受支持 SLA 的约束。", @@ -7396,7 +7388,6 @@ "xpack.enterpriseSearch.troubleshooting.standardAuth.title": "不支持使用标准身份验证的 {productName}", "xpack.enterpriseSearch.workplaceSearch.activityFeedEmptyDefault.title": "您的组织最近无活动", "xpack.enterpriseSearch.workplaceSearch.activityFeedNamedDefault.title": "{name} 最近无活动", - "xpack.enterpriseSearch.workplaceSearch.groups.addGroup.cancel.action": "取消", "xpack.enterpriseSearch.workplaceSearch.groups.addGroup.heading": "添加组", "xpack.enterpriseSearch.workplaceSearch.groups.addGroup.submit.action": "添加组", "xpack.enterpriseSearch.workplaceSearch.groups.addGroupForm.action": "创建组", @@ -7408,7 +7399,6 @@ "xpack.enterpriseSearch.workplaceSearch.groups.filterUsers.buttonText": "用户", "xpack.enterpriseSearch.workplaceSearch.groups.filterUsers.placeholder": "筛选用户......", "xpack.enterpriseSearch.workplaceSearch.groups.groupDeleted": "组“{groupName}”已成功删除。", - "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerCancel": "取消", "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerHeaderTitle": "管理 {label}", "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerSelectAllToggle": "全部{action}", "xpack.enterpriseSearch.workplaceSearch.groups.groupManagerSourceEmpty.body": "可能您尚未添加任何共享内容源。", @@ -7433,7 +7423,6 @@ "xpack.enterpriseSearch.workplaceSearch.groups.noSourcesMessage": "无共享内容源", "xpack.enterpriseSearch.workplaceSearch.groups.noUsersFound": "找不到用户", "xpack.enterpriseSearch.workplaceSearch.groups.noUsersMessage": "无用户", - "xpack.enterpriseSearch.workplaceSearch.groups.overview.cancelRemoveButtonText": "取消", "xpack.enterpriseSearch.workplaceSearch.groups.overview.confirmRemoveButtonText": "删除 {name}", "xpack.enterpriseSearch.workplaceSearch.groups.overview.confirmRemoveDescription": "您的组将从 Workplace Search 中删除。确定要移除 {name}?", "xpack.enterpriseSearch.workplaceSearch.groups.overview.confirmTitleText": "确认", @@ -9448,9 +9437,7 @@ "xpack.indexLifecycleMgmt.coldPhase.dataTier.defaultAllocationNotAvailableTitle": "没有分配到冷层的节点", "xpack.indexLifecycleMgmt.coldPhase.dataTier.description": "将数据移到针对不太频繁的只读访问优化的节点。将处于冷阶段的数据存储在成本较低的硬件上。", "xpack.indexLifecycleMgmt.coldPhase.freezeIndexLabel": "冻结索引", - "xpack.indexLifecycleMgmt.coldPhase.numberOfReplicasDescription": "设置副本数目。默认情况下仍与上一阶段相同。", "xpack.indexLifecycleMgmt.coldPhase.numberOfReplicasLabel": "副本分片数(可选)", - "xpack.indexLifecycleMgmt.coldPhase.replicasTitle": "副本分片", "xpack.indexLifecycleMgmt.common.dataTier.title": "数据分配", "xpack.indexLifecycleMgmt.confirmDelete.cancelButton": "取消", "xpack.indexLifecycleMgmt.confirmDelete.deleteButton": "删除", @@ -9463,11 +9450,8 @@ "xpack.indexLifecycleMgmt.editPolicy.cloudDataTierCallout.title": "创建冷层", "xpack.indexLifecycleMgmt.editPolicy.cold.nodeAttributesMissingDescription": "在 elasticsearch.yml 中定义定制节点属性,以使用基于属性的分配。", "xpack.indexLifecycleMgmt.editPolicy.coldPhase.activateColdPhaseSwitchLabel": "激活冷阶段", - "xpack.indexLifecycleMgmt.editPolicy.coldPhase.coldPhaseDescriptionText": "您查询自己索引的频率较低,因此您可以在效率较低的硬件上分配分片。因为您的查询较为缓慢,所以您可以减少副本分片数目。", - "xpack.indexLifecycleMgmt.editPolicy.coldPhase.coldPhaseLabel": "冷阶段", "xpack.indexLifecycleMgmt.editPolicy.coldPhase.freezeIndexExplanationText": "使索引只读,并最大限度减小其内存占用。", "xpack.indexLifecycleMgmt.editPolicy.coldPhase.freezeText": "冻结", - "xpack.indexLifecycleMgmt.editPolicy.coldPhase.numberOfReplicas.switchLabel": "设置副本", "xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.customOption.helpText": "根据节点属性移动数据。", "xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.customOption.input": "定制", "xpack.indexLifecycleMgmt.editPolicy.common.dataTierAllocation.cold.defaultOption.helpText": "将数据移到冷层中的节点。", @@ -9484,13 +9468,6 @@ "xpack.indexLifecycleMgmt.editPolicy.createPolicyMessage": "创建索引生命周期策略", "xpack.indexLifecycleMgmt.editPolicy.createSearchableSnapshotLink": "创建快照库", "xpack.indexLifecycleMgmt.editPolicy.createSnapshotRepositoryLink": "创建新的快照库", - "xpack.indexLifecycleMgmt.editPolicy.creationDaysOptionLabel": "天(自索引创建)", - "xpack.indexLifecycleMgmt.editPolicy.creationHoursOptionLabel": "小时(自索引创建)", - "xpack.indexLifecycleMgmt.editPolicy.creationMicroSecondsOptionLabel": "微秒(自索引创建)", - "xpack.indexLifecycleMgmt.editPolicy.creationMilliSecondsOptionLabel": "毫秒(自索引创建)", - "xpack.indexLifecycleMgmt.editPolicy.creationMinutesOptionLabel": "分钟(自索引创建)", - "xpack.indexLifecycleMgmt.editPolicy.creationNanoSecondsOptionLabel": "纳秒(自索引创建)", - "xpack.indexLifecycleMgmt.editPolicy.creationSecondsOptionLabel": "秒(自索引创建)", "xpack.indexLifecycleMgmt.editPolicy.dataTierAllocation.allocationFieldLabel": "数据层选项", "xpack.indexLifecycleMgmt.editPolicy.dataTierAllocation.nodeAllocationFieldLabel": "选择节点属性", "xpack.indexLifecycleMgmt.editPolicy.dataTierColdLabel": "冷", @@ -9531,23 +9508,17 @@ "xpack.indexLifecycleMgmt.editPolicy.formErrorsMessage": "请修复此页面上的错误。", "xpack.indexLifecycleMgmt.editPolicy.hidePolicyJsonButto": "隐藏请求", "xpack.indexLifecycleMgmt.editPolicy.hotPhase.enableRolloverTipContent": "在当前索引满足定义的条件之一时,滚动更新到新索引。", - "xpack.indexLifecycleMgmt.editPolicy.hotPhase.hotPhaseDescriptionMessage": "此阶段为必需。您正频繁地查询并写到您的索引。 为了获取更快的更新,在索引变得过大或过旧时,您可以滚动更新索引。", - "xpack.indexLifecycleMgmt.editPolicy.hotPhase.hotPhaseLabel": "热阶段", "xpack.indexLifecycleMgmt.editPolicy.hotPhase.learnAboutRolloverLinkText": "了解详情", "xpack.indexLifecycleMgmt.editPolicy.hotPhase.rolloverDefaultsTipContent": "当索引已存在 30 天或达到 50 GB 时滚动更新。", "xpack.indexLifecycleMgmt.editPolicy.hotPhase.rolloverDescriptionMessage": "自动滚动更新时间序列数据,以实现高效存储和更高性能。", "xpack.indexLifecycleMgmt.editPolicy.indexPriorityLabel": "索引优先级(可选)", "xpack.indexLifecycleMgmt.editPolicy.indexPriorityText": "索引优先级", - "xpack.indexLifecycleMgmt.editPolicy.learnAboutIndexLifecycleManagementLinkText": "了解索引生命周期。", "xpack.indexLifecycleMgmt.editPolicy.learnAboutIndexTemplatesLink": "了解索引模板", "xpack.indexLifecycleMgmt.editPolicy.learnAboutShardAllocationLink": "了解分片分配", - "xpack.indexLifecycleMgmt.editPolicy.learnAboutTimingText": "了解计时", "xpack.indexLifecycleMgmt.editPolicy.lifecyclePoliciesLoadingFailedTitle": "无法加载现有生命周期策略", "xpack.indexLifecycleMgmt.editPolicy.lifecyclePoliciesReloadButton": "重试", - "xpack.indexLifecycleMgmt.editPolicy.lifecyclePolicyDescriptionText": "使用索引策略自动化索引生命周期的四个阶段,从频繁地写入到索引到删除索引。", "xpack.indexLifecycleMgmt.editPolicy.loadSnapshotRepositoriesErrorBody": "刷新此字段并输入现有快照储存库的名称。", "xpack.indexLifecycleMgmt.editPolicy.loadSnapshotRepositoriesErrorTitle": "无法加载快照存储库", - "xpack.indexLifecycleMgmt.editPolicy.nameLabel": "名称", "xpack.indexLifecycleMgmt.editPolicy.nodeAllocation.customOption.description": "使用节点属性控制分片分配。{learnMoreLink}。", "xpack.indexLifecycleMgmt.editPolicy.nodeAllocation.doNotModifyAllocationOption": "切勿修改分配配置", "xpack.indexLifecycleMgmt.editPolicy.nodeAttributesLoadingFailedTitle": "无法加载节点数据", @@ -9574,13 +9545,6 @@ "xpack.indexLifecycleMgmt.editPolicy.readonlyDescription": "启用以使索引及索引元数据只读;禁用以允许写入和元数据更改。", "xpack.indexLifecycleMgmt.editPolicy.readonlyTitle": "只读", "xpack.indexLifecycleMgmt.editPolicy.reloadSnapshotRepositoriesLabel": "重新加载快照存储库", - "xpack.indexLifecycleMgmt.editPolicy.rolloverDaysOptionLabel": "天(自滚动更新)", - "xpack.indexLifecycleMgmt.editPolicy.rolloverHoursOptionLabel": "小时(自滚动更新)", - "xpack.indexLifecycleMgmt.editPolicy.rolloverMicroSecondsOptionLabel": "微秒(自滚动更新)", - "xpack.indexLifecycleMgmt.editPolicy.rolloverMilliSecondsOptionLabel": "毫秒(自滚动更新)", - "xpack.indexLifecycleMgmt.editPolicy.rolloverMinutesOptionLabel": "分钟(自滚动更新)", - "xpack.indexLifecycleMgmt.editPolicy.rolloverNanoSecondsOptionLabel": "纳秒(自滚动更新)", - "xpack.indexLifecycleMgmt.editPolicy.rolloverSecondsOptionLabel": "秒(自滚动更新)", "xpack.indexLifecycleMgmt.editPolicy.saveAsNewButton": "另存为新策略", "xpack.indexLifecycleMgmt.editPolicy.saveAsNewPolicyMessage": "另存为新策略", "xpack.indexLifecycleMgmt.editPolicy.saveButton": "保存策略", @@ -9605,15 +9569,11 @@ "xpack.indexLifecycleMgmt.editPolicy.warm.nodeAttributesMissingDescription": "在 elasticsearch.yml 中定义定制节点属性,以使用基于属性的分配。", "xpack.indexLifecycleMgmt.editPolicy.warmPhase.activateWarmPhaseSwitchLabel": "激活温阶段", "xpack.indexLifecycleMgmt.editPolicy.warmPhase.indexPriorityExplanationText": "设置在节点重新启动后恢复索引的优先级。较高优先级的索引会在较低优先级的索引之前恢复。", - "xpack.indexLifecycleMgmt.editPolicy.warmPhase.numberOfReplicas.switchLabel": "设置副本", - "xpack.indexLifecycleMgmt.editPolicy.warmPhase.warmPhaseDescriptionMessage": "您仍在查询自己的索引,但其为只读。您可以将分片分配给效率较低的硬件。为了获取更快的搜索,您可以减少分片数目并强制合并段。", - "xpack.indexLifecycleMgmt.editPolicy.warmPhase.warmPhaseLabel": "温阶段", "xpack.indexLifecycleMgmt.featureCatalogueDescription": "定义生命周期策略,以随着索引老化自动执行操作。", "xpack.indexLifecycleMgmt.featureCatalogueTitle": "管理索引生命周期", "xpack.indexLifecycleMgmt.forcemerge.bestCompressionLabel": "压缩已存储字段", "xpack.indexLifecycleMgmt.forcemerge.enableLabel": "强制合并数据", "xpack.indexLifecycleMgmt.forceMerge.numberOfSegmentsLabel": "分段数目", - "xpack.indexLifecycleMgmt.hotPhase.advancedSettingsButton": "高级设置", "xpack.indexLifecycleMgmt.hotPhase.bytesLabel": "字节", "xpack.indexLifecycleMgmt.hotPhase.daysLabel": "天", "xpack.indexLifecycleMgmt.hotPhase.enableRolloverLabel": "启用滚动更新", @@ -9739,7 +9699,6 @@ "xpack.indexLifecycleMgmt.shrink.indexFieldLabel": "缩小索引", "xpack.indexLifecycleMgmt.shrink.numberOfPrimaryShardsLabel": "主分片数目", "xpack.indexLifecycleMgmt.templateNotFoundMessage": "找不到模板 {name}。", - "xpack.indexLifecycleMgmt.warmPhase.advancedSettingsButton": "高级设置", "xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotAvailableBody": "至少将一个节点分配到温层或冷层,以使用基于角色的分配。如果没有可用节点,则策略无法完成分配。", "xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotAvailableTitle": "没有分配到温层的节点", "xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotice.cold": "此策略会改为将冷阶段的数据移到{tier}层节点。", @@ -9747,10 +9706,7 @@ "xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotice.warm": "此策略会改为将温阶段的数据移到{tier}层节点。", "xpack.indexLifecycleMgmt.warmPhase.dataTier.defaultAllocationNotice.warm.title": "没有分配到温层的节点", "xpack.indexLifecycleMgmt.warmPhase.dataTier.description": "将数据移到针对不太频繁的只读访问优化的节点。", - "xpack.indexLifecycleMgmt.warmPhase.moveToWarmPhaseOnRolloverLabel": "滚动更新时移到温阶段", - "xpack.indexLifecycleMgmt.warmPhase.numberOfReplicasDescription": "设置副本数目。默认情况下仍与上一阶段相同。", "xpack.indexLifecycleMgmt.warmPhase.numberOfReplicasLabel": "副本分片数(可选)", - "xpack.indexLifecycleMgmt.warmPhase.replicasTitle": "副本分片", "xpack.infra.alerting.alertFlyout.groupBy.placeholder": "无内容(未分组)", "xpack.infra.alerting.alertFlyout.groupByLabel": "分组依据", "xpack.infra.alerting.alertsButton": "告警", @@ -13145,8 +13101,6 @@ "xpack.ml.featureRegistry.mlFeatureName": "Machine Learning", "xpack.ml.fieldDataCard.cardBoolean.valuesLabel": "值", "xpack.ml.fieldDataCard.cardDate.summaryTableTitle": "摘要", - "xpack.ml.fieldDataCard.cardIp.topValuesLabel": "排名最前值", - "xpack.ml.fieldDataCard.cardKeyword.topValuesLabel": "排名最前值", "xpack.ml.fieldDataCard.cardText.fieldMayBePopulatedDescription": "例如,可以使用文档映射中的 {copyToParam} 参数进行填充,也可以在索引后通过使用 {includesParam} 和 {excludesParam} 参数从 {sourceParam} 字段中修剪。", "xpack.ml.fieldDataCard.cardText.fieldNotPresentDescription": "查询的文档的 {sourceParam} 字段中不存在此字段。", "xpack.ml.fieldDataCard.cardText.noExamplesForFieldsTitle": "没有获取此字段的示例", @@ -13164,7 +13118,6 @@ "xpack.ml.fieldDataCardExpandedRow.numberContent.medianLabel": "中值", "xpack.ml.fieldDataCardExpandedRow.numberContent.minLabel": "最小值", "xpack.ml.fieldDataCardExpandedRow.numberContent.summaryTableTitle": "摘要", - "xpack.ml.fieldDataCardExpandedRow.numberContent.topValuesTitle": "排名最前值", "xpack.ml.fieldTitleBar.documentCountLabel": "文档计数", "xpack.ml.fieldTypeIcon.booleanTypeAriaLabel": "布尔类型", "xpack.ml.fieldTypeIcon.dateTypeAriaLabel": "日期类型", @@ -18820,8 +18773,6 @@ "xpack.securitySolution.endpoint.policyDetails.malware.userNotification.placeholder": "输入您的定制通知消息", "xpack.securitySolution.endpoint.policyDetails.supportedVersion": "代理版本 {version}", "xpack.securitySolution.endpoint.policyDetailsConfig.customizeUserNotification": "定制通知消息", - "xpack.securitySolution.endpoint.policyDetailsConfig.customizeUserNotification.tooltip.a": "选择用户通知选项后,在阻止或检测到恶意软件时将向主机用户显示通知。", - "xpack.securitySolution.endpoint.policyDetailsConfig.customizeUserNotification.tooltip.b": " 可在下方文本框中定制用户通知。括号中的标签可用于动态填充适用操作(如已阻止或已检测)和文件名。", "xpack.securitySolution.endpoint.policyDetailsConfig.eventingEvents": "事件", "xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.file": "文件", "xpack.securitySolution.endpoint.policyDetailsConfig.linux.events.network": "网络", diff --git a/x-pack/plugins/upgrade_assistant/common/types.ts b/x-pack/plugins/upgrade_assistant/common/types.ts index 2a94b39ca6c66..9625ca89b11d0 100644 --- a/x-pack/plugins/upgrade_assistant/common/types.ts +++ b/x-pack/plugins/upgrade_assistant/common/types.ts @@ -5,8 +5,6 @@ */ import { SavedObject, SavedObjectAttributes } from 'src/core/public'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import type { DeprecationInfo } from '../../../../src/core/server/elasticsearch/legacy/api_types'; export enum ReindexStep { // Enum values are spaced out by 10 to give us room to insert steps in between. @@ -165,6 +163,23 @@ export interface UpgradeAssistantTelemetrySavedObjectAttributes { [key: string]: any; } +export type MIGRATION_DEPRECATION_LEVEL = 'none' | 'info' | 'warning' | 'critical'; +export interface DeprecationInfo { + level: MIGRATION_DEPRECATION_LEVEL; + message: string; + url: string; + details?: string; +} + +export interface IndexSettingsDeprecationInfo { + [indexName: string]: DeprecationInfo[]; +} +export interface DeprecationAPIResponse { + cluster_settings: DeprecationInfo[]; + ml_settings: DeprecationInfo[]; + node_settings: DeprecationInfo[]; + index_settings: IndexSettingsDeprecationInfo; +} export interface EnrichedDeprecationInfo extends DeprecationInfo { index?: string; node?: string; diff --git a/x-pack/plugins/upgrade_assistant/public/application/app.tsx b/x-pack/plugins/upgrade_assistant/public/application/app.tsx index 17eff71f1039b..2b245bceceb6c 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/app.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/app.tsx @@ -16,10 +16,10 @@ export interface AppDependencies extends ContextValue { i18n: I18nStart; } -export const RootComponent = ({ i18n, ...contexValue }: AppDependencies) => { +export const RootComponent = ({ i18n, ...contextValue }: AppDependencies) => { return ( - +
diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/latest_minor_banner.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/latest_minor_banner.tsx index 43d0364425cbb..d9ec183231739 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/latest_minor_banner.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/latest_minor_banner.tsx @@ -10,40 +10,49 @@ import { EuiCallOut, EuiLink } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { CURRENT_MAJOR_VERSION, NEXT_MAJOR_VERSION } from '../../../common/version'; +import { useAppContext } from '../app_context'; -export const LatestMinorBanner: React.FunctionComponent = () => ( - - } - color="warning" - iconType="help" - > -

- + } + color="warning" + iconType="help" + > +

+ - - - ), - nextEsVersion: `${NEXT_MAJOR_VERSION}.x`, - currentEsVersion: `${CURRENT_MAJOR_VERSION}.x`, - }} - /> -

-
-); + values={{ + breakingChangesDocButton: ( + + + + ), + nextEsVersion: `${NEXT_MAJOR_VERSION}.x`, + currentEsVersion: `${CURRENT_MAJOR_VERSION}.x`, + }} + /> +

+ + ); +}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs.test.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs.test.tsx index 463c0c9d016b3..6a99bd24ef26b 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs.test.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs.test.tsx @@ -17,6 +17,19 @@ const promisesToResolve = () => new Promise((resolve) => setTimeout(resolve, 0)) const mockHttp = httpServiceMock.createSetupContract(); +jest.mock('../app_context', () => { + return { + useAppContext: () => { + return { + docLinks: { + DOC_LINK_VERSION: 'current', + ELASTIC_WEBSITE_URL: 'https://www.elastic.co/', + }, + }; + }, + }; +}); + describe('UpgradeAssistantTabs', () => { test('renders loading state', async () => { mockHttp.get.mockReturnValue( diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/__snapshots__/checkup_tab.test.tsx.snap b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/__snapshots__/checkup_tab.test.tsx.snap index dda051e715234..5aa4a469e4f02 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/__snapshots__/checkup_tab.test.tsx.snap +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/__snapshots__/checkup_tab.test.tsx.snap @@ -40,7 +40,8 @@ exports[`CheckupTab render with deprecations 1`] = ` values={ Object { "snapshotRestoreDocsButton": { + return { + useAppContext: () => { + return { + docLinks: { + DOC_LINK_VERSION: 'current', + ELASTIC_WEBSITE_URL: 'https://www.elastic.co/', + }, + }; + }, + }; +}); + /** * Mostly a dumb container with copy, test the three main states. */ diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/checkup_tab.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/checkup_tab.tsx index 5688903b8f7cd..02cbc87483e55 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/checkup_tab.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/checkup_tab.tsx @@ -5,7 +5,7 @@ */ import { find } from 'lodash'; -import React, { Fragment } from 'react'; +import React, { FunctionComponent, useState } from 'react'; import { EuiCallOut, @@ -20,211 +20,65 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { NEXT_MAJOR_VERSION } from '../../../../../common/version'; import { LoadingErrorBanner } from '../../error_banner'; +import { useAppContext } from '../../../app_context'; import { GroupByOption, LevelFilterOption, LoadingState, - UpgradeAssistantTabComponent, UpgradeAssistantTabProps, } from '../../types'; import { CheckupControls } from './controls'; import { GroupedDeprecations } from './deprecations/grouped'; -interface CheckupTabProps extends UpgradeAssistantTabProps { +export interface CheckupTabProps extends UpgradeAssistantTabProps { checkupLabel: string; showBackupWarning?: boolean; } -interface CheckupTabState { - currentFilter: LevelFilterOption; - search: string; - currentGroupBy: GroupByOption; -} - /** * Displays a list of deprecations that filterable and groupable. Can be used for cluster, * nodes, or indices checkups. */ -export class CheckupTab extends UpgradeAssistantTabComponent { - constructor(props: CheckupTabProps) { - super(props); - - this.state = { - // initialize to all filters - currentFilter: LevelFilterOption.all, - search: '', - currentGroupBy: GroupByOption.message, - }; - } - - public render() { - const { - alertBanner, - checkupLabel, - deprecations, - loadingError, - loadingState, - refreshCheckupData, - setSelectedTabIndex, - showBackupWarning = false, - } = this.props; - const { currentFilter, currentGroupBy } = this.state; - - return ( - - - -

- {checkupLabel}, - nextEsVersion: `${NEXT_MAJOR_VERSION}.x`, - }} - /> -

-
- - - - {alertBanner && ( - - {alertBanner} - - - )} - - {showBackupWarning && ( - - - } - color="warning" - iconType="help" - > -

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

-
- -
- )} - - - - {loadingState === LoadingState.Error ? ( - - ) : deprecations && deprecations.length > 0 ? ( - - - - {this.renderCheckupData()} - - ) : ( - - - - } - body={ - -

- {checkupLabel}, - }} - /> -

-

- setSelectedTabIndex(0)}> - - - ), - }} - /> -

-
- } - /> - )} -
-
-
- ); - } - - private changeFilter = (filter: LevelFilterOption) => { - this.setState({ currentFilter: filter }); +export const CheckupTab: FunctionComponent = ({ + alertBanner, + checkupLabel, + deprecations, + loadingError, + loadingState, + refreshCheckupData, + setSelectedTabIndex, + showBackupWarning = false, +}) => { + const [currentFilter, setCurrentFilter] = useState(LevelFilterOption.all); + const [search, setSearch] = useState(''); + const [currentGroupBy, setCurrentGroupBy] = useState(GroupByOption.message); + + const { docLinks } = useAppContext(); + + const { DOC_LINK_VERSION, ELASTIC_WEBSITE_URL } = docLinks; + const esDocBasePath = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}`; + + const changeFilter = (filter: LevelFilterOption) => { + setCurrentFilter(filter); }; - private changeSearch = (search: string) => { - this.setState({ search }); + const changeSearch = (newSearch: string) => { + setSearch(newSearch); }; - private changeGroupBy = (groupBy: GroupByOption) => { - this.setState({ currentGroupBy: groupBy }); + const changeGroupBy = (groupBy: GroupByOption) => { + setCurrentGroupBy(groupBy); }; - private availableGroupByOptions() { - const { deprecations } = this.props; - + const availableGroupByOptions = () => { if (!deprecations) { return []; } return Object.keys(GroupByOption).filter((opt) => find(deprecations, opt)) as GroupByOption[]; - } - - private renderCheckupData() { - const { deprecations } = this.props; - const { currentFilter, currentGroupBy, search } = this.state; + }; + const renderCheckupData = () => { return ( ); - } -} + }; + + return ( + <> + + +

+ {checkupLabel}, + nextEsVersion: `${NEXT_MAJOR_VERSION}.x`, + }} + /> +

+
+ + + + {alertBanner && ( + <> + {alertBanner} + + + )} + + {showBackupWarning && ( + <> + + } + color="warning" + iconType="help" + > +

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

+
+ + + )} + + + + {loadingState === LoadingState.Error ? ( + + ) : deprecations && deprecations.length > 0 ? ( + <> + + + {renderCheckupData()} + + ) : ( + + + + } + body={ + <> +

+ {checkupLabel}, + }} + /> +

+

+ setSelectedTabIndex(0)}> + + + ), + }} + /> +

+ + } + /> + )} +
+
+ + ); +}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/constants.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/constants.tsx index 66c802097055b..8637099b77c9b 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/constants.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/constants.tsx @@ -6,8 +6,7 @@ import { IconColor } from '@elastic/eui'; import { invert } from 'lodash'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import type { DeprecationInfo } from '../../../../../../../../src/core/server/elasticsearch/legacy/api_types'; +import { DeprecationInfo } from '../../../../../common/types'; export const LEVEL_MAP: { [level: string]: number } = { warning: 0, diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/controls.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/controls.tsx index d75a25a95d67f..c75db7e2f96e1 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/controls.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/controls.tsx @@ -8,8 +8,7 @@ import React, { FunctionComponent, useState } from 'react'; import { i18n } from '@kbn/i18n'; import { EuiButton, EuiFieldSearch, EuiFlexGroup, EuiFlexItem, EuiCallOut } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import type { DeprecationInfo } from '../../../../../../../../src/core/server/elasticsearch/legacy/api_types'; +import { DeprecationInfo } from '../../../../../common/types'; import { GroupByOption, LevelFilterOption, LoadingState } from '../../types'; import { FilterBar } from './filter_bar'; import { GroupByBar } from './group_by_bar'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/grouped.test.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/grouped.test.tsx index 6bdb5df036224..727d959f49a71 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/grouped.test.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/grouped.test.tsx @@ -9,9 +9,7 @@ import React from 'react'; import { mountWithIntl, shallowWithIntl } from '@kbn/test/jest'; import { EuiBadge, EuiPagination } from '@elastic/eui'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import type { DeprecationInfo } from '../../../../../../../../../src/core/server/elasticsearch/legacy/api_types'; -import { EnrichedDeprecationInfo } from '../../../../../../common/types'; +import { DeprecationInfo, EnrichedDeprecationInfo } from '../../../../../../common/types'; import { GroupByOption, LevelFilterOption } from '../../../types'; import { DeprecationAccordion, filterDeps, GroupedDeprecations } from './grouped'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/grouped.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/grouped.tsx index de1a5a996d75f..20ffbae143672 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/grouped.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/grouped.tsx @@ -18,9 +18,7 @@ import { } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import type { DeprecationInfo } from '../../../../../../../../../src/core/server/elasticsearch/legacy/api_types'; -import { EnrichedDeprecationInfo } from '../../../../../../common/types'; +import { DeprecationInfo, EnrichedDeprecationInfo } from '../../../../../../common/types'; import { GroupByOption, LevelFilterOption } from '../../../types'; import { DeprecationCountSummary } from './count_summary'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/health.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/health.tsx index 3ce40d0c4fdf0..c866c1e1f6847 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/health.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/health.tsx @@ -10,8 +10,7 @@ import React, { FunctionComponent } from 'react'; import { EuiBadge, EuiToolTip } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import type { DeprecationInfo } from '../../../../../../../../../src/core/server/elasticsearch/legacy/api_types'; +import { DeprecationInfo } from '../../../../../../common/types'; import { COLOR_MAP, LEVEL_MAP, REVERSE_LEVEL_MAP } from '../constants'; const LocalizedLevels: { [level: string]: string } = { diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/list.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/list.tsx index 038f05aace4c3..afc443b8c3b7d 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/list.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/list.tsx @@ -6,9 +6,7 @@ import React, { FunctionComponent } from 'react'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import type { DeprecationInfo } from '../../../../../../../../../src/core/server/elasticsearch/legacy/api_types'; -import { EnrichedDeprecationInfo } from '../../../../../../common/types'; +import { DeprecationInfo, EnrichedDeprecationInfo } from '../../../../../../common/types'; import { GroupByOption } from '../../../types'; import { COLOR_MAP, LEVEL_MAP } from '../constants'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/warning_step.test.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/warning_step.test.tsx index 318d2bc7baffe..6428edfbe904d 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/warning_step.test.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/warning_step.test.tsx @@ -11,6 +11,19 @@ import React from 'react'; import { ReindexWarning } from '../../../../../../../../common/types'; import { idForWarning, WarningsFlyoutStep } from './warnings_step'; +jest.mock('../../../../../../app_context', () => { + return { + useAppContext: () => { + return { + docLinks: { + DOC_LINK_VERSION: 'current', + ELASTIC_WEBSITE_URL: 'https://www.elastic.co/', + }, + }; + }, + }; +}); + describe('WarningsFlyoutStep', () => { const defaultProps = { advanceNextStep: jest.fn(), diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/warnings_step.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/warnings_step.tsx index c3ef0fde6e749..9f48c77ec38e1 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/warnings_step.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/deprecations/reindex/flyout/warnings_step.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment } from 'react'; +import React, { useState } from 'react'; import { EuiButton, @@ -21,6 +21,7 @@ import { EuiText, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; +import { useAppContext } from '../../../../../../app_context'; import { ReindexWarning } from '../../../../../../../../common/types'; interface CheckedIds { @@ -37,7 +38,7 @@ const WarningCheckbox: React.FunctionComponent<{ documentationUrl: string; onChange: (event: React.ChangeEvent) => void; }> = ({ checkedIds, warning, label, onChange, description, documentationUrl }) => ( - + <> {description}
- + -
+ ); interface WarningsConfirmationFlyoutProps { @@ -68,175 +69,169 @@ interface WarningsConfirmationFlyoutProps { advanceNextStep: () => void; } -interface WarningsConfirmationFlyoutState { - checkedIds: CheckedIds; -} - /** * Displays warning text about destructive changes required to reindex this index. The user * must acknowledge each change before being allowed to proceed. */ -export class WarningsFlyoutStep extends React.Component< - WarningsConfirmationFlyoutProps, - WarningsConfirmationFlyoutState -> { - constructor(props: WarningsConfirmationFlyoutProps) { - super(props); - - this.state = { - checkedIds: props.warnings.reduce((checkedIds, warning) => { - checkedIds[idForWarning(warning)] = false; - return checkedIds; - }, {} as { [id: string]: boolean }), - }; - } - - public render() { - const { warnings, closeFlyout, advanceNextStep, renderGlobalCallouts } = this.props; - const { checkedIds } = this.state; - - // Do not allow to proceed until all checkboxes are checked. - const blockAdvance = Object.values(checkedIds).filter((v) => v).length < warnings.length; - - return ( - - - {renderGlobalCallouts()} - = ({ + warnings, + renderGlobalCallouts, + closeFlyout, + advanceNextStep, +}) => { + const [checkedIds, setCheckedIds] = useState( + warnings.reduce((initialCheckedIds, warning) => { + initialCheckedIds[idForWarning(warning)] = false; + return initialCheckedIds; + }, {} as { [id: string]: boolean }) + ); + + // Do not allow to proceed until all checkboxes are checked. + const blockAdvance = Object.values(checkedIds).filter((v) => v).length < warnings.length; + + const onChange = (e: React.ChangeEvent) => { + const optionId = e.target.id; + + setCheckedIds((prev) => ({ + ...prev, + ...{ + [optionId]: !checkedIds[optionId], + }, + })); + }; + + const { docLinks } = useAppContext(); + const { ELASTIC_WEBSITE_URL } = docLinks; + const esDocBasePath = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference`; + const observabilityDocBasePath = `${ELASTIC_WEBSITE_URL}guide/en/observability`; + + // TODO: Revisit warnings returned for 8.0 upgrade; many of these are likely obselete now + return ( + <> + + {renderGlobalCallouts()} + + } + color="danger" + iconType="alert" + > +

+ +

+
+ + + + {warnings.includes(ReindexWarning.allField) && ( + _all, + }} /> } - color="danger" - iconType="alert" - > -

+ description={ -

-
- - - - {warnings.includes(ReindexWarning.allField) && ( - _all, - }} - /> - } - description={ - _all, - }} - /> - } - documentationUrl="https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_the_literal__all_literal_meta_field_is_now_disabled_by_default" - /> - )} - - {warnings.includes(ReindexWarning.apmReindex) && ( - - } - description={ - + } + description={ + - } - documentationUrl="https://www.elastic.co/guide/en/apm/get-started/master/apm-release-notes.html" - /> - )} - - {warnings.includes(ReindexWarning.booleanFields) && ( - _source }} - /> - } - description={ - _source }} + /> + } + description={ + true, - false: false, - yes: "yes", - on: "on", - one: 1, - }} - /> - } - documentationUrl="https://www.elastic.co/guide/en/elasticsearch/reference/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_field" - /> - )} -
- - - - - - - - - - - - - - -
- ); - } - - private onChange = (e: React.ChangeEvent) => { - const optionId = e.target.id; - const nextCheckedIds = { - ...this.state.checkedIds, - ...{ - [optionId]: !this.state.checkedIds[optionId], - }, - }; - - this.setState({ checkedIds: nextCheckedIds }); - }; -} + values={{ + true: true, + false: false, + yes: "yes", + on: "on", + one: 1, + }} + /> + } + documentationUrl={`${esDocBasePath}/6.0/breaking_60_mappings_changes.html#_coercion_of_boolean_field`} + /> + )} + + + + + + + + + + + + + + + + + ); +}; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/filter_bar.test.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/filter_bar.test.tsx index 053ef21d6b309..231b15fc52d72 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/filter_bar.test.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/filter_bar.test.tsx @@ -6,9 +6,8 @@ import { mount, shallow } from 'enzyme'; import React from 'react'; +import { DeprecationInfo } from '../../../../../common/types'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import type { DeprecationInfo } from '../../../../../../../../src/core/server/elasticsearch/legacy/api_types'; import { LevelFilterOption } from '../../types'; import { FilterBar } from './filter_bar'; diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/filter_bar.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/filter_bar.tsx index 6939c547fee57..abcd02d5a5ce4 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/filter_bar.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/checkup/filter_bar.tsx @@ -10,8 +10,7 @@ import React from 'react'; import { EuiFilterButton, EuiFilterGroup, EuiFlexItem } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import type { DeprecationInfo } from '../../../../../../../../src/core/server/elasticsearch/legacy/api_types'; +import { DeprecationInfo } from '../../../../../common/types'; import { LevelFilterOption } from '../../types'; const LocalizedOptions: { [option: string]: string } = { diff --git a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/steps.tsx b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/steps.tsx index 85d275b080e13..1a1ea48a350c8 100644 --- a/x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/steps.tsx +++ b/x-pack/plugins/upgrade_assistant/public/application/components/tabs/overview/steps.tsx @@ -54,7 +54,7 @@ const WAIT_FOR_RELEASE_STEP = { // Swap in this step for the one above it on the last minor release. // @ts-ignore -const START_UPGRADE_STEP = (isCloudEnabled: boolean) => ({ +const START_UPGRADE_STEP = (isCloudEnabled: boolean, esDocBasePath: string) => ({ title: i18n.translate('xpack.upgradeAssistant.overviewTab.steps.startUpgradeStep.stepTitle', { defaultMessage: 'Start your upgrade', }), @@ -73,10 +73,7 @@ const START_UPGRADE_STEP = (isCloudEnabled: boolean) => ({ defaultMessage="Follow {instructionButton} to start your upgrade." values={{ instructionButton: ( - + ); diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_deprecation_logging_apis.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecation_logging_apis.test.ts index b0dec299b2b12..dee05c97f11af 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/es_deprecation_logging_apis.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecation_logging_apis.test.ts @@ -12,10 +12,10 @@ import { describe('getDeprecationLoggingStatus', () => { it('calls cluster.getSettings', async () => { - const dataClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); + const dataClient = elasticsearchServiceMock.createScopedClusterClient(); await getDeprecationLoggingStatus(dataClient); - expect(dataClient.callAsCurrentUser).toHaveBeenCalledWith('cluster.getSettings', { - includeDefaults: true, + expect(dataClient.asCurrentUser.cluster.getSettings).toHaveBeenCalledWith({ + include_defaults: true, }); }); }); @@ -23,9 +23,9 @@ describe('getDeprecationLoggingStatus', () => { describe('setDeprecationLogging', () => { describe('isEnabled = true', () => { it('calls cluster.putSettings with logger.deprecation = WARN', async () => { - const dataClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); + const dataClient = elasticsearchServiceMock.createScopedClusterClient(); await setDeprecationLogging(dataClient, true); - expect(dataClient.callAsCurrentUser).toHaveBeenCalledWith('cluster.putSettings', { + expect(dataClient.asCurrentUser.cluster.putSettings).toHaveBeenCalledWith({ body: { transient: { 'logger.deprecation': 'WARN' } }, }); }); @@ -33,9 +33,9 @@ describe('setDeprecationLogging', () => { describe('isEnabled = false', () => { it('calls cluster.putSettings with logger.deprecation = ERROR', async () => { - const dataClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); + const dataClient = elasticsearchServiceMock.createScopedClusterClient(); await setDeprecationLogging(dataClient, false); - expect(dataClient.callAsCurrentUser).toHaveBeenCalledWith('cluster.putSettings', { + expect(dataClient.asCurrentUser.cluster.putSettings).toHaveBeenCalledWith({ body: { transient: { 'logger.deprecation': 'ERROR' } }, }); }); diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_deprecation_logging_apis.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecation_logging_apis.ts index 348eebb97e384..c545d12ac1b82 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/es_deprecation_logging_apis.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_deprecation_logging_apis.ts @@ -4,17 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ import { get } from 'lodash'; -import { ILegacyScopedClusterClient } from 'src/core/server'; +import { IScopedClusterClient } from 'src/core/server'; interface DeprecationLoggingStatus { isEnabled: boolean; } export async function getDeprecationLoggingStatus( - dataClient: ILegacyScopedClusterClient + dataClient: IScopedClusterClient ): Promise { - const response = await dataClient.callAsCurrentUser('cluster.getSettings', { - includeDefaults: true, + const { body: response } = await dataClient.asCurrentUser.cluster.getSettings({ + include_defaults: true, }); return { @@ -23,10 +23,10 @@ export async function getDeprecationLoggingStatus( } export async function setDeprecationLogging( - dataClient: ILegacyScopedClusterClient, + dataClient: IScopedClusterClient, isEnabled: boolean ): Promise { - const response = await dataClient.callAsCurrentUser('cluster.putSettings', { + const { body: response } = await dataClient.asCurrentUser.cluster.putSettings({ body: { transient: { 'logger.deprecation': isEnabled ? 'WARN' : 'ERROR', diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_indices_state_check.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_indices_state_check.ts index bce48b152700f..739499e2235b5 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/es_indices_state_check.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_indices_state_check.ts @@ -4,22 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import { LegacyAPICaller } from 'kibana/server'; +import { ElasticsearchClient } from 'kibana/server'; import { getIndexState } from '../../common/get_index_state'; import { ResolveIndexResponseFromES } from '../../common/types'; type StatusCheckResult = Record; export const esIndicesStateCheck = async ( - callAsUser: LegacyAPICaller, + asCurrentUser: ElasticsearchClient, indices: string[] ): Promise => { - const response: ResolveIndexResponseFromES = await callAsUser('transport.request', { - method: 'GET', - path: `/_resolve/index/*`, - query: { - expand_wildcards: 'all', - }, + const { body: response } = await asCurrentUser.indices.resolveIndex({ + name: '*', + expand_wildcards: 'all', }); const result: StatusCheckResult = {}; diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.test.ts index 312a7275382b8..6e8a729b4d3bb 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.test.ts @@ -5,95 +5,94 @@ */ import _ from 'lodash'; +import { RequestEvent } from '@elastic/elasticsearch/lib/Transport'; import { elasticsearchServiceMock } from 'src/core/server/mocks'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import type { DeprecationAPIResponse } from '../../../../../src/core/server/elasticsearch/legacy/api_types'; +import { DeprecationAPIResponse } from '../../common/types'; import { getUpgradeAssistantStatus } from './es_migration_apis'; import fakeDeprecations from './__fixtures__/fake_deprecations.json'; const fakeIndexNames = Object.keys(fakeDeprecations.index_settings); +const asApiResponse = (body: T): RequestEvent => + ({ + body, + } as RequestEvent); + describe('getUpgradeAssistantStatus', () => { const resolvedIndices = { indices: fakeIndexNames.map((f) => ({ name: f, attributes: ['open'] })), }; - let deprecationsResponse: DeprecationAPIResponse; - - const dataClient = elasticsearchServiceMock.createLegacyScopedClusterClient(); - (dataClient.callAsCurrentUser as jest.Mock).mockImplementation(async (api, { path }) => { - if (path === '/_migration/deprecations') { - return deprecationsResponse; - } else if (path === '/_resolve/index/*') { - return resolvedIndices; - } else if (api === 'indices.getMapping') { - return {}; - } else { - throw new Error(`Unexpected API call: ${path}`); - } - }); + // @ts-expect-error mock data is too loosely typed + const deprecationsResponse: DeprecationAPIResponse = _.cloneDeep(fakeDeprecations); - beforeEach(() => { - // @ts-expect-error mock data is too loosely typed - deprecationsResponse = _.cloneDeep(fakeDeprecations); - }); + const esClient = elasticsearchServiceMock.createScopedClusterClient(); + + esClient.asCurrentUser.migration.deprecations.mockResolvedValue( + asApiResponse(deprecationsResponse) + ); + + esClient.asCurrentUser.indices.resolveIndex.mockResolvedValue(asApiResponse(resolvedIndices)); it('calls /_migration/deprecations', async () => { - await getUpgradeAssistantStatus(dataClient, false); - expect(dataClient.callAsCurrentUser).toHaveBeenCalledWith('transport.request', { - path: '/_migration/deprecations', - method: 'GET', - }); + await getUpgradeAssistantStatus(esClient, false); + expect(esClient.asCurrentUser.migration.deprecations).toHaveBeenCalled(); }); it('returns the correct shape of data', async () => { - const resp = await getUpgradeAssistantStatus(dataClient, false); + const resp = await getUpgradeAssistantStatus(esClient, false); expect(resp).toMatchSnapshot(); }); it('returns readyForUpgrade === false when critical issues found', async () => { - deprecationsResponse = { - cluster_settings: [{ level: 'critical', message: 'Do count me', url: 'https://...' }], - node_settings: [], - ml_settings: [], - index_settings: {}, - }; - - await expect(getUpgradeAssistantStatus(dataClient, false)).resolves.toHaveProperty( + esClient.asCurrentUser.migration.deprecations.mockResolvedValue( + asApiResponse({ + cluster_settings: [{ level: 'critical', message: 'Do count me', url: 'https://...' }], + node_settings: [], + ml_settings: [], + index_settings: {}, + }) + ); + + await expect(getUpgradeAssistantStatus(esClient, false)).resolves.toHaveProperty( 'readyForUpgrade', false ); }); it('returns readyForUpgrade === true when no critical issues found', async () => { - deprecationsResponse = { - cluster_settings: [{ level: 'warning', message: 'Do not count me', url: 'https://...' }], - node_settings: [], - ml_settings: [], - index_settings: {}, - }; - - await expect(getUpgradeAssistantStatus(dataClient, false)).resolves.toHaveProperty( + esClient.asCurrentUser.migration.deprecations.mockResolvedValue( + asApiResponse({ + cluster_settings: [{ level: 'warning', message: 'Do not count me', url: 'https://...' }], + node_settings: [], + ml_settings: [], + index_settings: {}, + }) + ); + + await expect(getUpgradeAssistantStatus(esClient, false)).resolves.toHaveProperty( 'readyForUpgrade', true ); }); it('filters out security realm deprecation on Cloud', async () => { - deprecationsResponse = { - cluster_settings: [ - { - level: 'critical', - message: 'Security realm settings structure changed', - url: 'https://...', - }, - ], - node_settings: [], - ml_settings: [], - index_settings: {}, - }; - - const result = await getUpgradeAssistantStatus(dataClient, true); + esClient.asCurrentUser.migration.deprecations.mockResolvedValue( + asApiResponse({ + cluster_settings: [ + { + level: 'critical', + message: 'Security realm settings structure changed', + url: 'https://...', + }, + ], + node_settings: [], + ml_settings: [], + index_settings: {}, + }) + ); + + const result = await getUpgradeAssistantStatus(esClient, true); expect(result).toHaveProperty('readyForUpgrade', true); expect(result).toHaveProperty('cluster', []); diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.ts index 9f55b9d049735..22b9956fc957a 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_migration_apis.ts @@ -4,21 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ILegacyScopedClusterClient } from 'src/core/server'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import type { DeprecationAPIResponse } from '../../../../../src/core/server/elasticsearch/legacy/api_types'; -import { EnrichedDeprecationInfo, UpgradeAssistantStatus } from '../../common/types'; +import { IScopedClusterClient } from 'src/core/server'; +import { + DeprecationAPIResponse, + EnrichedDeprecationInfo, + UpgradeAssistantStatus, +} from '../../common/types'; import { esIndicesStateCheck } from './es_indices_state_check'; export async function getUpgradeAssistantStatus( - dataClient: ILegacyScopedClusterClient, + dataClient: IScopedClusterClient, isCloudEnabled: boolean ): Promise { - const deprecations = await dataClient.callAsCurrentUser('transport.request', { - path: '/_migration/deprecations', - method: 'GET', - }); + const { + body: deprecations, + } = await dataClient.asCurrentUser.migration.deprecations(); const cluster = getClusterDeprecations(deprecations, isCloudEnabled); const indices = getCombinedIndexInfos(deprecations); @@ -28,10 +29,7 @@ export async function getUpgradeAssistantStatus( // If we have found deprecation information for index/indices check whether the index is // open or closed. if (indexNames.length) { - const indexStates = await esIndicesStateCheck( - dataClient.callAsCurrentUser.bind(dataClient), - indexNames - ); + const indexStates = await esIndicesStateCheck(dataClient.asCurrentUser, indexNames); indices.forEach((indexData) => { indexData.blockerForReindexing = diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.test.ts index 78f159ed98867..2310f993ce27d 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.test.ts @@ -5,7 +5,7 @@ */ import { SemVer } from 'semver'; -import { ILegacyScopedClusterClient, kibanaResponseFactory } from 'src/core/server'; +import { IScopedClusterClient, kibanaResponseFactory } from 'src/core/server'; import { xpackMocks } from '../../../../mocks'; import { CURRENT_VERSION } from '../../common/version'; import { @@ -17,14 +17,20 @@ import { describe('getAllNodeVersions', () => { it('returns a list of unique node versions', async () => { const adminClient = ({ - callAsInternalUser: jest.fn().mockResolvedValue({ + asInternalUser: { nodes: { - node1: { version: '7.0.0' }, - node2: { version: '7.0.0' }, - node3: { version: '6.0.0' }, + info: jest.fn().mockResolvedValue({ + body: { + nodes: { + node1: { version: '7.0.0' }, + node2: { version: '7.0.0' }, + node3: { version: '6.0.0' }, + }, + }, + }), }, - }), - } as unknown) as ILegacyScopedClusterClient; + }, + } as unknown) as IScopedClusterClient; await expect(getAllNodeVersions(adminClient)).resolves.toEqual([ new SemVer('6.0.0'), @@ -73,12 +79,18 @@ describe('verifyAllMatchKibanaVersion', () => { describe('EsVersionPrecheck', () => { it('returns a 403 when callCluster fails with a 403', async () => { - const fakeCall = jest.fn().mockRejectedValue({ status: 403 }); + const fakeCall = jest.fn().mockRejectedValue({ statusCode: 403 }); const ctx = xpackMocks.createRequestHandlerContext(); - ctx.core.elasticsearch.legacy.client = { - callAsCurrentUser: jest.fn(), - callAsInternalUser: fakeCall, + ctx.core.elasticsearch.client = { + asInternalUser: { + ...ctx.core.elasticsearch.client.asInternalUser, + nodes: { + ...ctx.core.elasticsearch.client.asInternalUser.nodes, + info: fakeCall, + }, + }, + asCurrentUser: ctx.core.elasticsearch.client.asCurrentUser, }; const result = await esVersionCheck(ctx, kibanaResponseFactory); @@ -87,14 +99,22 @@ describe('EsVersionPrecheck', () => { it('returns a 426 message w/ allNodesUpgraded = false when nodes are not on same version', async () => { const ctx = xpackMocks.createRequestHandlerContext(); - ctx.core.elasticsearch.legacy.client = { - callAsCurrentUser: jest.fn(), - callAsInternalUser: jest.fn().mockResolvedValue({ + ctx.core.elasticsearch.client = { + asInternalUser: { + ...ctx.core.elasticsearch.client.asInternalUser, nodes: { - node1: { version: CURRENT_VERSION.raw }, - node2: { version: new SemVer(CURRENT_VERSION.raw).inc('major').raw }, + ...ctx.core.elasticsearch.client.asInternalUser.nodes, + info: jest.fn().mockResolvedValue({ + body: { + nodes: { + node1: { version: CURRENT_VERSION.raw }, + node2: { version: new SemVer(CURRENT_VERSION.raw).inc('major').raw }, + }, + }, + }), }, - }), + }, + asCurrentUser: ctx.core.elasticsearch.client.asCurrentUser, }; const result = await esVersionCheck(ctx, kibanaResponseFactory); @@ -104,14 +124,22 @@ describe('EsVersionPrecheck', () => { it('returns a 426 message w/ allNodesUpgraded = true when nodes are on next version', async () => { const ctx = xpackMocks.createRequestHandlerContext(); - ctx.core.elasticsearch.legacy.client = { - callAsCurrentUser: jest.fn(), - callAsInternalUser: jest.fn().mockResolvedValue({ + ctx.core.elasticsearch.client = { + asInternalUser: { + ...ctx.core.elasticsearch.client.asInternalUser, nodes: { - node1: { version: new SemVer(CURRENT_VERSION.raw).inc('major').raw }, - node2: { version: new SemVer(CURRENT_VERSION.raw).inc('major').raw }, + ...ctx.core.elasticsearch.client.asInternalUser.nodes, + info: jest.fn().mockResolvedValue({ + body: { + nodes: { + node1: { version: new SemVer(CURRENT_VERSION.raw).inc('major').raw }, + node2: { version: new SemVer(CURRENT_VERSION.raw).inc('major').raw }, + }, + }, + }), }, - }), + }, + asCurrentUser: ctx.core.elasticsearch.client.asCurrentUser, }; const result = await esVersionCheck(ctx, kibanaResponseFactory); @@ -121,14 +149,22 @@ describe('EsVersionPrecheck', () => { it('returns undefined when nodes are on same version', async () => { const ctx = xpackMocks.createRequestHandlerContext(); - ctx.core.elasticsearch.legacy.client = { - callAsCurrentUser: jest.fn(), - callAsInternalUser: jest.fn().mockResolvedValue({ + ctx.core.elasticsearch.client = { + asInternalUser: { + ...ctx.core.elasticsearch.client.asInternalUser, nodes: { - node1: { version: CURRENT_VERSION.raw }, - node2: { version: CURRENT_VERSION.raw }, + ...ctx.core.elasticsearch.client.asInternalUser.nodes, + info: jest.fn().mockResolvedValue({ + body: { + nodes: { + node1: { version: CURRENT_VERSION.raw }, + node2: { version: CURRENT_VERSION.raw }, + }, + }, + }), }, - }), + }, + asCurrentUser: ctx.core.elasticsearch.client.asCurrentUser, }; await expect(esVersionCheck(ctx, kibanaResponseFactory)).resolves.toBe(undefined); diff --git a/x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.ts b/x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.ts index 2b49d4c286f61..be6c4f5ff0230 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/es_version_precheck.ts @@ -7,7 +7,7 @@ import { uniq } from 'lodash'; import { SemVer } from 'semver'; import { - ILegacyScopedClusterClient, + IScopedClusterClient, KibanaRequest, KibanaResponseFactory, RequestHandler, @@ -15,14 +15,22 @@ import { } from 'src/core/server'; import { CURRENT_VERSION } from '../../common/version'; +interface Nodes { + nodes: { + [nodeId: string]: { version: string }; + }; +} + /** * Returns an array of all the unique Elasticsearch Node Versions in the Elasticsearch cluster. */ -export const getAllNodeVersions = async (adminClient: ILegacyScopedClusterClient) => { +export const getAllNodeVersions = async (adminClient: IScopedClusterClient) => { // Get the version information for all nodes in the cluster. - const { nodes } = (await adminClient.callAsInternalUser('nodes.info', { - filterPath: 'nodes.*.version', - })) as { nodes: { [nodeId: string]: { version: string } } }; + const response = await adminClient.asInternalUser.nodes.info({ + filter_path: 'nodes.*.version', + }); + + const nodes = response.body.nodes; const versionStrings = Object.values(nodes).map(({ version }) => version); @@ -62,13 +70,13 @@ export const esVersionCheck = async ( ctx: RequestHandlerContext, response: KibanaResponseFactory ) => { - const { client } = ctx.core.elasticsearch.legacy; + const { client } = ctx.core.elasticsearch; let allNodeVersions: SemVer[]; try { allNodeVersions = await getAllNodeVersions(client); } catch (e) { - if (e.status === 403) { + if (e.statusCode === 403) { return response.forbidden({ body: e.message }); } diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts index 525c3781be749..d059c03bcecb1 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.test.ts @@ -3,7 +3,11 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsErrorHelpers } from '../../../../../../src/core/server'; +import { RequestEvent } from '@elastic/elasticsearch/lib/Transport'; +import { SavedObjectsErrorHelpers } from 'src/core/server'; +import { elasticsearchServiceMock } from 'src/core/server/mocks'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { ScopedClusterClientMock } from 'src/core/server/elasticsearch/client/mocks'; import moment from 'moment'; import { @@ -18,7 +22,7 @@ import { LOCK_WINDOW, ReindexActions, reindexActionsFactory } from './reindex_ac describe('ReindexActions', () => { let client: jest.Mocked; - let callCluster: jest.Mock; + let clusterClient: ScopedClusterClientMock; let actions: ReindexActions; const unimplemented = (name: string) => () => @@ -38,8 +42,8 @@ describe('ReindexActions', () => { Promise.resolve({ id, attributes } as ReindexSavedObject) ) as any, }; - callCluster = jest.fn(); - actions = reindexActionsFactory(client, callCluster); + clusterClient = elasticsearchServiceMock.createScopedClusterClient(); + actions = reindexActionsFactory(client, clusterClient.asCurrentUser); }); describe('createReindexOp', () => { @@ -281,13 +285,20 @@ describe('ReindexActions', () => { }); describe('getFlatSettings', () => { + const asApiResponse = (body: T): RequestEvent => + ({ + body, + } as RequestEvent); + it('returns flat settings', async () => { - callCluster.mockResolvedValueOnce({ - myIndex: { - settings: { 'index.mySetting': '1' }, - mappings: {}, - }, - }); + clusterClient.asCurrentUser.indices.getSettings.mockResolvedValueOnce( + asApiResponse({ + myIndex: { + settings: { 'index.mySetting': '1' }, + mappings: {}, + }, + }) + ); await expect(actions.getFlatSettings('myIndex')).resolves.toEqual({ settings: { 'index.mySetting': '1' }, mappings: {}, @@ -295,7 +306,7 @@ describe('ReindexActions', () => { }); it('returns null if index does not exist', async () => { - callCluster.mockResolvedValueOnce({}); + clusterClient.asCurrentUser.indices.getSettings.mockResolvedValueOnce(asApiResponse({})); await expect(actions.getFlatSettings('myIndex')).resolves.toBeNull(); }); }); diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.ts index 6d8afee1ff950..611ab3c92b72b 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_actions.ts @@ -9,7 +9,7 @@ import moment from 'moment'; import { SavedObjectsFindResponse, SavedObjectsClientContract, - LegacyAPICaller, + ElasticsearchClient, } from 'src/core/server'; import { IndexGroup, @@ -116,7 +116,7 @@ export interface ReindexActions { export const reindexActionsFactory = ( client: SavedObjectsClientContract, - callAsUser: LegacyAPICaller + esClient: ElasticsearchClient ): ReindexActions => { // ----- Internal functions const isLocked = (reindexOp: ReindexSavedObject) => { @@ -236,9 +236,12 @@ export const reindexActionsFactory = ( }, async getFlatSettings(indexName: string) { - const flatSettings = (await callAsUser('transport.request', { - path: `/${encodeURIComponent(indexName)}?flat_settings=true`, - })) as { [indexName: string]: FlatSettings }; + const { body: flatSettings } = await esClient.indices.getSettings<{ + [indexName: string]: FlatSettings; + }>({ + index: indexName, + flat_settings: true, + }); if (!flatSettings[indexName]) { return null; diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts index dea9974791a88..8a7033c1594da 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.test.ts @@ -5,8 +5,11 @@ */ jest.mock('../es_indices_state_check', () => ({ esIndicesStateCheck: jest.fn() })); import { BehaviorSubject } from 'rxjs'; +import { RequestEvent } from '@elastic/elasticsearch/lib/Transport'; import { Logger } from 'src/core/server'; -import { loggingSystemMock } from 'src/core/server/mocks'; +import { elasticsearchServiceMock, loggingSystemMock } from 'src/core/server/mocks'; +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { ScopedClusterClientMock } from 'src/core/server/elasticsearch/client/mocks'; import { IndexGroup, @@ -28,9 +31,14 @@ import { reindexServiceFactory, } from './reindex_service'; +const asApiResponse = (body: T): RequestEvent => + ({ + body, + } as RequestEvent); + describe('reindexService', () => { let actions: jest.Mocked; - let callCluster: jest.Mock; + let clusterClient: ScopedClusterClientMock; let log: Logger; let service: ReindexService; let licensingPluginSetup: LicensingPluginSetup; @@ -59,7 +67,7 @@ describe('reindexService', () => { decrementIndexGroupReindexes: jest.fn(unimplemented('decrementIndexGroupReindexes')), runWhileIndexGroupLocked: jest.fn(async (group: string, f: any) => f({ attributes: {} })), }; - callCluster = jest.fn(); + clusterClient = elasticsearchServiceMock.createScopedClusterClient(); log = loggingSystemMock.create().get(); licensingPluginSetup = licensingMock.createSetup(); licensingPluginSetup.license$ = new BehaviorSubject( @@ -68,7 +76,12 @@ describe('reindexService', () => { }) ); - service = reindexServiceFactory(callCluster as any, actions, log, licensingPluginSetup); + service = reindexServiceFactory( + clusterClient.asCurrentUser, + actions, + log, + licensingPluginSetup + ); }); describe('hasRequiredPrivileges', () => { @@ -83,13 +96,13 @@ describe('reindexService', () => { }); it('calls security API with basic requirements', async () => { - callCluster.mockResolvedValueOnce({ has_all_requested: true }); + clusterClient.asCurrentUser.security.hasPrivileges.mockResolvedValueOnce( + asApiResponse({ has_all_requested: true }) + ); const hasRequired = await service.hasRequiredPrivileges('anIndex'); expect(hasRequired).toBe(true); - expect(callCluster).toHaveBeenCalledWith('transport.request', { - path: '/_security/user/_has_privileges', - method: 'POST', + expect(clusterClient.asCurrentUser.security.hasPrivileges).toHaveBeenCalledWith({ body: { cluster: ['manage'], index: [ @@ -108,12 +121,12 @@ describe('reindexService', () => { }); it('includes manage_ml for ML indices', async () => { - callCluster.mockResolvedValueOnce({ has_all_requested: true }); + clusterClient.asCurrentUser.security.hasPrivileges.mockResolvedValueOnce( + asApiResponse({ has_all_requested: true }) + ); await service.hasRequiredPrivileges('.ml-anomalies'); - expect(callCluster).toHaveBeenCalledWith('transport.request', { - path: '/_security/user/_has_privileges', - method: 'POST', + expect(clusterClient.asCurrentUser.security.hasPrivileges).toHaveBeenCalledWith({ body: { cluster: ['manage', 'manage_ml'], index: [ @@ -132,15 +145,15 @@ describe('reindexService', () => { }); it('includes checking for permissions on the baseName which could be an alias', async () => { - callCluster.mockResolvedValueOnce({ has_all_requested: true }); + clusterClient.asCurrentUser.security.hasPrivileges.mockResolvedValueOnce( + asApiResponse({ has_all_requested: true }) + ); const hasRequired = await service.hasRequiredPrivileges( `reindexed-v${PREV_MAJOR_VERSION}-anIndex` ); expect(hasRequired).toBe(true); - expect(callCluster).toHaveBeenCalledWith('transport.request', { - path: '/_security/user/_has_privileges', - method: 'POST', + expect(clusterClient.asCurrentUser.security.hasPrivileges).toHaveBeenCalledWith({ body: { cluster: ['manage'], index: [ @@ -163,12 +176,14 @@ describe('reindexService', () => { }); it('includes manage_watcher for watcher indices', async () => { - callCluster.mockResolvedValueOnce({ has_all_requested: true }); + clusterClient.asCurrentUser.security.hasPrivileges.mockResolvedValueOnce( + asApiResponse({ + has_all_requested: true, + }) + ); await service.hasRequiredPrivileges('.watches'); - expect(callCluster).toHaveBeenCalledWith('transport.request', { - path: '/_security/user/_has_privileges', - method: 'POST', + expect(clusterClient.asCurrentUser.security.hasPrivileges).toHaveBeenCalledWith({ body: { cluster: ['manage', 'manage_watcher'], index: [ @@ -212,7 +227,7 @@ describe('reindexService', () => { describe('createReindexOperation', () => { it('creates new reindex operation', async () => { - callCluster.mockResolvedValueOnce(true); // indices.exist + clusterClient.asCurrentUser.indices.exists.mockResolvedValueOnce(asApiResponse(true)); actions.findReindexOperations.mockResolvedValueOnce({ total: 0 }); actions.createReindexOp.mockResolvedValueOnce(); @@ -222,13 +237,13 @@ describe('reindexService', () => { }); it('fails if index does not exist', async () => { - callCluster.mockResolvedValueOnce(false); // indices.exist + clusterClient.asCurrentUser.indices.exists.mockResolvedValueOnce(asApiResponse(false)); await expect(service.createReindexOperation('myIndex')).rejects.toThrow(); expect(actions.createReindexOp).not.toHaveBeenCalled(); }); it('deletes existing operation if it failed', async () => { - callCluster.mockResolvedValueOnce(true); // indices.exist + clusterClient.asCurrentUser.indices.exists.mockResolvedValueOnce(asApiResponse(true)); actions.findReindexOperations.mockResolvedValueOnce({ saved_objects: [{ id: 1, attributes: { status: ReindexStatus.failed } }], total: 1, @@ -244,7 +259,7 @@ describe('reindexService', () => { }); it('deletes existing operation if it was cancelled', async () => { - callCluster.mockResolvedValueOnce(true); // indices.exist + clusterClient.asCurrentUser.indices.exists.mockResolvedValueOnce(asApiResponse(true)); actions.findReindexOperations.mockResolvedValueOnce({ saved_objects: [{ id: 1, attributes: { status: ReindexStatus.cancelled } }], total: 1, @@ -260,7 +275,7 @@ describe('reindexService', () => { }); it('fails if existing operation did not fail', async () => { - callCluster.mockResolvedValueOnce(true); // indices.exist + clusterClient.asCurrentUser.indices.exists.mockResolvedValueOnce(asApiResponse(true)); actions.findReindexOperations.mockResolvedValueOnce({ saved_objects: [{ id: 1, attributes: { status: ReindexStatus.inProgress } }], total: 1, @@ -418,10 +433,11 @@ describe('reindexService', () => { reindexTaskId: '999333', }, } as any); - callCluster.mockResolvedValueOnce(true); + + clusterClient.asCurrentUser.tasks.cancel.mockResolvedValueOnce(asApiResponse(true)); await service.cancelReindexing('myIndex'); - expect(callCluster).toHaveBeenCalledWith('tasks.cancel', { taskId: '999333' }); + expect(clusterClient.asCurrentUser.tasks.cancel).toHaveBeenCalledWith({ task_id: '999333' }); findSpy.mockRestore(); }); @@ -433,7 +449,11 @@ describe('reindexService', () => { const findSpy = jest.spyOn(service, 'findReindexOperation').mockResolvedValueOnce(reindexOp); await expect(service.cancelReindexing('myIndex')).rejects.toThrow(); - expect(callCluster).not.toHaveBeenCalledWith('tasks.cancel', { taskId: '999333' }); + expect(clusterClient.asCurrentUser.tasks.cancel).not.toHaveBeenCalledWith( + asApiResponse({ + taskId: '999333', + }) + ); findSpy.mockRestore(); }); @@ -450,7 +470,11 @@ describe('reindexService', () => { const findSpy = jest.spyOn(service, 'findReindexOperation').mockResolvedValueOnce(reindexOp); await expect(service.cancelReindexing('myIndex')).rejects.toThrow(); - expect(callCluster).not.toHaveBeenCalledWith('tasks.cancel', { taskId: '999333' }); + expect(clusterClient.asCurrentUser.tasks.cancel).not.toHaveBeenCalledWith( + asApiResponse({ + taskId: '999333', + }) + ); findSpy.mockRestore(); }); @@ -525,7 +549,7 @@ describe('reindexService', () => { ); expect(actions.incrementIndexGroupReindexes).not.toHaveBeenCalled(); expect(actions.runWhileIndexGroupLocked).not.toHaveBeenCalled(); - expect(callCluster).not.toHaveBeenCalled(); + expect(clusterClient.asCurrentUser.nodes.info).not.toHaveBeenCalled(); }); it('supports an already migrated ML index', async () => { @@ -533,11 +557,12 @@ describe('reindexService', () => { actions.runWhileIndexGroupLocked.mockImplementationOnce(async (group: string, f: any) => f() ); - callCluster - // Mock call to /_nodes for version check - .mockResolvedValueOnce({ nodes: { nodeX: { version: '6.7.0-alpha' } } }) - // Mock call to /_ml/set_upgrade_mode?enabled=true - .mockResolvedValueOnce({ acknowledged: true }); + clusterClient.asCurrentUser.nodes.info.mockResolvedValueOnce( + asApiResponse({ nodes: { nodeX: { version: '6.7.0-alpha' } } }) + ); + clusterClient.asCurrentUser.ml.setUpgradeMode.mockResolvedValueOnce( + asApiResponse({ acknowledged: true }) + ); const mlReindexedOp = { id: '2', @@ -550,9 +575,8 @@ describe('reindexService', () => { ); expect(actions.incrementIndexGroupReindexes).toHaveBeenCalled(); expect(actions.runWhileIndexGroupLocked).toHaveBeenCalled(); - expect(callCluster).toHaveBeenCalledWith('transport.request', { - path: '/_ml/set_upgrade_mode?enabled=true', - method: 'POST', + expect(clusterClient.asCurrentUser.ml.setUpgradeMode).toHaveBeenCalledWith({ + enabled: true, }); }); @@ -561,11 +585,13 @@ describe('reindexService', () => { actions.runWhileIndexGroupLocked.mockImplementationOnce(async (group: string, f: any) => f() ); - callCluster - // Mock call to /_nodes for version check - .mockResolvedValueOnce({ nodes: { nodeX: { version: '6.7.0-alpha' } } }) - // Mock call to /_ml/set_upgrade_mode?enabled=true - .mockResolvedValueOnce({ acknowledged: true }); + + clusterClient.asCurrentUser.nodes.info.mockResolvedValueOnce( + asApiResponse({ nodes: { nodeX: { version: '6.7.0-alpha' } } }) + ); + clusterClient.asCurrentUser.ml.setUpgradeMode.mockResolvedValueOnce( + asApiResponse({ acknowledged: true }) + ); const updatedOp = await service.processNextStep(mlReindexOp); expect(updatedOp.attributes.lastCompletedStep).toEqual( @@ -573,9 +599,8 @@ describe('reindexService', () => { ); expect(actions.incrementIndexGroupReindexes).toHaveBeenCalled(); expect(actions.runWhileIndexGroupLocked).toHaveBeenCalled(); - expect(callCluster).toHaveBeenCalledWith('transport.request', { - path: '/_ml/set_upgrade_mode?enabled=true', - method: 'POST', + expect(clusterClient.asCurrentUser.ml.setUpgradeMode).toHaveBeenCalledWith({ + enabled: true, }); }); @@ -587,9 +612,8 @@ describe('reindexService', () => { expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); expect(updatedOp.attributes.errorMessage!.includes(`Can't lock!`)).toBeTruthy(); expect(log.error).toHaveBeenCalledWith(expect.any(String)); - expect(callCluster).not.toHaveBeenCalledWith('transport.request', { - path: '/_ml/set_upgrade_mode?enabled=true', - method: 'POST', + expect(clusterClient.asCurrentUser.ml.setUpgradeMode).not.toHaveBeenCalledWith({ + enabled: true, }); }); @@ -602,9 +626,8 @@ describe('reindexService', () => { expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); expect(updatedOp.attributes.errorMessage!.includes(`Can't lock!`)).toBeTruthy(); expect(log.error).toHaveBeenCalledWith(expect.any(String)); - expect(callCluster).not.toHaveBeenCalledWith('transport.request', { - path: '/_ml/set_upgrade_mode?enabled=true', - method: 'POST', + expect(clusterClient.asCurrentUser.ml.setUpgradeMode).not.toHaveBeenCalledWith({ + enabled: true, }); }); @@ -613,11 +636,12 @@ describe('reindexService', () => { actions.runWhileIndexGroupLocked.mockImplementationOnce(async (group: string, f: any) => f() ); - callCluster - // Mock call to /_nodes for version check - .mockResolvedValueOnce({ nodes: { nodeX: { version: '6.7.0' } } }) - // Mock call to /_ml/set_upgrade_mode?enabled=true - .mockResolvedValueOnce({ acknowledged: false }); + clusterClient.asCurrentUser.nodes.info.mockResolvedValueOnce( + asApiResponse({ nodes: { nodeX: { version: '6.7.0' } } }) + ); + clusterClient.asCurrentUser.ml.setUpgradeMode.mockResolvedValueOnce( + asApiResponse({ acknowledged: false }) + ); const updatedOp = await service.processNextStep(mlReindexOp); expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.created); @@ -626,9 +650,8 @@ describe('reindexService', () => { updatedOp.attributes.errorMessage!.includes('Could not stop ML jobs') ).toBeTruthy(); expect(log.error).toHaveBeenCalledWith(expect.any(String)); - expect(callCluster).toHaveBeenCalledWith('transport.request', { - path: '/_ml/set_upgrade_mode?enabled=true', - method: 'POST', + expect(clusterClient.asCurrentUser.ml.setUpgradeMode).toHaveBeenCalledWith({ + enabled: true, }); }); @@ -637,9 +660,9 @@ describe('reindexService', () => { actions.runWhileIndexGroupLocked.mockImplementationOnce(async (group: string, f: any) => f() ); - callCluster - // Mock call to /_nodes for version check - .mockResolvedValueOnce({ nodes: { nodeX: { version: '6.6.0' } } }); + clusterClient.asCurrentUser.nodes.info.mockResolvedValueOnce( + asApiResponse({ nodes: { nodeX: { version: '6.6.0' } } }) + ); const updatedOp = await service.processNextStep(mlReindexOp); expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.created); @@ -649,9 +672,8 @@ describe('reindexService', () => { ).toBeTruthy(); expect(log.error).toHaveBeenCalledWith(expect.any(String)); // Should not have called ML endpoint at all - expect(callCluster).not.toHaveBeenCalledWith('transport.request', { - path: '/_ml/set_upgrade_mode?enabled=true', - method: 'POST', + expect(clusterClient.asCurrentUser.ml.setUpgradeMode).not.toHaveBeenCalledWith({ + enabled: true, }); }); }); @@ -669,7 +691,7 @@ describe('reindexService', () => { ); expect(actions.incrementIndexGroupReindexes).not.toHaveBeenCalled(); expect(actions.runWhileIndexGroupLocked).not.toHaveBeenCalled(); - expect(callCluster).not.toHaveBeenCalled(); + expect(clusterClient.asCurrentUser.watcher.stop).not.toHaveBeenCalled(); }); it('increments ML reindexes and calls watcher stop endpoint', async () => { @@ -677,9 +699,9 @@ describe('reindexService', () => { actions.runWhileIndexGroupLocked.mockImplementationOnce(async (type: string, f: any) => f() ); - callCluster - // Mock call to /_watcher/_stop - .mockResolvedValueOnce({ acknowledged: true }); + clusterClient.asCurrentUser.watcher.stop.mockResolvedValueOnce( + asApiResponse({ acknowledged: true }) + ); const updatedOp = await service.processNextStep(watcherReindexOp); expect(updatedOp.attributes.lastCompletedStep).toEqual( @@ -687,10 +709,7 @@ describe('reindexService', () => { ); expect(actions.incrementIndexGroupReindexes).toHaveBeenCalledWith(IndexGroup.watcher); expect(actions.runWhileIndexGroupLocked).toHaveBeenCalled(); - expect(callCluster).toHaveBeenCalledWith('transport.request', { - path: '/_watcher/_stop', - method: 'POST', - }); + expect(clusterClient.asCurrentUser.watcher.stop).toHaveBeenCalled(); }); it('fails if watcher reindexes cannot be incremented', async () => { @@ -701,9 +720,8 @@ describe('reindexService', () => { expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); expect(updatedOp.attributes.errorMessage!.includes(`Can't lock!`)).toBeTruthy(); expect(log.error).toHaveBeenCalledWith(expect.any(String)); - expect(callCluster).not.toHaveBeenCalledWith('transport.request', { - path: '/_watcher/_stop', - method: 'POST', + expect(clusterClient.asCurrentUser.watcher.stop).not.toHaveBeenCalledWith({ + enabled: true, }); }); @@ -716,10 +734,7 @@ describe('reindexService', () => { expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); expect(updatedOp.attributes.errorMessage!.includes(`Can't lock!`)).toBeTruthy(); expect(log.error).toHaveBeenCalledWith(expect.any(String)); - expect(callCluster).not.toHaveBeenCalledWith('transport.request', { - path: '/_watcher/_stop', - method: 'POST', - }); + expect(clusterClient.asCurrentUser.watcher.stop).not.toHaveBeenCalled(); }); it('fails if watcher endpoint fails', async () => { @@ -727,9 +742,9 @@ describe('reindexService', () => { actions.runWhileIndexGroupLocked.mockImplementationOnce(async (type: string, f: any) => f() ); - callCluster - // Mock call to /_watcher/_stop - .mockResolvedValueOnce({ acknowledged: false }); + clusterClient.asCurrentUser.watcher.stop.mockResolvedValueOnce( + asApiResponse({ acknowledged: false }) + ); const updatedOp = await service.processNextStep(watcherReindexOp); expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.created); @@ -738,10 +753,7 @@ describe('reindexService', () => { updatedOp.attributes.errorMessage!.includes('Could not stop Watcher') ).toBeTruthy(); expect(log.error).toHaveBeenCalledWith(expect.any(String)); - expect(callCluster).toHaveBeenCalledWith('transport.request', { - path: '/_watcher/_stop', - method: 'POST', - }); + expect(clusterClient.asCurrentUser.watcher.stop).toHaveBeenCalled(); }); }); }); @@ -756,17 +768,21 @@ describe('reindexService', () => { } as ReindexSavedObject; it('blocks writes and updates lastCompletedStep', async () => { - callCluster.mockResolvedValueOnce({ acknowledged: true }); + clusterClient.asCurrentUser.indices.putSettings.mockResolvedValueOnce( + asApiResponse({ acknowledged: true }) + ); const updatedOp = await service.processNextStep(reindexOp); expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.readonly); - expect(callCluster).toHaveBeenCalledWith('indices.putSettings', { + expect(clusterClient.asCurrentUser.indices.putSettings).toHaveBeenCalledWith({ index: 'myIndex', body: { 'index.blocks.write': true }, }); }); it('fails if setting updates are not acknowledged', async () => { - callCluster.mockResolvedValueOnce({ acknowledged: false }); + clusterClient.asCurrentUser.indices.putSettings.mockResolvedValueOnce( + asApiResponse({ acknowledged: false }) + ); const updatedOp = await service.processNextStep(reindexOp); expect(updatedOp.attributes.lastCompletedStep).toEqual( ReindexStep.indexGroupServicesStopped @@ -777,7 +793,7 @@ describe('reindexService', () => { }); it('fails if setting updates fail', async () => { - callCluster.mockRejectedValueOnce(new Error('blah!')); + clusterClient.asCurrentUser.indices.putSettings.mockRejectedValueOnce(new Error('blah!')); const updatedOp = await service.processNextStep(reindexOp); expect(updatedOp.attributes.lastCompletedStep).toEqual( ReindexStep.indexGroupServicesStopped @@ -797,11 +813,12 @@ describe('reindexService', () => { // The more intricate details of how the settings are chosen are test separately. it('creates new index with settings and mappings and updates lastCompletedStep', async () => { actions.getFlatSettings.mockResolvedValueOnce(settingsMappings); - callCluster.mockResolvedValueOnce({ acknowledged: true }); // indices.create - + clusterClient.asCurrentUser.indices.create.mockResolvedValueOnce( + asApiResponse({ acknowledged: true }) + ); const updatedOp = await service.processNextStep(reindexOp); expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.newIndexCreated); - expect(callCluster).toHaveBeenCalledWith('indices.create', { + expect(clusterClient.asCurrentUser.indices.create).toHaveBeenCalledWith({ index: 'myIndex-reindex-0', body: { // index.blocks.write should be removed from the settings for the new index. @@ -812,9 +829,13 @@ describe('reindexService', () => { }); it('fails if create index is not acknowledged', async () => { - callCluster - .mockResolvedValueOnce({ myIndex: settingsMappings }) - .mockResolvedValueOnce({ acknowledged: false }); + clusterClient.asCurrentUser.indices.getSettings.mockResolvedValueOnce( + asApiResponse({ myIndex: settingsMappings }) + ); + + clusterClient.asCurrentUser.indices.create.mockResolvedValueOnce( + asApiResponse({ acknowledged: false }) + ); const updatedOp = await service.processNextStep(reindexOp); expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.readonly); expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); @@ -823,10 +844,16 @@ describe('reindexService', () => { }); it('fails if create index fails', async () => { - callCluster - .mockResolvedValueOnce({ myIndex: settingsMappings }) - .mockRejectedValueOnce(new Error(`blah!`)) - .mockResolvedValueOnce({ acknowledged: true }); + clusterClient.asCurrentUser.indices.getSettings.mockResolvedValueOnce( + asApiResponse({ myIndex: settingsMappings }) + ); + + clusterClient.asCurrentUser.indices.create.mockRejectedValueOnce(new Error(`blah!`)); + + clusterClient.asCurrentUser.indices.putSettings.mockResolvedValueOnce( + asApiResponse({ acknowledged: true }) + ); + const updatedOp = await service.processNextStep(reindexOp); expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.readonly); expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); @@ -834,7 +861,7 @@ describe('reindexService', () => { expect(log.error).toHaveBeenCalledWith(expect.any(String)); // Original index should have been set back to allow reads. - expect(callCluster).toHaveBeenCalledWith('indices.putSettings', { + expect(clusterClient.asCurrentUser.indices.putSettings).toHaveBeenCalledWith({ index: 'myIndex', body: { 'index.blocks.write': false }, }); @@ -858,14 +885,14 @@ describe('reindexService', () => { }); it('starts reindex, saves taskId, and updates lastCompletedStep', async () => { - callCluster.mockResolvedValueOnce({ task: 'xyz' }); // reindex + clusterClient.asCurrentUser.reindex.mockResolvedValueOnce(asApiResponse({ task: 'xyz' })); const updatedOp = await service.processNextStep(reindexOp); expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.reindexStarted); expect(updatedOp.attributes.reindexTaskId).toEqual('xyz'); expect(updatedOp.attributes.reindexTaskPercComplete).toEqual(0); - expect(callCluster).toHaveBeenLastCalledWith('reindex', { + expect(clusterClient.asCurrentUser.reindex).toHaveBeenLastCalledWith({ refresh: true, - waitForCompletion: false, + wait_for_completion: false, body: { source: { index: 'myIndex' }, dest: { index: 'myIndex-reindex-0' }, @@ -874,7 +901,7 @@ describe('reindexService', () => { }); it('fails if starting reindex fails', async () => { - callCluster.mockRejectedValueOnce(new Error('blah!')).mockResolvedValueOnce({}); + clusterClient.asCurrentUser.reindex.mockRejectedValueOnce(new Error('blah!')); const updatedOp = await service.processNextStep(reindexOp); expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.newIndexCreated); expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); @@ -895,10 +922,13 @@ describe('reindexService', () => { describe('reindex task is not complete', () => { it('updates reindexTaskPercComplete', async () => { - callCluster.mockResolvedValueOnce({ - completed: false, - task: { status: { created: 10, total: 100 } }, - }); + clusterClient.asCurrentUser.tasks.get.mockResolvedValueOnce( + asApiResponse({ + completed: false, + task: { status: { created: 10, total: 100 } }, + }) + ); + const updatedOp = await service.processNextStep(reindexOp); expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.reindexStarted); expect(updatedOp.attributes.reindexTaskPercComplete).toEqual(0.1); // 10 / 100 = 0.1 @@ -907,30 +937,47 @@ describe('reindexService', () => { describe('reindex task is complete', () => { it('deletes task, updates reindexTaskPercComplete, updates lastCompletedStep', async () => { - callCluster - .mockResolvedValueOnce({ + clusterClient.asCurrentUser.tasks.get.mockResolvedValueOnce( + asApiResponse({ completed: true, task: { status: { created: 100, total: 100 } }, }) - .mockResolvedValueOnce({ count: 100 }) - .mockResolvedValueOnce({ result: 'deleted' }); + ); + + clusterClient.asCurrentUser.count.mockResolvedValueOnce( + asApiResponse({ + count: 100, + }) + ); + + clusterClient.asCurrentUser.delete.mockResolvedValueOnce( + asApiResponse({ + result: 'deleted', + }) + ); const updatedOp = await service.processNextStep(reindexOp); expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.reindexCompleted); expect(updatedOp.attributes.reindexTaskPercComplete).toEqual(1); - expect(callCluster).toHaveBeenCalledWith('delete', { + expect(clusterClient.asCurrentUser.delete).toHaveBeenCalledWith({ index: '.tasks', id: 'xyz', }); }); it('fails if docs created is less than count in source index', async () => { - callCluster - .mockResolvedValueOnce({ + clusterClient.asCurrentUser.tasks.get.mockResolvedValueOnce( + asApiResponse({ completed: true, task: { status: { created: 95, total: 95 } }, }) - .mockReturnValueOnce({ count: 100 }); + ); + + clusterClient.asCurrentUser.count.mockResolvedValueOnce( + asApiResponse({ + count: 100, + }) + ); const updatedOp = await service.processNextStep(reindexOp); expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.reindexStarted); @@ -941,18 +988,22 @@ describe('reindexService', () => { }); describe('reindex task is cancelled', () => { - it('deletes tsk, updates status to cancelled', async () => { - callCluster - .mockResolvedValueOnce({ + it('deletes task, updates status to cancelled', async () => { + clusterClient.asCurrentUser.tasks.get.mockResolvedValueOnce( + asApiResponse({ completed: true, task: { status: { created: 100, total: 100, canceled: 'by user request' } }, }) - .mockResolvedValue({ result: 'deleted' }); + ); + + clusterClient.asCurrentUser.delete.mockResolvedValue( + asApiResponse({ result: 'deleted' }) + ); const updatedOp = await service.processNextStep(reindexOp); expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.reindexStarted); expect(updatedOp.attributes.status).toEqual(ReindexStatus.cancelled); - expect(callCluster).toHaveBeenCalledWith('delete', { + expect(clusterClient.asCurrentUser.delete).toHaveBeenLastCalledWith({ index: '.tasks', id: 'xyz', }); @@ -971,12 +1022,16 @@ describe('reindexService', () => { } as ReindexSavedObject; it('switches aliases, sets as complete, and updates lastCompletedStep', async () => { - callCluster - .mockResolvedValueOnce({ myIndex: { aliases: {} } }) - .mockResolvedValueOnce({ acknowledged: true }); + clusterClient.asCurrentUser.indices.getAlias.mockResolvedValue( + asApiResponse({ myIndex: { aliases: {} } }) + ); + + clusterClient.asCurrentUser.indices.updateAliases.mockResolvedValue( + asApiResponse({ acknowledged: true }) + ); const updatedOp = await service.processNextStep(reindexOp); expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.aliasCreated); - expect(callCluster).toHaveBeenCalledWith('indices.updateAliases', { + expect(clusterClient.asCurrentUser.indices.updateAliases).toHaveBeenCalledWith({ body: { actions: [ { add: { index: 'myIndex-reindex-0', alias: 'myIndex' } }, @@ -987,8 +1042,8 @@ describe('reindexService', () => { }); it('moves existing aliases over to new index', async () => { - callCluster - .mockResolvedValueOnce({ + clusterClient.asCurrentUser.indices.getAlias.mockResolvedValue( + asApiResponse({ myIndex: { aliases: { myAlias: {}, @@ -996,10 +1051,15 @@ describe('reindexService', () => { }, }, }) - .mockResolvedValueOnce({ acknowledged: true }); + ); + + clusterClient.asCurrentUser.indices.updateAliases.mockResolvedValue( + asApiResponse({ acknowledged: true }) + ); + const updatedOp = await service.processNextStep(reindexOp); expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.aliasCreated); - expect(callCluster).toHaveBeenCalledWith('indices.updateAliases', { + expect(clusterClient.asCurrentUser.indices.updateAliases).toHaveBeenCalledWith({ body: { actions: [ { add: { index: 'myIndex-reindex-0', alias: 'myIndex' } }, @@ -1018,7 +1078,9 @@ describe('reindexService', () => { }); it('fails if switching aliases is not acknowledged', async () => { - callCluster.mockResolvedValueOnce({ acknowledged: false }); + clusterClient.asCurrentUser.indices.updateAliases.mockResolvedValue( + asApiResponse({ acknowledged: false }) + ); const updatedOp = await service.processNextStep(reindexOp); expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.reindexCompleted); expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); @@ -1027,7 +1089,7 @@ describe('reindexService', () => { }); it('fails if switching aliases fails', async () => { - callCluster.mockRejectedValueOnce(new Error('blah!')); + clusterClient.asCurrentUser.indices.updateAliases.mockRejectedValueOnce(new Error('blah!')); const updatedOp = await service.processNextStep(reindexOp); expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.reindexCompleted); expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); @@ -1053,7 +1115,7 @@ describe('reindexService', () => { expect(updatedOp.attributes.lastCompletedStep).toEqual( ReindexStep.indexGroupServicesStarted ); - expect(callCluster).not.toHaveBeenCalled(); + expect(clusterClient.asCurrentUser.ml.setUpgradeMode).not.toHaveBeenCalled(); }); it('decrements ML reindexes and calls ML start endpoint if no remaining ML jobs', async () => { @@ -1061,17 +1123,17 @@ describe('reindexService', () => { actions.runWhileIndexGroupLocked.mockImplementationOnce(async (group: string, f: any) => f({ attributes: { runningReindexCount: 0 } }) ); - // Mock call to /_ml/set_upgrade_mode?enabled=false - callCluster.mockResolvedValueOnce({ acknowledged: true }); + clusterClient.asCurrentUser.ml.setUpgradeMode.mockResolvedValueOnce( + asApiResponse({ acknowledged: true }) + ); const updatedOp = await service.processNextStep(mlReindexOp); expect(actions.decrementIndexGroupReindexes).toHaveBeenCalledWith(IndexGroup.ml); expect(updatedOp.attributes.lastCompletedStep).toEqual( ReindexStep.indexGroupServicesStarted ); - expect(callCluster).toHaveBeenCalledWith('transport.request', { - path: '/_ml/set_upgrade_mode?enabled=false', - method: 'POST', + expect(clusterClient.asCurrentUser.ml.setUpgradeMode).toHaveBeenCalledWith({ + enabled: false, }); }); @@ -1080,16 +1142,16 @@ describe('reindexService', () => { actions.runWhileIndexGroupLocked.mockImplementationOnce(async (group: string, f: any) => f({ attributes: { runningReindexCount: 2 } }) ); - // Mock call to /_ml/set_upgrade_mode?enabled=false - callCluster.mockResolvedValueOnce({ acknowledged: true }); + clusterClient.asCurrentUser.ml.setUpgradeMode.mockResolvedValueOnce( + asApiResponse({ acknowledged: true }) + ); const updatedOp = await service.processNextStep(mlReindexOp); expect(updatedOp.attributes.lastCompletedStep).toEqual( ReindexStep.indexGroupServicesStarted ); - expect(callCluster).not.toHaveBeenCalledWith('transport.request', { - path: '/_ml/set_upgrade_mode?enabled=false', - method: 'POST', + expect(clusterClient.asCurrentUser.ml.setUpgradeMode).not.toHaveBeenCalledWith({ + enabled: false, }); }); @@ -1102,9 +1164,8 @@ describe('reindexService', () => { expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); expect(updatedOp.attributes.errorMessage!.includes(`Can't lock!`)).toBeTruthy(); expect(log.error).toHaveBeenCalledWith(expect.any(String)); - expect(callCluster).not.toHaveBeenCalledWith('transport.request', { - path: '/_ml/set_upgrade_mode?enabled=false', - method: 'POST', + expect(clusterClient.asCurrentUser.ml.setUpgradeMode).not.toHaveBeenCalledWith({ + enabled: false, }); }); @@ -1118,9 +1179,8 @@ describe('reindexService', () => { expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); expect(updatedOp.attributes.errorMessage!.includes(`Can't lock!`)).toBeTruthy(); expect(log.error).toHaveBeenCalledWith(expect.any(String)); - expect(callCluster).not.toHaveBeenCalledWith('transport.request', { - path: '/_ml/set_upgrade_mode?enabled=false', - method: 'POST', + expect(clusterClient.asCurrentUser.ml.setUpgradeMode).not.toHaveBeenCalledWith({ + enabled: false, }); }); @@ -1129,9 +1189,9 @@ describe('reindexService', () => { actions.runWhileIndexGroupLocked.mockImplementationOnce(async (group: string, f: any) => f({ attributes: { runningReindexCount: 0 } }) ); - // Mock call to /_ml/set_upgrade_mode?enabled=true - callCluster.mockResolvedValueOnce({ acknowledged: false }); - + clusterClient.asCurrentUser.ml.setUpgradeMode.mockResolvedValueOnce( + asApiResponse({ acknowledged: false }) + ); const updatedOp = await service.processNextStep(mlReindexOp); expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.aliasCreated); expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); @@ -1139,9 +1199,8 @@ describe('reindexService', () => { updatedOp.attributes.errorMessage!.includes('Could not resume ML jobs') ).toBeTruthy(); expect(log.error).toHaveBeenCalledWith(expect.any(String)); - expect(callCluster).toHaveBeenCalledWith('transport.request', { - path: '/_ml/set_upgrade_mode?enabled=false', - method: 'POST', + expect(clusterClient.asCurrentUser.ml.setUpgradeMode).toHaveBeenCalledWith({ + enabled: false, }); }); }); @@ -1157,7 +1216,7 @@ describe('reindexService', () => { expect(updatedOp.attributes.lastCompletedStep).toEqual( ReindexStep.indexGroupServicesStarted ); - expect(callCluster).not.toHaveBeenCalled(); + expect(clusterClient.asCurrentUser.watcher.start).not.toHaveBeenCalled(); }); it('decrements watcher reindexes and calls wathcer start endpoint if no remaining watcher reindexes', async () => { @@ -1165,36 +1224,31 @@ describe('reindexService', () => { actions.runWhileIndexGroupLocked.mockImplementationOnce(async (group: string, f: any) => f({ attributes: { runningReindexCount: 0 } }) ); - // Mock call to /_watcher/_start - callCluster.mockResolvedValueOnce({ acknowledged: true }); + clusterClient.asCurrentUser.watcher.start.mockResolvedValueOnce( + asApiResponse({ acknowledged: true }) + ); const updatedOp = await service.processNextStep(watcherReindexOp); expect(actions.decrementIndexGroupReindexes).toHaveBeenCalledWith(IndexGroup.watcher); expect(updatedOp.attributes.lastCompletedStep).toEqual( ReindexStep.indexGroupServicesStarted ); - expect(callCluster).toHaveBeenCalledWith('transport.request', { - path: '/_watcher/_start', - method: 'POST', - }); + expect(clusterClient.asCurrentUser.watcher.start).toHaveBeenCalled(); }); - it('does not call wathcer start endpoint if there are remaining wathcer reindexes', async () => { + it('does not call watcher start endpoint if there are remaining watcher reindexes', async () => { actions.decrementIndexGroupReindexes.mockResolvedValue(); actions.runWhileIndexGroupLocked.mockImplementationOnce(async (group: string, f: any) => f({ attributes: { runningReindexCount: 2 } }) ); - // Mock call to /_watcher/_start - callCluster.mockResolvedValueOnce({ acknowledged: true }); - + clusterClient.asCurrentUser.watcher.start.mockResolvedValueOnce( + asApiResponse({ acknowledged: true }) + ); const updatedOp = await service.processNextStep(watcherReindexOp); expect(updatedOp.attributes.lastCompletedStep).toEqual( ReindexStep.indexGroupServicesStarted ); - expect(callCluster).not.toHaveBeenCalledWith('transport.request', { - path: '/_watcher/_start', - method: 'POST', - }); + expect(clusterClient.asCurrentUser.watcher.start).not.toHaveBeenCalledWith(); }); it('fails if watcher reindexes cannot be decremented', async () => { @@ -1206,10 +1260,7 @@ describe('reindexService', () => { expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); expect(updatedOp.attributes.errorMessage!.includes(`Can't lock!`)).toBeTruthy(); expect(log.error).toHaveBeenCalledWith(expect.any(String)); - expect(callCluster).not.toHaveBeenCalledWith('transport.request', { - path: '/_watcher/_start', - method: 'POST', - }); + expect(clusterClient.asCurrentUser.watcher.start).not.toHaveBeenCalledWith(); }); it('fails if watcher doc cannot be locked', async () => { @@ -1222,10 +1273,7 @@ describe('reindexService', () => { expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); expect(updatedOp.attributes.errorMessage!.includes(`Can't lock!`)).toBeTruthy(); expect(log.error).toHaveBeenCalledWith(expect.any(String)); - expect(callCluster).not.toHaveBeenCalledWith('transport.request', { - path: '/_watcher/_start', - method: 'POST', - }); + expect(clusterClient.asCurrentUser.watcher.start).not.toHaveBeenCalledWith(); }); it('fails if watcher endpoint fails', async () => { @@ -1233,9 +1281,10 @@ describe('reindexService', () => { actions.runWhileIndexGroupLocked.mockImplementationOnce(async (group: string, f: any) => f({ attributes: { runningReindexCount: 0 } }) ); - // Mock call to /_watcher/_start - callCluster.mockResolvedValueOnce({ acknowledged: false }); + clusterClient.asCurrentUser.watcher.start.mockResolvedValueOnce( + asApiResponse({ acknowledged: false }) + ); const updatedOp = await service.processNextStep(watcherReindexOp); expect(updatedOp.attributes.lastCompletedStep).toEqual(ReindexStep.aliasCreated); expect(updatedOp.attributes.status).toEqual(ReindexStatus.failed); @@ -1243,10 +1292,7 @@ describe('reindexService', () => { updatedOp.attributes.errorMessage!.includes('Could not start Watcher') ).toBeTruthy(); expect(log.error).toHaveBeenCalledWith(expect.any(String)); - expect(callCluster).toHaveBeenCalledWith('transport.request', { - path: '/_watcher/_start', - method: 'POST', - }); + expect(clusterClient.asCurrentUser.watcher.start).toHaveBeenCalled(); }); }); }); diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts index e784f42867d57..f59dc66f40612 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/reindex_service.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { LegacyAPICaller, Logger } from 'src/core/server'; +import { ElasticsearchClient, Logger } from 'src/core/server'; import { first } from 'rxjs/operators'; import { LicensingPluginSetup } from '../../../../licensing/server'; @@ -127,7 +127,7 @@ export interface ReindexService { } export const reindexServiceFactory = ( - callAsUser: LegacyAPICaller, + esClient: ElasticsearchClient, actions: ReindexActions, log: Logger, licensing: LicensingPluginSetup @@ -144,12 +144,11 @@ export const reindexServiceFactory = ( await actions.runWhileIndexGroupLocked(IndexGroup.ml, async (mlDoc) => { await validateNodesMinimumVersion(6, 7); - const res = await callAsUser('transport.request', { - path: '/_ml/set_upgrade_mode?enabled=true', - method: 'POST', + const { body } = await esClient.ml.setUpgradeMode({ + enabled: true, }); - if (!res.acknowledged) { + if (!body.acknowledged) { throw new Error(`Could not stop ML jobs`); } @@ -164,12 +163,11 @@ export const reindexServiceFactory = ( await actions.decrementIndexGroupReindexes(IndexGroup.ml); await actions.runWhileIndexGroupLocked(IndexGroup.ml, async (mlDoc) => { if (mlDoc.attributes.runningReindexCount === 0) { - const res = await callAsUser('transport.request', { - path: '/_ml/set_upgrade_mode?enabled=false', - method: 'POST', + const { body } = await esClient.ml.setUpgradeMode({ + enabled: false, }); - if (!res.acknowledged) { + if (!body.acknowledged) { throw new Error(`Could not resume ML jobs`); } } @@ -184,12 +182,9 @@ export const reindexServiceFactory = ( const stopWatcher = async () => { await actions.incrementIndexGroupReindexes(IndexGroup.watcher); await actions.runWhileIndexGroupLocked(IndexGroup.watcher, async (watcherDoc) => { - const { acknowledged } = await callAsUser('transport.request', { - path: '/_watcher/_stop', - method: 'POST', - }); + const { body } = await esClient.watcher.stop(); - if (!acknowledged) { + if (!body.acknowledged) { throw new Error('Could not stop Watcher'); } @@ -204,12 +199,9 @@ export const reindexServiceFactory = ( await actions.decrementIndexGroupReindexes(IndexGroup.watcher); await actions.runWhileIndexGroupLocked(IndexGroup.watcher, async (watcherDoc) => { if (watcherDoc.attributes.runningReindexCount === 0) { - const { acknowledged } = await callAsUser('transport.request', { - path: '/_watcher/_start', - method: 'POST', - }); + const { body } = await esClient.watcher.start(); - if (!acknowledged) { + if (!body.acknowledged) { throw new Error('Could not start Watcher'); } } @@ -221,14 +213,16 @@ export const reindexServiceFactory = ( const cleanupChanges = async (reindexOp: ReindexSavedObject) => { // Cancel reindex task if it was started but not completed if (reindexOp.attributes.lastCompletedStep === ReindexStep.reindexStarted) { - await callAsUser('tasks.cancel', { - taskId: reindexOp.attributes.reindexTaskId, - }).catch((e) => undefined); // Ignore any exceptions trying to cancel (it may have already completed). + await esClient.tasks + .cancel({ + task_id: reindexOp.attributes.reindexTaskId ?? undefined, + }) + .catch((e) => undefined); // Ignore any exceptions trying to cancel (it may have already completed). } // Set index back to writable if we ever got past this point. if (reindexOp.attributes.lastCompletedStep >= ReindexStep.readonly) { - await callAsUser('indices.putSettings', { + await esClient.indices.putSettings({ index: reindexOp.attributes.indexName, body: { 'index.blocks.write': false }, }); @@ -238,7 +232,9 @@ export const reindexServiceFactory = ( reindexOp.attributes.lastCompletedStep >= ReindexStep.newIndexCreated && reindexOp.attributes.lastCompletedStep < ReindexStep.aliasCreated ) { - await callAsUser('indices.delete', { index: reindexOp.attributes.newIndexName }); + await esClient.indices.delete({ + index: reindexOp.attributes.newIndexName, + }); } // Resume consumers if we ever got past this point. @@ -252,10 +248,7 @@ export const reindexServiceFactory = ( // ------ Functions used to process the state machine const validateNodesMinimumVersion = async (minMajor: number, minMinor: number) => { - const nodesResponse = await callAsUser('transport.request', { - path: '/_nodes', - method: 'GET', - }); + const { body: nodesResponse } = await esClient.nodes.info(); const outDatedNodes = Object.values(nodesResponse.nodes).filter((node: any) => { const matches = node.version.match(VERSION_REGEX); @@ -293,7 +286,7 @@ export const reindexServiceFactory = ( */ const setReadonly = async (reindexOp: ReindexSavedObject) => { const { indexName } = reindexOp.attributes; - const putReadonly = await callAsUser('indices.putSettings', { + const { body: putReadonly } = await esClient.indices.putSettings({ index: indexName, body: { 'index.blocks.write': true }, }); @@ -319,7 +312,7 @@ export const reindexServiceFactory = ( const { settings, mappings } = transformFlatSettings(flatSettings); - const createIndex = await callAsUser('indices.create', { + const { body: createIndex } = await esClient.indices.create({ index: newIndexName, body: { settings, @@ -345,25 +338,25 @@ export const reindexServiceFactory = ( // Where possible, derive reindex options at the last moment before reindexing // to prevent them from becoming stale as they wait in the queue. - const indicesState = await esIndicesStateCheck(callAsUser, [indexName]); + const indicesState = await esIndicesStateCheck(esClient, [indexName]); const shouldOpenAndClose = indicesState[indexName] === 'closed'; if (shouldOpenAndClose) { log.debug(`Detected closed index ${indexName}, opening...`); - await callAsUser('indices.open', { index: indexName }); + await esClient.indices.open({ index: indexName }); } - const startReindex = (await callAsUser('reindex', { + const { body: startReindexResponse } = await esClient.reindex({ refresh: true, - waitForCompletion: false, + wait_for_completion: false, body: { source: { index: indexName }, dest: { index: reindexOp.attributes.newIndexName }, }, - })) as any; + }); return actions.updateReindexOp(reindexOp, { lastCompletedStep: ReindexStep.reindexStarted, - reindexTaskId: startReindex.task, + reindexTaskId: startReindexResponse.task, reindexTaskPercComplete: 0, reindexOptions: { ...(reindexOptions ?? {}), @@ -379,12 +372,12 @@ export const reindexServiceFactory = ( * @param reindexOp */ const updateReindexStatus = async (reindexOp: ReindexSavedObject) => { - const taskId = reindexOp.attributes.reindexTaskId; + const taskId = reindexOp.attributes.reindexTaskId!; // Check reindexing task progress - const taskResponse = await callAsUser('tasks.get', { - taskId, - waitForCompletion: false, + const { body: taskResponse } = await esClient.tasks.get({ + task_id: taskId, + wait_for_completion: false, }); if (!taskResponse.completed) { @@ -403,7 +396,7 @@ export const reindexServiceFactory = ( reindexOp = await cleanupChanges(reindexOp); } else { // Check that it reindexed all documents - const { count } = await callAsUser('count', { index: reindexOp.attributes.indexName }); + const { body: count } = await esClient.count({ index: reindexOp.attributes.indexName }); if (taskResponse.task.status.created < count) { // Include the entire task result in the error message. This should be guaranteed @@ -419,7 +412,7 @@ export const reindexServiceFactory = ( } // Delete the task from ES .tasks index - const deleteTaskResp = await callAsUser('delete', { + const { body: deleteTaskResp } = await esClient.delete({ index: '.tasks', id: taskId, }); @@ -438,22 +431,22 @@ export const reindexServiceFactory = ( const switchAlias = async (reindexOp: ReindexSavedObject) => { const { indexName, newIndexName, reindexOptions } = reindexOp.attributes; - const existingAliases = ( - await callAsUser('indices.getAlias', { - index: indexName, - }) - )[indexName].aliases; + const { body: response } = await esClient.indices.getAlias({ + index: indexName, + }); + + const existingAliases = response[indexName].aliases; - const extraAlises = Object.keys(existingAliases).map((aliasName) => ({ + const extraAliases = Object.keys(existingAliases).map((aliasName) => ({ add: { index: newIndexName, alias: aliasName, ...existingAliases[aliasName] }, })); - const aliasResponse = await callAsUser('indices.updateAliases', { + const { body: aliasResponse } = await esClient.indices.updateAliases({ body: { actions: [ { add: { index: newIndexName, alias: indexName } }, { remove_index: { index: indexName } }, - ...extraAlises, + ...extraAliases, ], }, }); @@ -463,7 +456,7 @@ export const reindexServiceFactory = ( } if (reindexOptions?.openAndClose === true) { - await callAsUser('indices.close', { index: indexName }); + await esClient.indices.close({ index: indexName }); } return actions.updateReindexOp(reindexOp, { @@ -540,9 +533,7 @@ export const reindexServiceFactory = ( body.cluster = [...body.cluster, 'manage_watcher']; } - const resp = await callAsUser('transport.request', { - path: '/_security/user/_has_privileges', - method: 'POST', + const { body: resp } = await esClient.security.hasPrivileges({ body, }); @@ -567,7 +558,7 @@ export const reindexServiceFactory = ( }, async createReindexOperation(indexName: string, opts?: { enqueue: boolean }) { - const indexExists = await callAsUser('indices.exists', { index: indexName }); + const { body: indexExists } = await esClient.indices.exists({ index: indexName }); if (!indexExists) { throw error.indexNotFound(`Index ${indexName} does not exist in this cluster.`); } @@ -752,8 +743,8 @@ export const reindexServiceFactory = ( ); } - const resp = await callAsUser('tasks.cancel', { - taskId: reindexOp.attributes.reindexTaskId, + const { body: resp } = await esClient.tasks.cancel({ + task_id: reindexOp.attributes.reindexTaskId!, }); if (resp.node_failures && resp.node_failures.length > 0) { diff --git a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/worker.ts b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/worker.ts index 756ce17b60199..1a30f98296c3a 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/reindexing/worker.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/reindexing/worker.ts @@ -3,12 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { - ILegacyClusterClient, - Logger, - SavedObjectsClientContract, - FakeRequest, -} from 'src/core/server'; +import { IClusterClient, Logger, SavedObjectsClientContract, FakeRequest } from 'src/core/server'; import moment from 'moment'; import { ReindexSavedObject, ReindexStatus } from '../../../common/types'; import { Credential, CredentialStore } from './credential_store'; @@ -53,7 +48,7 @@ export class ReindexWorker { constructor( private client: SavedObjectsClientContract, private credentialStore: CredentialStore, - private clusterClient: ILegacyClusterClient, + private clusterClient: IClusterClient, log: Logger, private licensing: LicensingPluginSetup ) { @@ -62,7 +57,7 @@ export class ReindexWorker { throw new Error(`More than one ReindexWorker cannot be created.`); } - const callAsInternalUser = this.clusterClient.callAsInternalUser.bind(this.clusterClient); + const callAsInternalUser = this.clusterClient.asInternalUser; this.reindexService = reindexServiceFactory( callAsInternalUser, @@ -151,7 +146,7 @@ export class ReindexWorker { private getCredentialScopedReindexService = (credential: Credential) => { const fakeRequest: FakeRequest = { headers: credential }; const scopedClusterClient = this.clusterClient.asScoped(fakeRequest); - const callAsCurrentUser = scopedClusterClient.callAsCurrentUser.bind(scopedClusterClient); + const callAsCurrentUser = scopedClusterClient.asCurrentUser; const actions = reindexActionsFactory(this.client, callAsCurrentUser); return reindexServiceFactory(callAsCurrentUser, actions, this.log, this.licensing); }; diff --git a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts index e14056439ca6b..9b5223d07bfbf 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.test.ts @@ -5,7 +5,7 @@ */ import { elasticsearchServiceMock } from 'src/core/server/mocks'; import { registerUpgradeAssistantUsageCollector } from './usage_collector'; -import { ILegacyClusterClient } from 'src/core/server'; +import { IClusterClient } from 'src/core/server'; /** * Since these route callbacks are so thin, these serve simply as integration tests @@ -18,15 +18,17 @@ describe('Upgrade Assistant Usage Collector', () => { let dependencies: any; let callClusterStub: any; let usageCollection: any; - let clusterClient: ILegacyClusterClient; + let clusterClient: IClusterClient; beforeEach(() => { - clusterClient = elasticsearchServiceMock.createLegacyClusterClient(); - (clusterClient.callAsInternalUser as jest.Mock).mockResolvedValue({ - persistent: {}, - transient: { - logger: { - deprecation: 'WARN', + clusterClient = elasticsearchServiceMock.createClusterClient(); + (clusterClient.asInternalUser.cluster.getSettings as jest.Mock).mockResolvedValue({ + body: { + persistent: {}, + transient: { + logger: { + deprecation: 'WARN', + }, }, }, }); @@ -59,7 +61,7 @@ describe('Upgrade Assistant Usage Collector', () => { }), }, elasticsearch: { - legacy: { client: clusterClient }, + client: clusterClient, }, }; }); diff --git a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts index 276e639678fd8..1eafefb4238c2 100644 --- a/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts +++ b/x-pack/plugins/upgrade_assistant/server/lib/telemetry/usage_collector.ts @@ -6,7 +6,7 @@ import { get } from 'lodash'; import { - LegacyAPICaller, + ElasticsearchClient, ElasticsearchServiceStart, ISavedObjectsRepository, SavedObjectsServiceStart, @@ -38,12 +38,10 @@ async function getSavedObjectAttributesFromRepo( } } -async function getDeprecationLoggingStatusValue( - callAsCurrentUser: LegacyAPICaller -): Promise { +async function getDeprecationLoggingStatusValue(esClient: ElasticsearchClient): Promise { try { - const loggerDeprecationCallResult = await callAsCurrentUser('cluster.getSettings', { - includeDefaults: true, + const { body: loggerDeprecationCallResult } = await esClient.cluster.getSettings({ + include_defaults: true, }); return isDeprecationLoggingEnabled(loggerDeprecationCallResult); @@ -53,7 +51,7 @@ async function getDeprecationLoggingStatusValue( } export async function fetchUpgradeAssistantMetrics( - { legacy: { client: esClient } }: ElasticsearchServiceStart, + { client: esClient }: ElasticsearchServiceStart, savedObjects: SavedObjectsServiceStart ): Promise { const savedObjectsRepository = savedObjects.createInternalRepository(); @@ -62,8 +60,9 @@ export async function fetchUpgradeAssistantMetrics( UPGRADE_ASSISTANT_TYPE, UPGRADE_ASSISTANT_DOC_ID ); - const callAsInternalUser = esClient.callAsInternalUser.bind(esClient); - const deprecationLoggingStatusValue = await getDeprecationLoggingStatusValue(callAsInternalUser); + const deprecationLoggingStatusValue = await getDeprecationLoggingStatusValue( + esClient.asInternalUser + ); const getTelemetrySavedObject = ( upgradeAssistantTelemetrySavedObjectAttrs: UpgradeAssistantTelemetrySavedObjectAttributes | null diff --git a/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/routes.mock.ts b/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/routes.mock.ts index 2df770c3ce45c..43fe8af18c392 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/routes.mock.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/__mocks__/routes.mock.ts @@ -12,9 +12,7 @@ import { export const routeHandlerContextMock = ({ core: { elasticsearch: { - legacy: { - client: elasticsearchServiceMock.createLegacyScopedClusterClient(), - }, + client: elasticsearchServiceMock.createScopedClusterClient(), }, savedObjects: { client: savedObjectsClientMock.create() }, }, diff --git a/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.test.ts index 16f8001f8e1de..630ee9157dc90 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.test.ts @@ -80,7 +80,7 @@ describe('cluster checkup API', () => { it('returns an 403 error if it throws forbidden', async () => { const e: any = new Error(`you can't go here!`); - e.status = 403; + e.statusCode = 403; MigrationApis.getUpgradeAssistantStatus.mockRejectedValue(e); const resp = await routeDependencies.router.getHandler({ diff --git a/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts b/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts index ff673adaf1642..6eb0378b21baa 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/cluster_checkup.ts @@ -23,9 +23,7 @@ export function registerClusterCheckupRoutes({ cloud, router, licensing, log }: { core: { savedObjects: { client: savedObjectsClient }, - elasticsearch: { - legacy: { client }, - }, + elasticsearch: { client }, }, }, request, @@ -34,10 +32,10 @@ export function registerClusterCheckupRoutes({ cloud, router, licensing, log }: try { const status = await getUpgradeAssistantStatus(client, isCloudEnabled); - const callAsCurrentUser = client.callAsCurrentUser.bind(client); - const reindexActions = reindexActionsFactory(savedObjectsClient, callAsCurrentUser); + const asCurrentUser = client.asCurrentUser; + const reindexActions = reindexActionsFactory(savedObjectsClient, asCurrentUser); const reindexService = reindexServiceFactory( - callAsCurrentUser, + asCurrentUser, reindexActions, log, licensing @@ -52,7 +50,7 @@ export function registerClusterCheckupRoutes({ cloud, router, licensing, log }: body: status, }); } catch (e) { - if (e.status === 403) { + if (e.statusCode === 403) { return response.forbidden(e.message); } diff --git a/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts index b8021eeef75e6..f76a86704e2c4 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.test.ts @@ -37,9 +37,9 @@ describe('deprecation logging API', () => { describe('GET /api/upgrade_assistant/deprecation_logging', () => { it('returns isEnabled', async () => { - (routeHandlerContextMock.core.elasticsearch.legacy.client - .callAsCurrentUser as jest.Mock).mockResolvedValue({ - default: { logger: { deprecation: 'WARN' } }, + (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.cluster + .getSettings as jest.Mock).mockResolvedValue({ + body: { default: { logger: { deprecation: 'WARN' } } }, }); const resp = await routeDependencies.router.getHandler({ method: 'get', @@ -51,8 +51,8 @@ describe('deprecation logging API', () => { }); it('returns an error if it throws', async () => { - (routeHandlerContextMock.core.elasticsearch.legacy.client - .callAsCurrentUser as jest.Mock).mockRejectedValue(new Error(`scary error!`)); + (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.cluster + .getSettings as jest.Mock).mockRejectedValue(new Error(`scary error!`)); const resp = await routeDependencies.router.getHandler({ method: 'get', pathPattern: '/api/upgrade_assistant/deprecation_logging', @@ -64,21 +64,21 @@ describe('deprecation logging API', () => { describe('PUT /api/upgrade_assistant/deprecation_logging', () => { it('returns isEnabled', async () => { - (routeHandlerContextMock.core.elasticsearch.legacy.client - .callAsCurrentUser as jest.Mock).mockResolvedValue({ - default: { logger: { deprecation: 'ERROR' } }, + (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.cluster + .putSettings as jest.Mock).mockResolvedValue({ + body: { default: { logger: { deprecation: 'ERROR' } } }, }); const resp = await routeDependencies.router.getHandler({ - method: 'get', + method: 'put', pathPattern: '/api/upgrade_assistant/deprecation_logging', - })(routeHandlerContextMock, createRequestMock(), kibanaResponseFactory); + })(routeHandlerContextMock, { body: { isEnabled: true } }, kibanaResponseFactory); expect(resp.payload).toEqual({ isEnabled: false }); }); it('returns an error if it throws', async () => { - (routeHandlerContextMock.core.elasticsearch.legacy.client - .callAsCurrentUser as jest.Mock).mockRejectedValue(new Error(`scary error!`)); + (routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.cluster + .putSettings as jest.Mock).mockRejectedValue(new Error(`scary error!`)); const resp = await routeDependencies.router.getHandler({ method: 'put', pathPattern: '/api/upgrade_assistant/deprecation_logging', diff --git a/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.ts b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.ts index 38f244a4e1154..4bdb1364b071a 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/deprecation_logging.ts @@ -23,9 +23,7 @@ export function registerDeprecationLoggingRoutes({ router }: RouteDependencies) async ( { core: { - elasticsearch: { - legacy: { client }, - }, + elasticsearch: { client }, }, }, request, @@ -54,9 +52,7 @@ export function registerDeprecationLoggingRoutes({ router }: RouteDependencies) async ( { core: { - elasticsearch: { - legacy: { client }, - }, + elasticsearch: { client }, }, }, request, diff --git a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_handler.ts b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_handler.ts index fffde339c59e5..e33398fdcc93b 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_handler.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_handler.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { i18n } from '@kbn/i18n'; -import { ILegacyScopedClusterClient, Logger, SavedObjectsClientContract } from 'kibana/server'; +import { IScopedClusterClient, Logger, SavedObjectsClientContract } from 'kibana/server'; import { LicensingPluginSetup } from '../../../../licensing/server'; @@ -17,7 +17,7 @@ import { error } from '../../lib/reindexing/error'; interface ReindexHandlerArgs { savedObjects: SavedObjectsClientContract; - dataClient: ILegacyScopedClusterClient; + dataClient: IScopedClusterClient; indexName: string; log: Logger; licensing: LicensingPluginSetup; @@ -38,7 +38,7 @@ export const reindexHandler = async ({ savedObjects, reindexOptions, }: ReindexHandlerArgs): Promise => { - const callAsCurrentUser = dataClient.callAsCurrentUser.bind(dataClient); + const callAsCurrentUser = dataClient.asCurrentUser; const reindexActions = reindexActionsFactory(savedObjects, callAsCurrentUser); const reindexService = reindexServiceFactory(callAsCurrentUser, reindexActions, log, licensing); diff --git a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts index c745def5e8936..52157f39743e8 100644 --- a/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts +++ b/x-pack/plugins/upgrade_assistant/server/routes/reindex_indices/reindex_indices.ts @@ -51,7 +51,7 @@ export function createReindexWorker({ savedObjects, licensing, }: CreateReindexWorker) { - const esClient = elasticsearchService.legacy.client; + const esClient = elasticsearchService.client; return new ReindexWorker(savedObjects, credentialStore, esClient, logger, licensing); } @@ -100,9 +100,7 @@ export function registerReindexIndicesRoutes( { core: { savedObjects: { client: savedObjectsClient }, - elasticsearch: { - legacy: { client: esClient }, - }, + elasticsearch: { client: esClient }, }, }, request, @@ -142,9 +140,7 @@ export function registerReindexIndicesRoutes( async ( { core: { - elasticsearch: { - legacy: { client: esClient }, - }, + elasticsearch: { client: esClient }, savedObjects, }, }, @@ -152,7 +148,7 @@ export function registerReindexIndicesRoutes( response ) => { const { client } = savedObjects; - const callAsCurrentUser = esClient.callAsCurrentUser.bind(esClient); + const callAsCurrentUser = esClient.asCurrentUser; const reindexActions = reindexActionsFactory(client, callAsCurrentUser); try { const inProgressOps = await reindexActions.findAllByStatus(ReindexStatus.inProgress); @@ -184,9 +180,7 @@ export function registerReindexIndicesRoutes( { core: { savedObjects: { client: savedObjectsClient }, - elasticsearch: { - legacy: { client: esClient }, - }, + elasticsearch: { client: esClient }, }, }, request, @@ -245,9 +239,7 @@ export function registerReindexIndicesRoutes( { core: { savedObjects, - elasticsearch: { - legacy: { client: esClient }, - }, + elasticsearch: { client: esClient }, }, }, request, @@ -255,14 +247,9 @@ export function registerReindexIndicesRoutes( ) => { const { client } = savedObjects; const { indexName } = request.params; - const callAsCurrentUser = esClient.callAsCurrentUser.bind(esClient); - const reindexActions = reindexActionsFactory(client, callAsCurrentUser); - const reindexService = reindexServiceFactory( - callAsCurrentUser, - reindexActions, - log, - licensing - ); + const asCurrentUser = esClient.asCurrentUser; + const reindexActions = reindexActionsFactory(client, asCurrentUser); + const reindexService = reindexServiceFactory(asCurrentUser, reindexActions, log, licensing); try { const hasRequiredPrivileges = await reindexService.hasRequiredPrivileges(indexName); @@ -303,9 +290,7 @@ export function registerReindexIndicesRoutes( { core: { savedObjects, - elasticsearch: { - legacy: { client: esClient }, - }, + elasticsearch: { client: esClient }, }, }, request, @@ -313,7 +298,7 @@ export function registerReindexIndicesRoutes( ) => { const { indexName } = request.params; const { client } = savedObjects; - const callAsCurrentUser = esClient.callAsCurrentUser.bind(esClient); + const callAsCurrentUser = esClient.asCurrentUser; const reindexActions = reindexActionsFactory(client, callAsCurrentUser); const reindexService = reindexServiceFactory( callAsCurrentUser, diff --git a/x-pack/plugins/uptime/server/lib/requests/get_network_events.test.ts b/x-pack/plugins/uptime/server/lib/requests/get_network_events.test.ts index 2d590e80ca42d..e1c62f3469518 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_network_events.test.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_network_events.test.ts @@ -5,9 +5,19 @@ */ import { getUptimeESMockClient } from './helper'; -import { getNetworkEvents } from './get_network_events'; +import { getNetworkEvents, secondsToMillis } from './get_network_events'; describe('getNetworkEvents', () => { + describe('secondsToMillis conversion', () => { + it('returns -1 for -1 value', () => { + expect(secondsToMillis(-1)).toBe(-1); + }); + + it('returns a value of seconds as milliseconds', () => { + expect(secondsToMillis(10)).toBe(10_000); + }); + }); + let mockHits: any; beforeEach(() => { diff --git a/x-pack/plugins/uptime/server/lib/requests/get_network_events.ts b/x-pack/plugins/uptime/server/lib/requests/get_network_events.ts index ec1fffd62350d..6b1bca8f2d7b7 100644 --- a/x-pack/plugins/uptime/server/lib/requests/get_network_events.ts +++ b/x-pack/plugins/uptime/server/lib/requests/get_network_events.ts @@ -12,6 +12,10 @@ interface GetNetworkEventsParams { stepIndex: string; } +export const secondsToMillis = (seconds: number) => + // -1 is a special case where a value was unavailable + seconds === -1 ? -1 : seconds * 1000; + export const getNetworkEvents: UMElasticsearchQueryFn< GetNetworkEventsParams, { events: NetworkEvent[]; total: number } @@ -35,17 +39,15 @@ export const getNetworkEvents: UMElasticsearchQueryFn< const { body: result } = await uptimeEsClient.search({ body: params }); - const microToMillis = (micro: number): number => (micro === -1 ? -1 : micro * 1000); - return { total: result.hits.total.value, events: result.hits.hits.map((event: any) => { - const requestSentTime = microToMillis(event._source.synthetics.payload.request_sent_time); - const loadEndTime = microToMillis(event._source.synthetics.payload.load_end_time); + const requestSentTime = secondsToMillis(event._source.synthetics.payload.request_sent_time); + const loadEndTime = secondsToMillis(event._source.synthetics.payload.load_end_time); const requestStartTime = event._source.synthetics.payload.response && event._source.synthetics.payload.response.timing - ? microToMillis(event._source.synthetics.payload.response.timing.request_time) + ? secondsToMillis(event._source.synthetics.payload.response.timing.request_time) : undefined; return { diff --git a/x-pack/plugins/vis_type_timeseries_enhanced/README.md b/x-pack/plugins/vis_type_timeseries_enhanced/README.md deleted file mode 100644 index 33aa16d8574ae..0000000000000 --- a/x-pack/plugins/vis_type_timeseries_enhanced/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# vis_type_timeseries_enhanced - -The `vis_type_timeseries_enhanced` plugin is the x-pack counterpart to the OSS `vis_type_timeseries` plugin. - -It exists to provide Elastic-licensed services, or parts of services, which -enhance existing OSS functionality from `vis_type_timeseries`. - -Currently the `vis_type_timeseries_enhanced` plugin doesn't return any APIs which you can -consume directly. - diff --git a/x-pack/plugins/vis_type_timeseries_enhanced/kibana.json b/x-pack/plugins/vis_type_timeseries_enhanced/kibana.json deleted file mode 100644 index 4b296856c3f97..0000000000000 --- a/x-pack/plugins/vis_type_timeseries_enhanced/kibana.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "id": "visTypeTimeseriesEnhanced", - "version": "8.0.0", - "kibanaVersion": "kibana", - "server": true, - "ui": false, - "requiredPlugins": [ - "visTypeTimeseries" - ] -} diff --git a/x-pack/plugins/vis_type_timeseries_enhanced/server/plugin.ts b/x-pack/plugins/vis_type_timeseries_enhanced/server/plugin.ts deleted file mode 100644 index 0598a691ab7c5..0000000000000 --- a/x-pack/plugins/vis_type_timeseries_enhanced/server/plugin.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Plugin, PluginInitializerContext, Logger, CoreSetup } from 'src/core/server'; -import { VisTypeTimeseriesSetup } from 'src/plugins/vis_type_timeseries/server'; -import { RollupSearchStrategy } from './search_strategies/rollup_search_strategy'; - -interface VisTypeTimeseriesEnhancedSetupDependencies { - visTypeTimeseries: VisTypeTimeseriesSetup; -} - -export class VisTypeTimeseriesEnhanced - implements Plugin { - private logger: Logger; - - constructor(initializerContext: PluginInitializerContext) { - this.logger = initializerContext.logger.get('vis_type_timeseries_enhanced'); - } - - public async setup( - core: CoreSetup, - { visTypeTimeseries }: VisTypeTimeseriesEnhancedSetupDependencies - ) { - this.logger.debug('Starting plugin'); - - visTypeTimeseries.addSearchStrategy(new RollupSearchStrategy()); - } - - public start() {} -} diff --git a/x-pack/plugins/watcher/public/application/models/watch/default_watch.js b/x-pack/plugins/watcher/public/application/models/watch/default_watch.js new file mode 100644 index 0000000000000..51b28f8bd0332 --- /dev/null +++ b/x-pack/plugins/watcher/public/application/models/watch/default_watch.js @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const defaultWatch = { + trigger: { + schedule: { + interval: '30m', + }, + }, + input: { + search: { + request: { + body: { + size: 0, + query: { + match_all: {}, + }, + }, + indices: ['*'], + }, + }, + }, + condition: { + compare: { + 'ctx.payload.hits.total': { + gte: 10, + }, + }, + }, + actions: { + 'my-logging-action': { + logging: { + text: 'There are {{ctx.payload.hits.total}} documents in your index. Threshold is 10.', + }, + }, + }, +}; diff --git a/x-pack/plugins/watcher/public/application/models/watch/default_watch.json b/x-pack/plugins/watcher/public/application/models/watch/default_watch.json deleted file mode 100644 index 22c78660a0bb0..0000000000000 --- a/x-pack/plugins/watcher/public/application/models/watch/default_watch.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "trigger": { - "schedule": { - "interval": "30m" - } - }, - "input": { - "search": { - "request": { - "body": { - "size": 0, - "query" : { - "match_all": {} - } - }, - "indices": [ "*" ] - } - } - }, - "condition": { - "compare": { - "ctx.payload.hits.total": { - "gte": 10 - } - }}, - "actions": { - "my-logging-action": { - "logging": { - "text": "There are {{ctx.payload.hits.total}} documents in your index. Threshold is 10." - } - } - } -} diff --git a/x-pack/plugins/watcher/public/application/models/watch/index.d.ts b/x-pack/plugins/watcher/public/application/models/watch/index.d.ts index 73ee2279d3912..b9158bdd9ed70 100644 --- a/x-pack/plugins/watcher/public/application/models/watch/index.d.ts +++ b/x-pack/plugins/watcher/public/application/models/watch/index.d.ts @@ -5,3 +5,4 @@ */ export const Watch: any; +export const defaultWatch: any; diff --git a/x-pack/plugins/watcher/public/application/models/watch/index.js b/x-pack/plugins/watcher/public/application/models/watch/index.js index 9a74f6e548409..0741e5c5400df 100644 --- a/x-pack/plugins/watcher/public/application/models/watch/index.js +++ b/x-pack/plugins/watcher/public/application/models/watch/index.js @@ -5,3 +5,4 @@ */ export { Watch } from './watch'; +export { defaultWatch } from './default_watch'; diff --git a/x-pack/plugins/watcher/public/application/models/watch/json_watch.js b/x-pack/plugins/watcher/public/application/models/watch/json_watch.js index 0e67c8b18ca5e..24378d42b7451 100644 --- a/x-pack/plugins/watcher/public/application/models/watch/json_watch.js +++ b/x-pack/plugins/watcher/public/application/models/watch/json_watch.js @@ -6,11 +6,12 @@ import uuid from 'uuid'; import { get } from 'lodash'; -import { BaseWatch } from './base_watch'; -import { ACTION_TYPES, WATCH_TYPES } from '../../../../common/constants'; -import defaultWatchJson from './default_watch.json'; import { i18n } from '@kbn/i18n'; +import { ACTION_TYPES, WATCH_TYPES } from '../../../../common/constants'; +import { BaseWatch } from './base_watch'; +import { defaultWatch } from './default_watch'; + /** * {@code JsonWatch} allows a user to create a Watch by writing the raw JSON. */ @@ -20,11 +21,11 @@ export class JsonWatch extends BaseWatch { props.id = typeof props.id === 'undefined' ? uuid.v4() : props.id; super(props); const existingWatch = get(props, 'watch'); - this.watch = existingWatch ? existingWatch : defaultWatchJson; + this.watch = existingWatch ? existingWatch : defaultWatch; this.watchString = get( props, 'watchString', - JSON.stringify(existingWatch ? existingWatch : defaultWatchJson, null, 2) + JSON.stringify(existingWatch ? existingWatch : defaultWatch, null, 2) ); this.id = props.id; } @@ -113,7 +114,6 @@ export class JsonWatch extends BaseWatch { return new JsonWatch(upstreamWatch); } - static defaultWatchJson = defaultWatchJson; static typeName = i18n.translate('xpack.watcher.models.jsonWatch.typeName', { defaultMessage: 'Advanced Watch', }); diff --git a/x-pack/plugins/watcher/tests_client_integration/watch_create_json.test.ts b/x-pack/plugins/watcher/tests_client_integration/watch_create_json.test.ts index b3fbb8235f251..9bd8f8bbd7d57 100644 --- a/x-pack/plugins/watcher/tests_client_integration/watch_create_json.test.ts +++ b/x-pack/plugins/watcher/tests_client_integration/watch_create_json.test.ts @@ -5,11 +5,12 @@ */ import { act } from 'react-dom/test-utils'; + +import { getExecuteDetails } from '../__fixtures__'; +import { defaultWatch } from '../public/application/models/watch'; import { setupEnvironment, pageHelpers, nextTick, wrapBodyResponse } from './helpers'; import { WatchCreateJsonTestBed } from './helpers/watch_create_json.helpers'; import { WATCH } from './helpers/jest_constants'; -import defaultWatchJson from '../public/application/models/watch/default_watch.json'; -import { getExecuteDetails } from '../__fixtures__'; const { setup } = pageHelpers.watchCreateJson; @@ -117,7 +118,7 @@ describe(' create route', () => { }, }, ], - watch: defaultWatchJson, + watch: defaultWatch, }) ); }); @@ -172,7 +173,7 @@ describe(' create route', () => { const latestRequest = server.requests[server.requests.length - 1]; - const actionModes = Object.keys(defaultWatchJson.actions).reduce( + const actionModes = Object.keys(defaultWatch.actions).reduce( (actionAccum: any, action) => { actionAccum[action] = 'simulate'; return actionAccum; @@ -186,7 +187,7 @@ describe(' create route', () => { isNew: true, isActive: true, actions: [], - watch: defaultWatchJson, + watch: defaultWatch, }; expect(latestRequest.requestBody).toEqual( @@ -234,7 +235,7 @@ describe(' create route', () => { const latestRequest = server.requests[server.requests.length - 1]; - const actionModes = Object.keys(defaultWatchJson.actions).reduce( + const actionModes = Object.keys(defaultWatch.actions).reduce( (actionAccum: any, action) => { actionAccum[action] = ACTION_MODE; return actionAccum; @@ -248,7 +249,7 @@ describe(' create route', () => { isNew: true, isActive: true, actions: [], - watch: defaultWatchJson, + watch: defaultWatch, }; const triggeredTime = `now+${TRIGGERED_TIME}s`; diff --git a/x-pack/plugins/watcher/tests_client_integration/watch_edit.test.ts b/x-pack/plugins/watcher/tests_client_integration/watch_edit.test.ts index eefe9d03c05ef..c24d939c9237e 100644 --- a/x-pack/plugins/watcher/tests_client_integration/watch_edit.test.ts +++ b/x-pack/plugins/watcher/tests_client_integration/watch_edit.test.ts @@ -7,12 +7,13 @@ import { act } from 'react-dom/test-utils'; import axiosXhrAdapter from 'axios/lib/adapters/xhr'; import axios from 'axios'; +import { getRandomString } from '@kbn/test/jest'; + +import { getWatch } from '../__fixtures__'; +import { defaultWatch } from '../public/application/models/watch'; import { setupEnvironment, pageHelpers, nextTick, wrapBodyResponse } from './helpers'; import { WatchEditTestBed } from './helpers/watch_edit.helpers'; import { WATCH } from './helpers/jest_constants'; -import defaultWatchJson from '../public/application/models/watch/default_watch.json'; -import { getWatch } from '../__fixtures__'; -import { getRandomString } from '@kbn/test/jest'; const mockHttpClient = axios.create({ adapter: axiosXhrAdapter }); @@ -69,7 +70,7 @@ describe('', () => { expect(exists('jsonWatchForm')).toBe(true); expect(find('nameInput').props().value).toBe(watch.name); expect(find('idInput').props().value).toBe(watch.id); - expect(JSON.parse(codeEditor.props().value as string)).toEqual(defaultWatchJson); + expect(JSON.parse(codeEditor.props().value as string)).toEqual(defaultWatch); // ID should not be editable expect(find('idInput').props().readOnly).toEqual(true); @@ -112,7 +113,7 @@ describe('', () => { }, }, ], - watch: defaultWatchJson, + watch: defaultWatch, }) ); }); diff --git a/x-pack/test/alerting_api_integration/common/config.ts b/x-pack/test/alerting_api_integration/common/config.ts index cf944008c08d6..4193843c63bce 100644 --- a/x-pack/test/alerting_api_integration/common/config.ts +++ b/x-pack/test/alerting_api_integration/common/config.ts @@ -17,6 +17,7 @@ interface CreateTestConfigOptions { disabledPlugins?: string[]; ssl?: boolean; enableActionsProxy: boolean; + rejectUnauthorized?: boolean; } // test.not-enabled is specifically not enabled @@ -39,7 +40,12 @@ const enabledActionTypes = [ ]; export function createTestConfig(name: string, options: CreateTestConfigOptions) { - const { license = 'trial', disabledPlugins = [], ssl = false } = options; + const { + license = 'trial', + disabledPlugins = [], + ssl = false, + rejectUnauthorized = true, + } = options; return async ({ readConfigFile }: FtrConfigProviderContext) => { const xPackApiIntegrationTestsConfig = await readConfigFile( @@ -95,6 +101,7 @@ export function createTestConfig(name: string, options: CreateTestConfigOptions) '--xpack.encryptedSavedObjects.encryptionKey="wuGNaIhoMpk5sO4UBxgr3NyW1sFcLgIf"', '--xpack.alerts.invalidateApiKeysTask.interval="15s"', `--xpack.actions.enabledActionTypes=${JSON.stringify(enabledActionTypes)}`, + `--xpack.actions.rejectUnauthorized=${rejectUnauthorized}`, ...actionsProxyUrl, '--xpack.eventLog.logEntries=true', diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/plugin.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/plugin.ts index e7ce0638c6319..18f3c83b00141 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/plugin.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/plugin.ts @@ -5,6 +5,7 @@ */ import http from 'http'; +import https from 'https'; import { Plugin, CoreSetup, IRouter } from 'kibana/server'; import { EncryptedSavedObjectsPluginStart } from '../../../../../../../plugins/encrypted_saved_objects/server'; import { PluginSetupContract as FeaturesPluginSetup } from '../../../../../../../plugins/features/server'; @@ -47,7 +48,13 @@ export function getAllExternalServiceSimulatorPaths(): string[] { } export async function getWebhookServer(): Promise { - return await initWebhook(); + const { httpServer } = await initWebhook(); + return httpServer; +} + +export async function getHttpsWebhookServer(): Promise { + const { httpsServer } = await initWebhook(); + return httpsServer; } export async function getSlackServer(): Promise { diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/webhook_simulation.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/webhook_simulation.ts index a34293090d7af..116f0604a37c9 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/webhook_simulation.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/actions_simulators/server/webhook_simulation.ts @@ -3,16 +3,35 @@ * 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 expect from '@kbn/expect'; import http from 'http'; +import https from 'https'; +import { promisify } from 'util'; import { fromNullable, map, filter, getOrElse } from 'fp-ts/lib/Option'; import { pipe } from 'fp-ts/lib/pipeable'; import { constant } from 'fp-ts/lib/function'; +import { KBN_KEY_PATH, KBN_CERT_PATH } from '@kbn/dev-utils'; export async function initPlugin() { - const payloads: string[] = []; + const httpsServerKey = await promisify(fs.readFile)(KBN_KEY_PATH, 'utf8'); + const httpsServerCert = await promisify(fs.readFile)(KBN_CERT_PATH, 'utf8'); + + return { + httpServer: http.createServer(createServerCallback()), + httpsServer: https.createServer( + { + key: httpsServerKey, + cert: httpsServerCert, + }, + createServerCallback() + ), + }; +} - return http.createServer((request, response) => { +function createServerCallback() { + const payloads: string[] = []; + return (request: http.IncomingMessage, response: http.ServerResponse) => { const credentials = pipe( fromNullable(request.headers.authorization), map((authorization) => authorization.split(/\s+/)), @@ -77,7 +96,7 @@ export async function initPlugin() { return; }); } - }); + }; } function validateAuthentication(credentials: any, res: any) { diff --git a/x-pack/test/alerting_api_integration/common/lib/get_event_log.ts b/x-pack/test/alerting_api_integration/common/lib/get_event_log.ts index 6336d834c3943..5b093dfb28eab 100644 --- a/x-pack/test/alerting_api_integration/common/lib/get_event_log.ts +++ b/x-pack/test/alerting_api_integration/common/lib/get_event_log.ts @@ -28,6 +28,7 @@ interface GetEventLogParams { id: string; provider: string; actions: Map; + filter?: string; } // Return event log entries given the specified parameters; for the `actions` @@ -37,7 +38,9 @@ export async function getEventLog(params: GetEventLogParams): Promise { - let webhookSimulatorURL: string = ''; - let webhookServer: http.Server; - before(async () => { - webhookServer = await getWebhookServer(); - const availablePort = await getPort({ port: 9000 }); - webhookServer.listen(availablePort); - webhookSimulatorURL = `http://localhost:${availablePort}`; - }); + describe('with http endpoint', () => { + let webhookSimulatorURL: string = ''; + let webhookServer: http.Server; + before(async () => { + webhookServer = await getWebhookServer(); + const availablePort = await getPort({ port: 9000 }); + webhookServer.listen(availablePort); + webhookSimulatorURL = `http://localhost:${availablePort}`; + }); + + it('webhook can be executed without username and password', async () => { + const webhookActionId = await createWebhookAction(webhookSimulatorURL); + const { body: result } = await supertest + .post(`/api/actions/action/${webhookActionId}/_execute`) + .set('kbn-xsrf', 'test') + .send({ + params: { + body: 'success', + }, + }) + .expect(200); - it('webhook can be executed without username and password', async () => { - const webhookActionId = await createWebhookAction(webhookSimulatorURL); - const { body: result } = await supertest - .post(`/api/actions/action/${webhookActionId}/_execute`) - .set('kbn-xsrf', 'test') - .send({ - params: { - body: 'success', - }, - }) - .expect(200); + expect(result.status).to.eql('ok'); + }); - expect(result.status).to.eql('ok'); + after(() => { + webhookServer.close(); + }); }); - after(() => { - webhookServer.close(); + describe('with https endpoint and rejectUnauthorized=false', () => { + let webhookSimulatorURL: string = ''; + let webhookServer: https.Server; + + before(async () => { + webhookServer = await getHttpsWebhookServer(); + const availablePort = await getPort({ port: getPort.makeRange(9000, 9100) }); + webhookServer.listen(availablePort); + webhookSimulatorURL = `https://localhost:${availablePort}`; + }); + + it('should support the POST method against webhook target', async () => { + const webhookActionId = await createWebhookAction(webhookSimulatorURL, { method: 'post' }); + const { body: result } = await supertest + .post(`/api/actions/action/${webhookActionId}/_execute`) + .set('kbn-xsrf', 'test') + .send({ + params: { + body: 'success_post_method', + }, + }) + .expect(200); + + expect(result.status).to.eql('ok'); + }); + + after(() => { + webhookServer.close(); + }); }); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts index d3e1370bef285..5ff7b0d45a019 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/event_log.ts @@ -84,6 +84,23 @@ export default function eventLogTests({ getService }: FtrProviderContext) { }); }); + // get the filtered events only with action 'new-instance' + const filteredEvents = await retry.try(async () => { + return await getEventLog({ + getService, + spaceId: Spaces.space1.id, + type: 'alert', + id: alertId, + provider: 'alerting', + actions: new Map([['new-instance', { equal: 1 }]]), + filter: 'event.action:(new-instance)', + }); + }); + + expect(getEventsByAction(filteredEvents, 'execute').length).equal(0); + expect(getEventsByAction(filteredEvents, 'execute-action').length).equal(0); + expect(getEventsByAction(events, 'new-instance').length).equal(1); + const executeEvents = getEventsByAction(events, 'execute'); const executeActionEvents = getEventsByAction(events, 'execute-action'); const newInstanceEvents = getEventsByAction(events, 'new-instance'); diff --git a/x-pack/test/api_integration/apis/metrics_ui/log_entries.ts b/x-pack/test/api_integration/apis/metrics_ui/log_entries.ts index 2d148f4c2c0f7..79d5e68344432 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/log_entries.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/log_entries.ts @@ -13,10 +13,13 @@ import { LOG_ENTRIES_PATH, logEntriesRequestRT, logEntriesResponseRT, +} from '../../../../plugins/infra/common/http_api'; + +import { LogTimestampColumn, LogFieldColumn, LogMessageColumn, -} from '../../../../plugins/infra/common/http_api'; +} from '../../../../plugins/infra/common/log_entry'; import { FtrProviderContext } from '../../ftr_provider_context'; diff --git a/x-pack/test/api_integration/apis/metrics_ui/metrics.ts b/x-pack/test/api_integration/apis/metrics_ui/metrics.ts index 23b0a96ecd401..b9cbc58bbd6f7 100644 --- a/x-pack/test/api_integration/apis/metrics_ui/metrics.ts +++ b/x-pack/test/api_integration/apis/metrics_ui/metrics.ts @@ -93,5 +93,30 @@ export default function ({ getService }: FtrProviderContext) { expect(resp.metrics.length).to.equal(2); }); }); + + it('should return multiple values for hostSystemOverview metric', () => { + const data = fetchNodeDetails({ + sourceId: 'default', + metrics: ['hostSystemOverview'], + timerange: { + to: max, + from: min, + interval: '>=1m', + }, + nodeId: 'demo-stack-mysql-01', + nodeType: 'host' as InfraNodeType, + }); + return data.then((resp) => { + if (!resp) { + return; + } + + const hostSystemOverviewMetric = resp.metrics.find( + (metric) => metric.id === 'hostSystemOverview' + ); + + expect(hostSystemOverviewMetric?.series.length).to.be.greaterThan(1); + }); + }); }); } diff --git a/x-pack/test/api_integration/apis/upgrade_assistant/upgrade_assistant.ts b/x-pack/test/api_integration/apis/upgrade_assistant/upgrade_assistant.ts index daeb71ef12382..bdfbff61bc5db 100644 --- a/x-pack/test/api_integration/apis/upgrade_assistant/upgrade_assistant.ts +++ b/x-pack/test/api_integration/apis/upgrade_assistant/upgrade_assistant.ts @@ -10,7 +10,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; import { reindexOperationWithLargeErrorMessage } from './reindex_operation_with_large_error_message'; export default function ({ getService }: FtrProviderContext) { - const es = getService('legacyEs'); + const es = getService('es'); describe('Reindex operation saved object', function () { const dotKibanaIndex = '.kibana'; diff --git a/x-pack/test/apm_api_integration/basic/config.ts b/x-pack/test/apm_api_integration/basic/config.ts index 03b8b21bf3232..f3e6ec29f3df2 100644 --- a/x-pack/test/apm_api_integration/basic/config.ts +++ b/x-pack/test/apm_api_integration/basic/config.ts @@ -4,10 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createTestConfig } from '../common/config'; +import { configs } from '../configs'; -export default createTestConfig({ - license: 'basic', - name: 'X-Pack APM API integration tests (basic)', - testFiles: [require.resolve('./tests')], -}); +export default configs.basic; diff --git a/x-pack/test/apm_api_integration/basic/tests/alerts/chart_preview.ts b/x-pack/test/apm_api_integration/basic/tests/alerts/chart_preview.ts deleted file mode 100644 index 46c0dbeb8940f..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/alerts/chart_preview.ts +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { format } from 'url'; -import archives from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const esArchiver = getService('esArchiver'); - const supertest = getService('supertest'); - const archiveName = 'apm_8.0.0'; - const { end } = archives[archiveName]; - const start = new Date(Date.parse(end) - 600000).toISOString(); - - describe('Alerting chart previews', () => { - describe('GET /api/apm/alerts/chart_preview/transaction_error_rate', () => { - const url = format({ - pathname: '/api/apm/alerts/chart_preview/transaction_error_rate', - query: { - start, - end, - transactionType: 'request', - serviceName: 'opbeans-java', - }, - }); - - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect(response.body).to.eql([]); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - it('returns the correct data', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect( - response.body.some((item: { x: number; y: number | null }) => item.x && item.y) - ).to.equal(true); - }); - }); - }); - - describe('GET /api/apm/alerts/chart_preview/transaction_error_count', () => { - const url = format({ - pathname: '/api/apm/alerts/chart_preview/transaction_error_count', - query: { - start, - end, - serviceName: 'opbeans-java', - }, - }); - - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect(response.body).to.eql([]); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - it('returns the correct data', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect( - response.body.some((item: { x: number; y: number | null }) => item.x && item.y) - ).to.equal(true); - }); - }); - }); - - describe('GET /api/apm/alerts/chart_preview/transaction_duration', () => { - const url = format({ - pathname: '/api/apm/alerts/chart_preview/transaction_duration', - query: { - start, - end, - serviceName: 'opbeans-java', - transactionType: 'request', - }, - }); - - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect(response.body).to.eql([]); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - it('returns the correct data', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect( - response.body.some((item: { x: number; y: number | null }) => item.x && item.y) - ).to.equal(true); - }); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/index.ts b/x-pack/test/apm_api_integration/basic/tests/index.ts deleted file mode 100644 index 4e66c6e6f76c3..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/index.ts +++ /dev/null @@ -1,72 +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 { FtrProviderContext } from '../../common/ftr_provider_context'; - -export default function apmApiIntegrationTests({ loadTestFile }: FtrProviderContext) { - describe('APM specs (basic)', function () { - this.tags('ciGroup1'); - - loadTestFile(require.resolve('./feature_controls')); - - describe('Alerts', function () { - loadTestFile(require.resolve('./alerts/chart_preview')); - }); - - describe('Service Maps', function () { - loadTestFile(require.resolve('./service_maps/service_maps')); - }); - - describe('Services', function () { - loadTestFile(require.resolve('./services/agent_name')); - loadTestFile(require.resolve('./services/annotations')); - loadTestFile(require.resolve('./services/throughput')); - loadTestFile(require.resolve('./services/top_services')); - loadTestFile(require.resolve('./services/transaction_types')); - loadTestFile(require.resolve('./services/service_details')); - loadTestFile(require.resolve('./services/service_icons')); - }); - - describe('Service overview', function () { - loadTestFile(require.resolve('./service_overview/error_groups')); - loadTestFile(require.resolve('./service_overview/dependencies')); - loadTestFile(require.resolve('./service_overview/instances')); - }); - - describe('Settings', function () { - loadTestFile(require.resolve('./settings/custom_link')); - loadTestFile(require.resolve('./settings/agent_configuration')); - - describe('Anomaly detection', function () { - loadTestFile(require.resolve('./settings/anomaly_detection/no_access_user')); - loadTestFile(require.resolve('./settings/anomaly_detection/read_user')); - loadTestFile(require.resolve('./settings/anomaly_detection/write_user')); - }); - }); - - describe('Traces', function () { - loadTestFile(require.resolve('./traces/top_traces')); - }); - - describe('Transactions', function () { - loadTestFile(require.resolve('./transactions/top_transaction_groups')); - loadTestFile(require.resolve('./transactions/latency')); - loadTestFile(require.resolve('./transactions/throughput')); - loadTestFile(require.resolve('./transactions/error_rate')); - loadTestFile(require.resolve('./transactions/breakdown')); - loadTestFile(require.resolve('./transactions/distribution')); - loadTestFile(require.resolve('./transactions/transactions_groups_overview')); - }); - - describe('Observability overview', function () { - loadTestFile(require.resolve('./observability_overview/has_data')); - loadTestFile(require.resolve('./observability_overview/observability_overview')); - }); - - describe('Metrics', function () { - loadTestFile(require.resolve('./metrics_charts/metrics_charts')); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/metrics_charts/metrics_charts.ts b/x-pack/test/apm_api_integration/basic/tests/metrics_charts/metrics_charts.ts deleted file mode 100644 index d52aa2727d651..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/metrics_charts/metrics_charts.ts +++ /dev/null @@ -1,439 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import expect from '@kbn/expect'; -import { first } from 'lodash'; -import { MetricsChartsByAgentAPIResponse } from '../../../../../plugins/apm/server/lib/metrics/get_metrics_chart_data_by_agent'; -import { GenericMetricsChart } from '../../../../../plugins/apm/server/lib/metrics/transform_metrics_chart'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -interface ChartResponse { - body: MetricsChartsByAgentAPIResponse; - status: number; -} - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - describe('when data is loaded', () => { - before(() => esArchiver.load('metrics_8.0.0')); - after(() => esArchiver.unload('metrics_8.0.0')); - - describe('for opbeans-node', () => { - const start = encodeURIComponent('2020-09-08T14:50:00.000Z'); - const end = encodeURIComponent('2020-09-08T14:55:00.000Z'); - const uiFilters = encodeURIComponent(JSON.stringify({})); - const agentName = 'nodejs'; - - describe('returns metrics data', () => { - let chartsResponse: ChartResponse; - before(async () => { - chartsResponse = await supertest.get( - `/api/apm/services/opbeans-node/metrics/charts?start=${start}&end=${end}&uiFilters=${uiFilters}&agentName=${agentName}` - ); - }); - it('contains CPU usage and System memory usage chart data', async () => { - expect(chartsResponse.status).to.be(200); - expectSnapshot(chartsResponse.body.charts.map((chart) => chart.title)).toMatchInline(` - Array [ - "CPU usage", - "System memory usage", - ] - `); - }); - - describe('CPU usage', () => { - let cpuUsageChart: GenericMetricsChart | undefined; - before(() => { - cpuUsageChart = chartsResponse.body.charts.find(({ key }) => key === 'cpu_usage_chart'); - }); - - it('has correct series', () => { - expect(cpuUsageChart).to.not.empty(); - expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "System max", - "System average", - "Process max", - "Process average", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 0.714, - 0.3877, - 0.75, - 0.2543, - ] - `); - }); - }); - - describe("System memory usage (using 'system.memory' fields to calculate the memory usage)", () => { - let systemMemoryUsageChart: GenericMetricsChart | undefined; - before(() => { - systemMemoryUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'memory_usage_chart' - ); - }); - - it('has correct series', () => { - expect(systemMemoryUsageChart).to.not.empty(); - expectSnapshot(systemMemoryUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "Max", - "Average", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(systemMemoryUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 0.722093920925555, - 0.718173546796348, - ] - `); - }); - }); - }); - }); - - describe('for opbeans-java', () => { - const uiFilters = encodeURIComponent(JSON.stringify({})); - const agentName = 'java'; - - describe('returns metrics data', () => { - const start = encodeURIComponent('2020-09-08T14:55:30.000Z'); - const end = encodeURIComponent('2020-09-08T15:00:00.000Z'); - - let chartsResponse: ChartResponse; - before(async () => { - chartsResponse = await supertest.get( - `/api/apm/services/opbeans-java/metrics/charts?start=${start}&end=${end}&uiFilters=${uiFilters}&agentName=${agentName}` - ); - }); - - it('has correct chart data', async () => { - expect(chartsResponse.status).to.be(200); - expectSnapshot(chartsResponse.body.charts.map((chart) => chart.title)).toMatchInline(` - Array [ - "CPU usage", - "System memory usage", - "Heap Memory", - "Non-Heap Memory", - "Thread Count", - "Garbage collection per minute", - "Garbage collection time spent per minute", - ] - `); - }); - - describe('CPU usage', () => { - let cpuUsageChart: GenericMetricsChart | undefined; - before(() => { - cpuUsageChart = chartsResponse.body.charts.find(({ key }) => key === 'cpu_usage_chart'); - }); - - it('has correct series', () => { - expect(cpuUsageChart).to.not.empty(); - expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "System max", - "System average", - "Process max", - "Process average", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 0.203, - 0.178777777777778, - 0.01, - 0.009, - ] - `); - }); - - it('has the correct rate', async () => { - const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); - expectSnapshot(yValues).toMatchInline(` - Array [ - 0.193, - 0.193, - 0.009, - 0.009, - ] - `); - }); - }); - - describe("System memory usage (using 'system.process.cgroup' fields to calculate the memory usage)", () => { - let systemMemoryUsageChart: GenericMetricsChart | undefined; - before(() => { - systemMemoryUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'memory_usage_chart' - ); - }); - - it('has correct series', () => { - expect(systemMemoryUsageChart).to.not.empty(); - expectSnapshot(systemMemoryUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "Max", - "Average", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(systemMemoryUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 0.707924703557837, - 0.705395980841182, - ] - `); - }); - - it('has the correct rate', async () => { - const yValues = systemMemoryUsageChart?.series.map((serie) => first(serie.data)?.y); - expectSnapshot(yValues).toMatchInline(` - Array [ - 0.707924703557837, - 0.707924703557837, - ] - `); - }); - }); - - describe('Heap Memory', () => { - let cpuUsageChart: GenericMetricsChart | undefined; - before(() => { - cpuUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'heap_memory_area_chart' - ); - }); - - it('has correct series', () => { - expect(cpuUsageChart).to.not.empty(); - expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "Avg. used", - "Avg. committed", - "Avg. limit", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 222501617.777778, - 374341632, - 1560281088, - ] - `); - }); - - it('has the correct rate', async () => { - const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); - expectSnapshot(yValues).toMatchInline(` - Array [ - 211472896, - 374341632, - 1560281088, - ] - `); - }); - }); - - describe('Non-Heap Memory', () => { - let cpuUsageChart: GenericMetricsChart | undefined; - before(() => { - cpuUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'non_heap_memory_area_chart' - ); - }); - - it('has correct series', () => { - expect(cpuUsageChart).to.not.empty(); - expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "Avg. used", - "Avg. committed", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 138573397.333333, - 147677639.111111, - ] - `); - }); - - it('has the correct rate', async () => { - const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); - expectSnapshot(yValues).toMatchInline(` - Array [ - 138162752, - 147386368, - ] - `); - }); - }); - - describe('Thread Count', () => { - let cpuUsageChart: GenericMetricsChart | undefined; - before(() => { - cpuUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'thread_count_line_chart' - ); - }); - - it('has correct series', () => { - expect(cpuUsageChart).to.not.empty(); - expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "Avg. count", - "Max count", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 44.4444444444444, - 45, - ] - `); - }); - - it('has the correct rate', async () => { - const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); - expectSnapshot(yValues).toMatchInline(` - Array [ - 44, - 44, - ] - `); - }); - }); - - describe('Garbage collection per minute', () => { - let cpuUsageChart: GenericMetricsChart | undefined; - before(() => { - cpuUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'gc_rate_line_chart' - ); - }); - - it('has correct series', () => { - expect(cpuUsageChart).to.not.empty(); - expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "G1 Old Generation", - "G1 Young Generation", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 0, - 15, - ] - `); - }); - }); - - describe('Garbage collection time spent per minute', () => { - let cpuUsageChart: GenericMetricsChart | undefined; - before(() => { - cpuUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'gc_time_line_chart' - ); - }); - - it('has correct series', () => { - expect(cpuUsageChart).to.not.empty(); - expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "G1 Old Generation", - "G1 Young Generation", - ] - `); - }); - - it('has correct series overall values', () => { - expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 0, - 187.5, - ] - `); - }); - }); - }); - - // 9223372036854771712 = memory limit for a c-group when no memory limit is specified - it('calculates system memory usage using system total field when cgroup limit is equal to 9223372036854771712', async () => { - const start = encodeURIComponent('2020-09-08T15:00:30.000Z'); - const end = encodeURIComponent('2020-09-08T15:05:00.000Z'); - - const chartsResponse: ChartResponse = await supertest.get( - `/api/apm/services/opbeans-java/metrics/charts?start=${start}&end=${end}&uiFilters=${uiFilters}&agentName=${agentName}` - ); - - const systemMemoryUsageChart = chartsResponse.body.charts.find( - ({ key }) => key === 'memory_usage_chart' - ); - - expect(systemMemoryUsageChart).to.not.empty(); - expectSnapshot(systemMemoryUsageChart?.series.map(({ title }) => title)).toMatchInline(` - Array [ - "Max", - "Average", - ] - `); - expectSnapshot(systemMemoryUsageChart?.series.map(({ overallValue }) => overallValue)) - .toMatchInline(` - Array [ - 0.114523896426499, - 0.114002376090415, - ] - `); - - const yValues = systemMemoryUsageChart?.series.map((serie) => first(serie.data)?.y); - expectSnapshot(yValues).toMatchInline(` - Array [ - 0.11383724014064, - 0.11383724014064, - ] - `); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/service_maps/service_maps.ts b/x-pack/test/apm_api_integration/basic/tests/service_maps/service_maps.ts deleted file mode 100644 index f44b1561f2a5a..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/service_maps/service_maps.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function serviceMapsApiTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - - const archiveName = 'apm_8.0.0'; - const metadata = archives_metadata[archiveName]; - - // url parameters - const start = encodeURIComponent(metadata.start); - const end = encodeURIComponent(metadata.end); - - describe('Service Maps', () => { - it('is only be available to users with Platinum license (or higher)', async () => { - const response = await supertest.get(`/api/apm/service-map?start=${start}&end=${end}`); - - expect(response.status).to.be(403); - - expectSnapshot(response.body.message).toMatchInline( - `"In order to access Service Maps, you must be subscribed to an Elastic Platinum license. With it, you'll have the ability to visualize your entire application stack along with your APM data."` - ); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/services/annotations.ts b/x-pack/test/apm_api_integration/basic/tests/services/annotations.ts deleted file mode 100644 index 3136dcef2e985..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/services/annotations.ts +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { JsonObject } from 'src/plugins/kibana_utils/common'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function annotationApiTests({ getService }: FtrProviderContext) { - const supertestWrite = getService('supertestAsApmAnnotationsWriteUser'); - - function request({ method, url, data }: { method: string; url: string; data?: JsonObject }) { - switch (method.toLowerCase()) { - case 'post': - return supertestWrite.post(url).send(data).set('kbn-xsrf', 'foo'); - - default: - throw new Error(`Unsupported method ${method}`); - } - } - - describe('APM annotations with a basic license', () => { - describe('when creating an annotation', () => { - it('fails with a 403 forbidden', async () => { - const response = await request({ - url: '/api/apm/services/opbeans-java/annotation', - method: 'POST', - data: { - '@timestamp': new Date().toISOString(), - message: 'New deployment', - tags: ['foo'], - service: { - version: '1.1', - environment: 'production', - }, - }, - }); - - expect(response.status).to.be(403); - expect(response.body.message).to.be( - 'Annotations require at least a gold license or a trial license.' - ); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/services/throughput.ts b/x-pack/test/apm_api_integration/basic/tests/services/throughput.ts deleted file mode 100644 index 07a1442d751b4..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/services/throughput.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import expect from '@kbn/expect'; -import qs from 'querystring'; -import { first, last } from 'lodash'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - const archiveName = 'apm_8.0.0'; - const metadata = archives_metadata[archiveName]; - - describe('Throughput', () => { - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-java/throughput?${qs.stringify({ - start: metadata.start, - end: metadata.end, - uiFilters: encodeURIComponent('{}'), - transactionType: 'request', - })}` - ); - expect(response.status).to.be(200); - expect(response.body.throughput.length).to.be(0); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - describe('returns the service throughput', () => { - let throughputResponse: { - throughput: Array<{ x: number; y: number | null }>; - }; - before(async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-java/throughput?${qs.stringify({ - start: metadata.start, - end: metadata.end, - uiFilters: encodeURIComponent('{}'), - transactionType: 'request', - })}` - ); - throughputResponse = response.body; - }); - - it('returns some data', () => { - expect(throughputResponse.throughput.length).to.be.greaterThan(0); - - const nonNullDataPoints = throughputResponse.throughput.filter(({ y }) => y !== null); - - expect(nonNullDataPoints.length).to.be.greaterThan(0); - }); - - it('has the correct start date', () => { - expectSnapshot( - new Date(first(throughputResponse.throughput)?.x ?? NaN).toISOString() - ).toMatchInline(`"2020-12-08T13:57:30.000Z"`); - }); - - it('has the correct end date', () => { - expectSnapshot( - new Date(last(throughputResponse.throughput)?.x ?? NaN).toISOString() - ).toMatchInline(`"2020-12-08T14:27:30.000Z"`); - }); - - it('has the correct number of buckets', () => { - expectSnapshot(throughputResponse.throughput.length).toMatchInline(`61`); - }); - - it('has the correct throughput', () => { - expectSnapshot(throughputResponse.throughput).toMatch(); - }); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/services/top_services.ts b/x-pack/test/apm_api_integration/basic/tests/services/top_services.ts deleted file mode 100644 index 98bfe84cf56ee..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/services/top_services.ts +++ /dev/null @@ -1,259 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { isEmpty, pick, sortBy } from 'lodash'; -import { APIReturnType } from '../../../../../plugins/apm/public/services/rest/createCallApmApi'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - const archiveName = 'apm_8.0.0'; - - const range = archives_metadata[archiveName]; - - // url parameters - const start = encodeURIComponent(range.start); - const end = encodeURIComponent(range.end); - - const uiFilters = encodeURIComponent(JSON.stringify({})); - - describe('APM Services Overview', () => { - describe('when data is not loaded ', () => { - it('handles the empty state', async () => { - const response = await supertest.get( - `/api/apm/services?start=${start}&end=${end}&uiFilters=${uiFilters}` - ); - - expect(response.status).to.be(200); - expect(response.body.hasHistoricalData).to.be(false); - expect(response.body.hasLegacyData).to.be(false); - expect(response.body.items.length).to.be(0); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - describe('and fetching a list of services', () => { - let response: { - status: number; - body: APIReturnType<'GET /api/apm/services'>; - }; - - let sortedItems: typeof response.body.items; - - before(async () => { - response = await supertest.get( - `/api/apm/services?start=${start}&end=${end}&uiFilters=${uiFilters}` - ); - sortedItems = sortBy(response.body.items, 'serviceName'); - }); - - it('the response is successful', () => { - expect(response.status).to.eql(200); - }); - - it('returns hasHistoricalData: true', () => { - expect(response.body.hasHistoricalData).to.be(true); - }); - - it('returns hasLegacyData: false', () => { - expect(response.body.hasLegacyData).to.be(false); - }); - - it('returns the correct service names', () => { - expectSnapshot(sortedItems.map((item) => item.serviceName)).toMatchInline(` - Array [ - "kibana", - "kibana-frontend", - "opbeans-dotnet", - "opbeans-go", - "opbeans-java", - "opbeans-node", - "opbeans-python", - "opbeans-ruby", - "opbeans-rum", - ] - `); - }); - - it('returns the correct metrics averages', () => { - expectSnapshot( - sortedItems.map((item) => - pick( - item, - 'transactionErrorRate.value', - 'avgResponseTime.value', - 'transactionsPerMinute.value' - ) - ) - ).toMatchInline(` - Array [ - Object { - "avgResponseTime": Object { - "value": 420419.34550767, - }, - "transactionErrorRate": Object { - "value": 0, - }, - "transactionsPerMinute": Object { - "value": 45.6333333333333, - }, - }, - Object { - "avgResponseTime": Object { - "value": 2382833.33333333, - }, - "transactionErrorRate": Object { - "value": null, - }, - "transactionsPerMinute": Object { - "value": 0.2, - }, - }, - Object { - "avgResponseTime": Object { - "value": 631521.83908046, - }, - "transactionErrorRate": Object { - "value": 0.0229885057471264, - }, - "transactionsPerMinute": Object { - "value": 2.9, - }, - }, - Object { - "avgResponseTime": Object { - "value": 27946.1484375, - }, - "transactionErrorRate": Object { - "value": 0.015625, - }, - "transactionsPerMinute": Object { - "value": 4.26666666666667, - }, - }, - Object { - "avgResponseTime": Object { - "value": 237339.813333333, - }, - "transactionErrorRate": Object { - "value": 0.16, - }, - "transactionsPerMinute": Object { - "value": 2.5, - }, - }, - Object { - "avgResponseTime": Object { - "value": 24920.1052631579, - }, - "transactionErrorRate": Object { - "value": 0.0210526315789474, - }, - "transactionsPerMinute": Object { - "value": 3.16666666666667, - }, - }, - Object { - "avgResponseTime": Object { - "value": 29542.6607142857, - }, - "transactionErrorRate": Object { - "value": 0.0357142857142857, - }, - "transactionsPerMinute": Object { - "value": 1.86666666666667, - }, - }, - Object { - "avgResponseTime": Object { - "value": 70518.9328358209, - }, - "transactionErrorRate": Object { - "value": 0.0373134328358209, - }, - "transactionsPerMinute": Object { - "value": 4.46666666666667, - }, - }, - Object { - "avgResponseTime": Object { - "value": 2319812.5, - }, - "transactionErrorRate": Object { - "value": null, - }, - "transactionsPerMinute": Object { - "value": 0.533333333333333, - }, - }, - ] - `); - }); - - it('returns environments', () => { - expectSnapshot(sortedItems.map((item) => item.environments ?? [])).toMatchInline(` - Array [ - Array [ - "production", - ], - Array [ - "production", - ], - Array [ - "production", - ], - Array [ - "testing", - ], - Array [ - "production", - ], - Array [ - "testing", - ], - Array [], - Array [], - Array [ - "testing", - ], - ] - `); - }); - - it(`RUM services don't report any transaction error rates`, () => { - // RUM transactions don't have event.outcome set, - // so they should not have an error rate - - const rumServices = sortedItems.filter((item) => item.agentName === 'rum-js'); - - expect(rumServices.length).to.be.greaterThan(0); - - expect(rumServices.every((item) => isEmpty(item.transactionErrorRate?.value))); - }); - - it('non-RUM services all report transaction error rates', () => { - const nonRumServices = sortedItems.filter((item) => item.agentName !== 'rum-js'); - - expect( - nonRumServices.every((item) => { - return ( - typeof item.transactionErrorRate?.value === 'number' && - item.transactionErrorRate.timeseries.length > 0 - ); - }) - ).to.be(true); - }); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/settings/agent_configuration.ts b/x-pack/test/apm_api_integration/basic/tests/settings/agent_configuration.ts deleted file mode 100644 index 1817c7c4511fa..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/settings/agent_configuration.ts +++ /dev/null @@ -1,489 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { omit, orderBy } from 'lodash'; -import { AgentConfigurationIntake } from '../../../../../plugins/apm/common/agent_configuration/configuration_types'; -import { AgentConfigSearchParams } from '../../../../../plugins/apm/server/routes/settings/agent_configuration'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function agentConfigurationTests({ getService }: FtrProviderContext) { - const supertestRead = getService('supertestAsApmReadUser'); - const supertestWrite = getService('supertestAsApmWriteUser'); - const log = getService('log'); - const esArchiver = getService('esArchiver'); - - const archiveName = 'apm_8.0.0'; - - function getServices() { - return supertestRead - .get(`/api/apm/settings/agent-configuration/services`) - .set('kbn-xsrf', 'foo'); - } - - function getEnvironments(serviceName: string) { - return supertestRead - .get(`/api/apm/settings/agent-configuration/environments?serviceName=${serviceName}`) - .set('kbn-xsrf', 'foo'); - } - - function getAgentName(serviceName: string) { - return supertestRead - .get(`/api/apm/settings/agent-configuration/agent_name?serviceName=${serviceName}`) - .set('kbn-xsrf', 'foo'); - } - - function searchConfigurations(configuration: AgentConfigSearchParams) { - return supertestRead - .post(`/api/apm/settings/agent-configuration/search`) - .send(configuration) - .set('kbn-xsrf', 'foo'); - } - - function getAllConfigurations() { - return supertestRead.get(`/api/apm/settings/agent-configuration`).set('kbn-xsrf', 'foo'); - } - - async function createConfiguration(config: AgentConfigurationIntake, { user = 'write' } = {}) { - log.debug('creating configuration', config.service); - const supertestClient = user === 'read' ? supertestRead : supertestWrite; - - const res = await supertestClient - .put(`/api/apm/settings/agent-configuration`) - .send(config) - .set('kbn-xsrf', 'foo'); - - throwOnError(res); - - return res; - } - - async function updateConfiguration(config: AgentConfigurationIntake, { user = 'write' } = {}) { - log.debug('updating configuration', config.service); - const supertestClient = user === 'read' ? supertestRead : supertestWrite; - - const res = await supertestClient - .put(`/api/apm/settings/agent-configuration?overwrite=true`) - .send(config) - .set('kbn-xsrf', 'foo'); - - throwOnError(res); - - return res; - } - - async function deleteConfiguration( - { service }: AgentConfigurationIntake, - { user = 'write' } = {} - ) { - log.debug('deleting configuration', service); - const supertestClient = user === 'read' ? supertestRead : supertestWrite; - - const res = await supertestClient - .delete(`/api/apm/settings/agent-configuration`) - .send({ service }) - .set('kbn-xsrf', 'foo'); - - throwOnError(res); - - return res; - } - - function throwOnError(res: any) { - const { statusCode, req, body } = res; - if (statusCode !== 200) { - const e = new Error(` - Endpoint: ${req.method} ${req.path} - Service: ${JSON.stringify(res.request._data.service)} - Status code: ${statusCode} - Response: ${body.message}`); - - // @ts-ignore - e.res = res; - - throw e; - } - } - - describe('agent configuration', () => { - describe('when no data is loaded', () => { - it('handles the empty state for services', async () => { - const { body } = await getServices(); - expect(body).to.eql(['ALL_OPTION_VALUE']); - }); - - it('handles the empty state for environments', async () => { - const { body } = await getEnvironments('myservice'); - expect(body).to.eql([{ name: 'ALL_OPTION_VALUE', alreadyConfigured: false }]); - }); - - it('handles the empty state for agent names', async () => { - const { body } = await getAgentName('myservice'); - expect(body).to.eql({}); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - it('returns all services', async () => { - const { body } = await getServices(); - expectSnapshot(body).toMatchInline(` - Array [ - "ALL_OPTION_VALUE", - "kibana", - "kibana-frontend", - "opbeans-dotnet", - "opbeans-go", - "opbeans-java", - "opbeans-node", - "opbeans-python", - "opbeans-ruby", - "opbeans-rum", - ] - `); - }); - - it('returns the environments, all unconfigured', async () => { - const { body } = await getEnvironments('opbeans-node'); - - expect(body.map((item: { name: string }) => item.name)).to.contain('ALL_OPTION_VALUE'); - - expect( - body.every((item: { alreadyConfigured: boolean }) => item.alreadyConfigured === false) - ).to.be(true); - - expectSnapshot(body).toMatchInline(` - Array [ - Object { - "alreadyConfigured": false, - "name": "ALL_OPTION_VALUE", - }, - Object { - "alreadyConfigured": false, - "name": "testing", - }, - ] - `); - }); - - it('returns the agent names', async () => { - const { body } = await getAgentName('opbeans-node'); - expect(body).to.eql({ agentName: 'nodejs' }); - }); - }); - - describe('as a read-only user', () => { - const newConfig = { service: {}, settings: { transaction_sample_rate: '0.55' } }; - it('throws when attempting to create config', async () => { - try { - await createConfiguration(newConfig, { user: 'read' }); - - // ensure that `createConfiguration` throws - expect(true).to.be(false); - } catch (e) { - expect(e.res.statusCode).to.be(403); - } - }); - - describe('when a configuration already exists', () => { - before(async () => createConfiguration(newConfig)); - after(async () => deleteConfiguration(newConfig)); - - it('throws when attempting to update config', async () => { - try { - await updateConfiguration(newConfig, { user: 'read' }); - - // ensure that `updateConfiguration` throws - expect(true).to.be(false); - } catch (e) { - expect(e.res.statusCode).to.be(403); - } - }); - - it('throws when attempting to delete config', async () => { - try { - await deleteConfiguration(newConfig, { user: 'read' }); - - // ensure that `deleteConfiguration` throws - expect(true).to.be(false); - } catch (e) { - expect(e.res.statusCode).to.be(403); - } - }); - }); - }); - - describe('when creating one configuration', () => { - const newConfig = { - service: {}, - settings: { transaction_sample_rate: '0.55' }, - }; - - const searchParams = { - service: { name: 'myservice', environment: 'development' }, - etag: '7312bdcc34999629a3d39df24ed9b2a7553c0c39', - }; - - it('can create and delete config', async () => { - // assert that config does not exist - const res1 = await searchConfigurations(searchParams); - expect(res1.status).to.equal(404); - - // assert that config was created - await createConfiguration(newConfig); - const res2 = await searchConfigurations(searchParams); - expect(res2.status).to.equal(200); - - // assert that config was deleted - await deleteConfiguration(newConfig); - const res3 = await searchConfigurations(searchParams); - expect(res3.status).to.equal(404); - }); - - describe('when a configuration exists', () => { - before(async () => createConfiguration(newConfig)); - after(async () => deleteConfiguration(newConfig)); - - it('can find the config', async () => { - const { status, body } = await searchConfigurations(searchParams); - expect(status).to.equal(200); - expect(body._source.service).to.eql({}); - expect(body._source.settings).to.eql({ transaction_sample_rate: '0.55' }); - }); - - it('can list the config', async () => { - const { status, body } = await getAllConfigurations(); - expect(status).to.equal(200); - expect(omitTimestamp(body)).to.eql([ - { - service: {}, - settings: { transaction_sample_rate: '0.55' }, - applied_by_agent: false, - etag: 'eb88a8997666cc4b33745ef355a1bbd7c4782f2d', - }, - ]); - }); - - it('can update the config', async () => { - await updateConfiguration({ service: {}, settings: { transaction_sample_rate: '0.85' } }); - const { status, body } = await searchConfigurations(searchParams); - expect(status).to.equal(200); - expect(body._source.service).to.eql({}); - expect(body._source.settings).to.eql({ transaction_sample_rate: '0.85' }); - }); - }); - }); - - describe('when creating multiple configurations', () => { - const configs = [ - { - service: {}, - settings: { transaction_sample_rate: '0.1' }, - }, - { - service: { name: 'my_service' }, - settings: { transaction_sample_rate: '0.2' }, - }, - { - service: { name: 'my_service', environment: 'development' }, - settings: { transaction_sample_rate: '0.3' }, - }, - { - service: { environment: 'production' }, - settings: { transaction_sample_rate: '0.4' }, - }, - { - service: { environment: 'development' }, - settings: { transaction_sample_rate: '0.5' }, - }, - ]; - - before(async () => { - await Promise.all(configs.map((config) => createConfiguration(config))); - }); - - after(async () => { - await Promise.all(configs.map((config) => deleteConfiguration(config))); - }); - - const agentsRequests = [ - { - service: { name: 'non_existing_service', environment: 'non_existing_env' }, - expectedSettings: { transaction_sample_rate: '0.1' }, - }, - { - service: { name: 'my_service', environment: 'non_existing_env' }, - expectedSettings: { transaction_sample_rate: '0.2' }, - }, - { - service: { name: 'my_service', environment: 'production' }, - expectedSettings: { transaction_sample_rate: '0.2' }, - }, - { - service: { name: 'my_service', environment: 'development' }, - expectedSettings: { transaction_sample_rate: '0.3' }, - }, - { - service: { name: 'non_existing_service', environment: 'production' }, - expectedSettings: { transaction_sample_rate: '0.4' }, - }, - { - service: { name: 'non_existing_service', environment: 'development' }, - expectedSettings: { transaction_sample_rate: '0.5' }, - }, - ]; - - it('can list all configs', async () => { - const { status, body } = await getAllConfigurations(); - expect(status).to.equal(200); - expect(orderBy(omitTimestamp(body), ['settings.transaction_sample_rate'])).to.eql([ - { - service: {}, - settings: { transaction_sample_rate: '0.1' }, - applied_by_agent: false, - etag: '0758cb18817de60cca29e07480d472694239c4c3', - }, - { - service: { name: 'my_service' }, - settings: { transaction_sample_rate: '0.2' }, - applied_by_agent: false, - etag: 'e04737637056fdf1763bf0ef0d3fcb86e89ae5fc', - }, - { - service: { name: 'my_service', environment: 'development' }, - settings: { transaction_sample_rate: '0.3' }, - applied_by_agent: false, - etag: 'af4dac62621b6762e6281481d1f7523af1124120', - }, - { - service: { environment: 'production' }, - settings: { transaction_sample_rate: '0.4' }, - applied_by_agent: false, - etag: '8d1bf8e6b778b60af351117e2cf53fb1ee570068', - }, - { - service: { environment: 'development' }, - settings: { transaction_sample_rate: '0.5' }, - applied_by_agent: false, - etag: '4ce40da57e3c71daca704121c784b911ec05ae81', - }, - ]); - }); - - for (const agentRequest of agentsRequests) { - it(`${agentRequest.service.name} / ${agentRequest.service.environment}`, async () => { - const { status, body } = await searchConfigurations({ - service: agentRequest.service, - etag: 'abc', - }); - - expect(status).to.equal(200); - expect(body._source.settings).to.eql(agentRequest.expectedSettings); - }); - } - }); - - describe('when an agent retrieves a configuration', () => { - const config = { - service: { name: 'myservice', environment: 'development' }, - settings: { transaction_sample_rate: '0.9' }, - }; - const configProduction = { - service: { name: 'myservice', environment: 'production' }, - settings: { transaction_sample_rate: '0.9' }, - }; - let etag: string; - - before(async () => { - log.debug('creating agent configuration'); - await createConfiguration(config); - await createConfiguration(configProduction); - }); - - after(async () => { - await deleteConfiguration(config); - await deleteConfiguration(configProduction); - }); - - it(`should have 'applied_by_agent=false' before supplying etag`, async () => { - const res1 = await searchConfigurations({ - service: { name: 'myservice', environment: 'development' }, - }); - - etag = res1.body._source.etag; - - const res2 = await searchConfigurations({ - service: { name: 'myservice', environment: 'development' }, - etag, - }); - - expect(res1.body._source.applied_by_agent).to.be(false); - expect(res2.body._source.applied_by_agent).to.be(false); - }); - - it(`should have 'applied_by_agent=true' after supplying etag`, async () => { - await searchConfigurations({ - service: { name: 'myservice', environment: 'development' }, - etag, - }); - - async function hasBeenAppliedByAgent() { - const { body } = await searchConfigurations({ - service: { name: 'myservice', environment: 'development' }, - }); - - return body._source.applied_by_agent; - } - - // wait until `applied_by_agent` has been updated in elasticsearch - expect(await waitFor(hasBeenAppliedByAgent)).to.be(true); - }); - it(`should have 'applied_by_agent=false' before marking as applied`, async () => { - const res1 = await searchConfigurations({ - service: { name: 'myservice', environment: 'production' }, - }); - - expect(res1.body._source.applied_by_agent).to.be(false); - }); - it(`should have 'applied_by_agent=true' when 'mark_as_applied_by_agent' attribute is true`, async () => { - await searchConfigurations({ - service: { name: 'myservice', environment: 'production' }, - mark_as_applied_by_agent: true, - }); - - async function hasBeenAppliedByAgent() { - const { body } = await searchConfigurations({ - service: { name: 'myservice', environment: 'production' }, - }); - - return body._source.applied_by_agent; - } - - // wait until `applied_by_agent` has been updated in elasticsearch - expect(await waitFor(hasBeenAppliedByAgent)).to.be(true); - }); - }); - }); -} - -async function waitFor(cb: () => Promise, retries = 50): Promise { - if (retries === 0) { - throw new Error(`Maximum number of retries reached`); - } - - const res = await cb(); - if (!res) { - await new Promise((resolve) => setTimeout(resolve, 100)); - return waitFor(cb, retries - 1); - } - return res; -} - -function omitTimestamp(configs: AgentConfigurationIntake[]) { - return configs.map((config: AgentConfigurationIntake) => omit(config, '@timestamp')); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/no_access_user.ts b/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/no_access_user.ts deleted file mode 100644 index 5630bd195b6cd..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/no_access_user.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; - -export default function apiTest({ getService }: FtrProviderContext) { - const noAccessUser = getService('supertestAsNoAccessUser'); - - function getAnomalyDetectionJobs() { - return noAccessUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); - } - - function createAnomalyDetectionJobs(environments: string[]) { - return noAccessUser - .post(`/api/apm/settings/anomaly-detection/jobs`) - .send({ environments }) - .set('kbn-xsrf', 'foo'); - } - - describe('when user does not have read access to ML', () => { - describe('when calling the endpoint for listing jobs', () => { - it('returns an error because the user does not have access', async () => { - const { body } = await getAnomalyDetectionJobs(); - - expect(body.statusCode).to.be(403); - expect(body.error).to.be('Forbidden'); - }); - }); - - describe('when calling create endpoint', () => { - it('returns an error because the user does not have access', async () => { - const { body } = await createAnomalyDetectionJobs(['production', 'staging']); - - expect(body.statusCode).to.be(403); - expect(body.error).to.be('Forbidden'); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/read_user.ts b/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/read_user.ts deleted file mode 100644 index 30e097e791eaa..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/read_user.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; - -export default function apiTest({ getService }: FtrProviderContext) { - const apmReadUser = getService('supertestAsApmReadUser'); - - function getAnomalyDetectionJobs() { - return apmReadUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); - } - - function createAnomalyDetectionJobs(environments: string[]) { - return apmReadUser - .post(`/api/apm/settings/anomaly-detection/jobs`) - .send({ environments }) - .set('kbn-xsrf', 'foo'); - } - - describe('when user has read access to ML', () => { - describe('when calling the endpoint for listing jobs', () => { - it('returns an error because the user is on basic license', async () => { - const { body } = await getAnomalyDetectionJobs(); - - expect(body.statusCode).to.be(403); - expect(body.error).to.be('Forbidden'); - - expectSnapshot(body.message).toMatchInline( - `"To use anomaly detection, you must be subscribed to an Elastic Platinum license. With it, you'll be able to monitor your services with the aid of machine learning."` - ); - }); - }); - - describe('when calling create endpoint', () => { - it('returns an error because the user does not have access', async () => { - const { body } = await createAnomalyDetectionJobs(['production', 'staging']); - expect(body.statusCode).to.be(403); - expect(body.error).to.be('Forbidden'); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/write_user.ts b/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/write_user.ts deleted file mode 100644 index 15659229a1917..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/settings/anomaly_detection/write_user.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; - -export default function apiTest({ getService }: FtrProviderContext) { - const apmWriteUser = getService('supertestAsApmWriteUser'); - - function getAnomalyDetectionJobs() { - return apmWriteUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); - } - - function createAnomalyDetectionJobs(environments: string[]) { - return apmWriteUser - .post(`/api/apm/settings/anomaly-detection/jobs`) - .send({ environments }) - .set('kbn-xsrf', 'foo'); - } - - describe('when user has write access to ML', () => { - describe('when calling the endpoint for listing jobs', () => { - it('returns an error because the user is on basic license', async () => { - const { body } = await getAnomalyDetectionJobs(); - - expect(body.statusCode).to.be(403); - expect(body.error).to.be('Forbidden'); - expectSnapshot(body.message).toMatchInline( - `"To use anomaly detection, you must be subscribed to an Elastic Platinum license. With it, you'll be able to monitor your services with the aid of machine learning."` - ); - }); - }); - - describe('when calling create endpoint', () => { - it('returns an error because the user is on basic license', async () => { - const { body } = await createAnomalyDetectionJobs(['production', 'staging']); - - expect(body.statusCode).to.be(403); - expect(body.error).to.be('Forbidden'); - - expectSnapshot(body.message).toMatchInline( - `"To use anomaly detection, you must be subscribed to an Elastic Platinum license. With it, you'll be able to monitor your services with the aid of machine learning."` - ); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/settings/custom_link.ts b/x-pack/test/apm_api_integration/basic/tests/settings/custom_link.ts deleted file mode 100644 index 8ac5566fc2c49..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/settings/custom_link.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import expect from '@kbn/expect'; -import { CustomLink } from '../../../../../plugins/apm/common/custom_link/custom_link_types'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function customLinksTests({ getService }: FtrProviderContext) { - const supertestWrite = getService('supertestAsApmWriteUser'); - - describe('custom links', () => { - it('is only be available to users with Gold license (or higher)', async () => { - const customLink = { - url: 'https://elastic.co', - label: 'with filters', - filters: [ - { key: 'service.name', value: 'baz' }, - { key: 'transaction.type', value: 'qux' }, - ], - } as CustomLink; - const response = await supertestWrite - .post(`/api/apm/settings/custom_links`) - .send(customLink) - .set('kbn-xsrf', 'foo'); - - expect(response.status).to.be(403); - - expectSnapshot(response.body.message).toMatchInline( - `"To create custom links, you must be subscribed to an Elastic Gold license or above. With it, you'll have the ability to create custom links to improve your workflow when analyzing your services."` - ); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/breakdown.ts b/x-pack/test/apm_api_integration/basic/tests/transactions/breakdown.ts deleted file mode 100644 index 947defca05d94..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/breakdown.ts +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import expect from '@kbn/expect'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - const archiveName = 'apm_8.0.0'; - const metadata = archives_metadata[archiveName]; - - const start = encodeURIComponent(metadata.start); - const end = encodeURIComponent(metadata.end); - const transactionType = 'request'; - const transactionName = 'GET /api'; - const uiFilters = encodeURIComponent(JSON.stringify({})); - - describe('Breakdown', () => { - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}` - ); - expect(response.status).to.be(200); - expect(response.body).to.eql({ timeseries: [] }); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - it('returns the transaction breakdown for a service', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}` - ); - - expect(response.status).to.be(200); - expectSnapshot(response.body).toMatch(); - }); - it('returns the transaction breakdown for a transaction group', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}&transactionName=${transactionName}` - ); - - expect(response.status).to.be(200); - - const { timeseries } = response.body; - - const numberOfSeries = timeseries.length; - - expectSnapshot(numberOfSeries).toMatchInline(`1`); - - const { title, color, type, data, hideLegend, legendValue } = timeseries[0]; - - const nonNullDataPoints = data.filter((y: number | null) => y !== null); - - expectSnapshot(nonNullDataPoints.length).toMatchInline(`61`); - - expectSnapshot( - data.slice(0, 5).map(({ x, y }: { x: number; y: number | null }) => { - return { - x: new Date(x ?? NaN).toISOString(), - y, - }; - }) - ).toMatchInline(` - Array [ - Object { - "x": "2020-12-08T13:57:30.000Z", - "y": null, - }, - Object { - "x": "2020-12-08T13:58:00.000Z", - "y": null, - }, - Object { - "x": "2020-12-08T13:58:30.000Z", - "y": 1, - }, - Object { - "x": "2020-12-08T13:59:00.000Z", - "y": 1, - }, - Object { - "x": "2020-12-08T13:59:30.000Z", - "y": null, - }, - ] - `); - - expectSnapshot(title).toMatchInline(`"app"`); - expectSnapshot(color).toMatchInline(`"#54b399"`); - expectSnapshot(type).toMatchInline(`"areaStacked"`); - expectSnapshot(hideLegend).toMatchInline(`false`); - expectSnapshot(legendValue).toMatchInline(`"100%"`); - - expectSnapshot(data).toMatch(); - }); - it('returns the transaction breakdown sorted by name', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}` - ); - - expect(response.status).to.be(200); - expectSnapshot(response.body.timeseries.map((serie: { title: string }) => serie.title)) - .toMatchInline(` - Array [ - "app", - "http", - "postgresql", - "redis", - ] - `); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/latency.ts b/x-pack/test/apm_api_integration/basic/tests/transactions/latency.ts deleted file mode 100644 index 3088f4fd481d7..0000000000000 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/latency.ts +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import expect from '@kbn/expect'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - const archiveName = 'apm_8.0.0'; - const metadata = archives_metadata[archiveName]; - - // url parameters - const start = encodeURIComponent(metadata.start); - const end = encodeURIComponent(metadata.end); - const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'testing' })); - - describe('Latency', () => { - describe('when data is not loaded ', () => { - it('returns 400 when latencyAggregationType is not informed', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request` - ); - - expect(response.status).to.be(400); - }); - - it('returns 400 when transactionType is not informed', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&latencyAggregationType=avg` - ); - - expect(response.status).to.be(400); - }); - - it('handles the empty state', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&latencyAggregationType=avg&transactionType=request` - ); - - expect(response.status).to.be(200); - - expect(response.body.overallAvgDuration).to.be(null); - expect(response.body.latencyTimeseries.length).to.be(0); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - let response: PromiseReturnType; - - describe('average latency type', () => { - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=avg` - ); - }); - - it('returns average duration and timeseries', async () => { - expect(response.status).to.be(200); - expect(response.body.overallAvgDuration).not.to.be(null); - expect(response.body.latencyTimeseries.length).to.be.eql(61); - }); - }); - - describe('95th percentile latency type', () => { - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=p95` - ); - }); - - it('returns average duration and timeseries', async () => { - expect(response.status).to.be(200); - expect(response.body.overallAvgDuration).not.to.be(null); - expect(response.body.latencyTimeseries.length).to.be.eql(61); - }); - }); - - describe('99th percentile latency type', () => { - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=p99` - ); - }); - - it('returns average duration and timeseries', async () => { - expect(response.status).to.be(200); - expect(response.body.overallAvgDuration).not.to.be(null); - expect(response.body.latencyTimeseries.length).to.be.eql(61); - }); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/common/config.ts b/x-pack/test/apm_api_integration/common/config.ts index f94bd6bd3be6f..08333de15ec6d 100644 --- a/x-pack/test/apm_api_integration/common/config.ts +++ b/x-pack/test/apm_api_integration/common/config.ts @@ -11,11 +11,12 @@ import path from 'path'; import { InheritedFtrProviderContext, InheritedServices } from './ftr_provider_context'; import { PromiseReturnType } from '../../../plugins/observability/typings/common'; import { createApmUser, APM_TEST_PASSWORD, ApmUser } from './authentication'; +import { APMFtrConfigName } from '../configs'; +import { registry } from './registry'; -interface Settings { +interface Config { + name: APMFtrConfigName; license: 'basic' | 'trial'; - testFiles: string[]; - name: string; } const supertestAsApmUser = (kibanaServer: UrlObject, apmUser: ApmUser) => async ( @@ -34,8 +35,8 @@ const supertestAsApmUser = (kibanaServer: UrlObject, apmUser: ApmUser) => async return supertestAsPromised(url); }; -export function createTestConfig(settings: Settings) { - const { testFiles, license, name } = settings; +export function createTestConfig(config: Config) { + const { license, name } = config; return async ({ readConfigFile }: FtrConfigProviderContext) => { const xPackAPITestsConfig = await readConfigFile( @@ -47,8 +48,10 @@ export function createTestConfig(settings: Settings) { const supertestAsApmReadUser = supertestAsApmUser(servers.kibana, ApmUser.apmReadUser); + registry.init(config.name); + return { - testFiles, + testFiles: [require.resolve('../tests')], servers, esArchiver: { directory: path.resolve(__dirname, './fixtures/es_archiver'), @@ -69,7 +72,7 @@ export function createTestConfig(settings: Settings) { ), }, junit: { - reportName: name, + reportName: `APM API Integration tests (${name})`, }, esTestCluster: { ...xPackAPITestsConfig.get('esTestCluster'), diff --git a/x-pack/test/apm_api_integration/common/registry.ts b/x-pack/test/apm_api_integration/common/registry.ts new file mode 100644 index 0000000000000..ba43db2fd5835 --- /dev/null +++ b/x-pack/test/apm_api_integration/common/registry.ts @@ -0,0 +1,174 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { castArray, groupBy } from 'lodash'; +import callsites from 'callsites'; +import { maybe } from '../../../plugins/apm/common/utils/maybe'; +import { joinByKey } from '../../../plugins/apm/common/utils/join_by_key'; +import { APMFtrConfigName } from '../configs'; +import { FtrProviderContext } from './ftr_provider_context'; + +type ArchiveName = + | 'apm_8.0.0' + | '8.0.0' + | 'metrics_8.0.0' + | 'ml_8.0.0' + | 'observability_overview' + | 'rum_8.0.0' + | 'rum_test_data'; + +interface RunCondition { + config: APMFtrConfigName; + archives: ArchiveName[]; +} + +const callbacks: Array< + RunCondition & { + runs: Array<{ + cb: () => void; + }>; + } +> = []; + +let configName: APMFtrConfigName | undefined; + +let running: boolean = false; + +function when( + title: string, + conditions: RunCondition | RunCondition[], + callback: (condition: RunCondition) => void, + skip?: boolean +) { + const allConditions = castArray(conditions); + + if (!allConditions.length) { + throw new Error('At least one condition should be defined'); + } + + if (running) { + throw new Error("Can't add tests when running"); + } + + const frame = maybe(callsites()[1]); + + const file = frame?.getFileName(); + + if (!file) { + throw new Error('Could not infer file for suite'); + } + + allConditions.forEach((matchedCondition) => { + callbacks.push({ + ...matchedCondition, + runs: [ + { + cb: () => { + const suite: ReturnType = (skip ? describe.skip : describe)( + title, + () => { + callback(matchedCondition); + } + ) as any; + + suite.file = file; + suite.eachTest((test) => { + test.file = file; + }); + }, + }, + ], + }); + }); +} + +when.skip = ( + title: string, + conditions: RunCondition | RunCondition[], + callback: (condition: RunCondition) => void +) => { + when(title, conditions, callback, true); +}; + +export const registry = { + init: (config: APMFtrConfigName) => { + configName = config; + callbacks.length = 0; + running = false; + }, + when, + run: (context: FtrProviderContext) => { + if (!configName) { + throw new Error(`registry was not init() before running`); + } + running = true; + const esArchiver = context.getService('esArchiver'); + const logger = context.getService('log'); + const logWithTimer = () => { + const start = process.hrtime(); + + return (message: string) => { + const diff = process.hrtime(start); + const time = `${Math.round(diff[0] * 1000 + diff[1] / 1e6)}ms`; + logger.info(`(${time}) ${message}`); + }; + }; + + const groups = joinByKey(callbacks, ['config', 'archives'], (a, b) => ({ + ...a, + ...b, + runs: a.runs.concat(b.runs), + })); + + callbacks.length = 0; + + const byConfig = groupBy(groups, 'config'); + + Object.keys(byConfig).forEach((config) => { + const groupsForConfig = byConfig[config]; + // register suites for other configs, but skip them so tests are marked as such + // and their snapshots are not marked as obsolete + (config === configName ? describe : describe.skip)(config, () => { + groupsForConfig.forEach((group) => { + const { runs, ...condition } = group; + + const runBefore = async () => { + const log = logWithTimer(); + for (const archiveName of condition.archives) { + log(`Loading ${archiveName}`); + await esArchiver.load(archiveName); + } + if (condition.archives.length) { + log('Loaded all archives'); + } + }; + + const runAfter = async () => { + const log = logWithTimer(); + for (const archiveName of condition.archives) { + log(`Unloading ${archiveName}`); + await esArchiver.unload(archiveName); + } + if (condition.archives.length) { + log('Unloaded all archives'); + } + }; + + describe(condition.archives.join(',') || 'no data', () => { + before(runBefore); + + runs.forEach((run) => { + run.cb(); + }); + + after(runAfter); + }); + }); + }); + }); + + running = false; + }, +}; diff --git a/x-pack/test/apm_api_integration/configs/index.ts b/x-pack/test/apm_api_integration/configs/index.ts new file mode 100644 index 0000000000000..4bda5419c8729 --- /dev/null +++ b/x-pack/test/apm_api_integration/configs/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 { mapValues } from 'lodash'; +import { createTestConfig } from '../common/config'; + +const apmFtrConfigs = { + basic: { + license: 'basic' as const, + }, + trial: { + license: 'trial' as const, + }, +}; + +export type APMFtrConfigName = keyof typeof apmFtrConfigs; + +export const configs = mapValues(apmFtrConfigs, (value, key) => { + return createTestConfig({ + name: key as APMFtrConfigName, + ...value, + }); +}); diff --git a/x-pack/test/apm_api_integration/tests/alerts/chart_preview.ts b/x-pack/test/apm_api_integration/tests/alerts/chart_preview.ts new file mode 100644 index 0000000000000..2b14e3b8a4a75 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/alerts/chart_preview.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { format } from 'url'; +import archives from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const archiveName = 'apm_8.0.0'; + const { end } = archives[archiveName]; + const start = new Date(Date.parse(end) - 600000).toISOString(); + + const apis = [ + { + pathname: '/api/apm/alerts/chart_preview/transaction_error_rate', + params: { transactionType: 'request' }, + }, + { pathname: '/api/apm/alerts/chart_preview/transaction_error_count', params: {} }, + { + pathname: '/api/apm/alerts/chart_preview/transaction_duration', + params: { transactionType: 'request' }, + }, + ]; + + apis.forEach((api) => { + const url = format({ + pathname: api.pathname, + query: { + start, + end, + serviceName: 'opbeans-java', + ...api.params, + }, + }); + + registry.when( + `GET ${api.pathname} without data loaded`, + { config: 'basic', archives: [] }, + () => { + it('handles the empty state', async () => { + const response = await supertest.get(url); + + expect(response.status).to.be(200); + expect(response.body).to.eql([]); + }); + } + ); + + registry.when( + `GET ${api.pathname} with data loaded`, + { config: 'basic', archives: [archiveName] }, + () => { + it('returns the correct data', async () => { + const response = await supertest.get(url); + + expect(response.status).to.be(200); + expect( + response.body.some((item: { x: number; y: number | null }) => item.x && item.y) + ).to.equal(true); + }); + } + ); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/correlations/slow_transactions.ts b/x-pack/test/apm_api_integration/tests/correlations/slow_transactions.ts new file mode 100644 index 0000000000000..2439943a664ea --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/correlations/slow_transactions.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { format } from 'url'; +import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const archiveName = 'apm_8.0.0'; + const range = archives_metadata[archiveName]; + + const url = format({ + pathname: `/api/apm/correlations/slow_transactions`, + query: { + start: range.start, + end: range.end, + durationPercentile: 95, + fieldNames: + 'user.username,user.id,host.ip,user_agent.name,kubernetes.pod.uuid,url.domain,container.id,service.node.name', + }, + }); + + registry.when('without data', { config: 'trial', archives: [] }, () => { + it('handles the empty state', async () => { + const response = await supertest.get(url); + + expect(response.status).to.be(200); + expect(response.body.response).to.be(undefined); + }); + }); + + registry.when('with data and default args', { config: 'trial', archives: ['apm_8.0.0'] }, () => { + type ResponseBody = APIReturnType<'GET /api/apm/correlations/slow_transactions'>; + let response: { + status: number; + body: NonNullable; + }; + + before(async () => { + response = await supertest.get(url); + }); + + it('returns successfully', () => { + expect(response.status).to.eql(200); + }); + + it('returns significant terms', () => { + const sorted = response.body?.significantTerms?.sort(); + expectSnapshot(sorted?.map((term) => term.fieldName)).toMatchInline(` + Array [ + "user_agent.name", + "url.domain", + "host.ip", + "service.node.name", + "container.id", + "url.domain", + "user_agent.name", + ] + `); + }); + + it('returns a distribution per term', () => { + expectSnapshot(response.body?.significantTerms?.map((term) => term.distribution.length)) + .toMatchInline(` + Array [ + 11, + 11, + 11, + 11, + 11, + 11, + 11, + ] + `); + }); + + it('returns overall distribution', () => { + expectSnapshot(response.body?.overall?.distribution.length).toMatchInline(`11`); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_load_dist.snap b/x-pack/test/apm_api_integration/tests/csm/__snapshots__/page_load_dist.snap similarity index 95% rename from x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_load_dist.snap rename to x-pack/test/apm_api_integration/tests/csm/__snapshots__/page_load_dist.snap index c8681866169a5..e21069ba2f0a6 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_load_dist.snap +++ b/x-pack/test/apm_api_integration/tests/csm/__snapshots__/page_load_dist.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM specs (trial) CSM UX page load dist when there is data returns page load distribution 1`] = ` +exports[`APM API tests trial 8.0.0,rum_8.0.0 UX page load dist with data returns page load distribution 1`] = ` Object { "maxDuration": 54.46, "minDuration": 0, @@ -456,7 +456,7 @@ Object { } `; -exports[`APM specs (trial) CSM UX page load dist when there is data returns page load distribution with breakdown 1`] = ` +exports[`APM API tests trial 8.0.0,rum_8.0.0 UX page load dist with data returns page load distribution with breakdown 1`] = ` Array [ Object { "data": Array [ @@ -819,6 +819,6 @@ Array [ ] `; -exports[`APM specs (trial) CSM UX page load dist when there is no data returns empty list 1`] = `Object {}`; +exports[`APM API tests trial no data UX page load dist without data returns empty list 1`] = `Object {}`; -exports[`APM specs (trial) CSM UX page load dist when there is no data returns empty list with breakdowns 1`] = `Object {}`; +exports[`APM API tests trial no data UX page load dist without data returns empty list with breakdowns 1`] = `Object {}`; diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_views.snap b/x-pack/test/apm_api_integration/tests/csm/__snapshots__/page_views.snap similarity index 90% rename from x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_views.snap rename to x-pack/test/apm_api_integration/tests/csm/__snapshots__/page_views.snap index 76e5180ba2141..01d0d7fb6139c 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/__snapshots__/page_views.snap +++ b/x-pack/test/apm_api_integration/tests/csm/__snapshots__/page_views.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM specs (trial) CSM CSM page views when there is data returns page views 1`] = ` +exports[`APM API tests trial 8.0.0,rum_8.0.0 CSM page views with data returns page views 1`] = ` Object { "items": Array [ Object { @@ -128,7 +128,7 @@ Object { } `; -exports[`APM specs (trial) CSM CSM page views when there is data returns page views with breakdown 1`] = ` +exports[`APM API tests trial 8.0.0,rum_8.0.0 CSM page views with data returns page views with breakdown 1`] = ` Object { "items": Array [ Object { @@ -265,14 +265,14 @@ Object { } `; -exports[`APM specs (trial) CSM CSM page views when there is no data returns empty list 1`] = ` +exports[`APM API tests trial no data CSM page views without data returns empty list 1`] = ` Object { "items": Array [], "topItems": Array [], } `; -exports[`APM specs (trial) CSM CSM page views when there is no data returns empty list with breakdowns 1`] = ` +exports[`APM API tests trial no data CSM page views without data returns empty list with breakdowns 1`] = ` Object { "items": Array [], "topItems": Array [], diff --git a/x-pack/test/apm_api_integration/tests/csm/csm_services.ts b/x-pack/test/apm_api_integration/tests/csm/csm_services.ts new file mode 100644 index 0000000000000..6460c0832a947 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/csm/csm_services.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function rumServicesApiTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + registry.when('CSM Services without data', { config: 'trial', archives: [] }, () => { + it('returns empty list', async () => { + const response = await supertest.get( + '/api/apm/rum-client/services?start=2020-06-28T10%3A24%3A46.055Z&end=2020-07-29T10%3A24%3A46.055Z&uiFilters=%7B%22agentName%22%3A%5B%22js-base%22%2C%22rum-js%22%5D%7D' + ); + + expect(response.status).to.be(200); + expect(response.body).to.eql([]); + }); + }); + + registry.when( + 'CSM services with data', + { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, + () => { + it('returns rum services list', async () => { + const response = await supertest.get( + '/api/apm/rum-client/services?start=2020-06-28T10%3A24%3A46.055Z&end=2020-07-29T10%3A24%3A46.055Z&uiFilters=%7B%22agentName%22%3A%5B%22js-base%22%2C%22rum-js%22%5D%7D' + ); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatchInline(`Array []`); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/has_rum_data.ts b/x-pack/test/apm_api_integration/tests/csm/has_rum_data.ts similarity index 53% rename from x-pack/test/apm_api_integration/trial/tests/csm/has_rum_data.ts rename to x-pack/test/apm_api_integration/tests/csm/has_rum_data.ts index f2033e03f5821..66f99dd29df5a 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/has_rum_data.ts +++ b/x-pack/test/apm_api_integration/tests/csm/has_rum_data.ts @@ -5,38 +5,31 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function rumHasDataApiTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - describe('CSM has rum data api', () => { - describe('when there is no data', () => { - it('returns empty list', async () => { - const response = await supertest.get( - '/api/apm/observability_overview/has_rum_data?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=' - ); + registry.when('has_rum_data without data', { config: 'trial', archives: [] }, () => { + it('returns empty list', async () => { + const response = await supertest.get( + '/api/apm/observability_overview/has_rum_data?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=' + ); - expect(response.status).to.be(200); - expectSnapshot(response.body).toMatchInline(` + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatchInline(` Object { "hasData": false, } `); - }); }); + }); - describe('when there is data', () => { - before(async () => { - await esArchiver.load('8.0.0'); - await esArchiver.load('rum_8.0.0'); - }); - after(async () => { - await esArchiver.unload('8.0.0'); - await esArchiver.unload('rum_8.0.0'); - }); - + registry.when( + 'has RUM data with data', + { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, + () => { it('returns that it has data and service name with most traffice', async () => { const response = await supertest.get( '/api/apm/observability_overview/has_rum_data?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=' @@ -51,6 +44,6 @@ export default function rumHasDataApiTests({ getService }: FtrProviderContext) { } `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/js_errors.ts b/x-pack/test/apm_api_integration/tests/csm/js_errors.ts similarity index 72% rename from x-pack/test/apm_api_integration/trial/tests/csm/js_errors.ts rename to x-pack/test/apm_api_integration/tests/csm/js_errors.ts index 6abb701f98a1c..dcadc8424ef7e 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/js_errors.ts +++ b/x-pack/test/apm_api_integration/tests/csm/js_errors.ts @@ -5,40 +5,33 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function rumJsErrorsApiTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - describe('CSM js errors', () => { - describe('when there is no data', () => { - it('returns no js errors', async () => { - const response = await supertest.get( - '/api/apm/rum-client/js-errors?pageSize=5&pageIndex=0&start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' - ); + registry.when('CSM JS errors with data', { config: 'trial', archives: [] }, () => { + it('returns no js errors', async () => { + const response = await supertest.get( + '/api/apm/rum-client/js-errors?pageSize=5&pageIndex=0&start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' + ); - expect(response.status).to.be(200); - expectSnapshot(response.body).toMatchInline(` + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatchInline(` Object { "totalErrorGroups": 0, "totalErrorPages": 0, "totalErrors": 0, } `); - }); }); + }); - describe('when there is data', () => { - before(async () => { - await esArchiver.load('8.0.0'); - await esArchiver.load('rum_test_data'); - }); - after(async () => { - await esArchiver.unload('8.0.0'); - await esArchiver.unload('rum_test_data'); - }); - + registry.when( + 'CSM JS errors without data', + { config: 'trial', archives: ['8.0.0', 'rum_test_data'] }, + () => { it('returns js errors', async () => { const response = await supertest.get( '/api/apm/rum-client/js-errors?start=2021-01-18T12%3A20%3A17.202Z&end=2021-01-18T12%3A25%3A17.203Z&uiFilters=%7B%22environment%22%3A%22ENVIRONMENT_ALL%22%2C%22serviceName%22%3A%5B%22elastic-co-frontend%22%5D%7D&pageSize=5&pageIndex=0' @@ -81,6 +74,6 @@ export default function rumJsErrorsApiTests({ getService }: FtrProviderContext) } `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/long_task_metrics.ts b/x-pack/test/apm_api_integration/tests/csm/long_task_metrics.ts similarity index 50% rename from x-pack/test/apm_api_integration/trial/tests/csm/long_task_metrics.ts rename to x-pack/test/apm_api_integration/tests/csm/long_task_metrics.ts index 6db5de24baa99..0da0889c11775 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/long_task_metrics.ts +++ b/x-pack/test/apm_api_integration/tests/csm/long_task_metrics.ts @@ -5,38 +5,31 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function rumServicesApiTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - describe('CSM long task metrics', () => { - describe('when there is no data', () => { - it('returns empty list', async () => { - const response = await supertest.get( - '/api/apm/rum-client/long-task-metrics?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' - ); - - expect(response.status).to.be(200); - expect(response.body).to.eql({ - longestLongTask: 0, - noOfLongTasks: 0, - sumOfLongTasks: 0, - }); + registry.when('CSM long task metrics without data', { config: 'trial', archives: [] }, () => { + it('returns empty list', async () => { + const response = await supertest.get( + '/api/apm/rum-client/long-task-metrics?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' + ); + + expect(response.status).to.be(200); + expect(response.body).to.eql({ + longestLongTask: 0, + noOfLongTasks: 0, + sumOfLongTasks: 0, }); }); + }); - describe('when there is data', () => { - before(async () => { - await esArchiver.load('8.0.0'); - await esArchiver.load('rum_8.0.0'); - }); - after(async () => { - await esArchiver.unload('8.0.0'); - await esArchiver.unload('rum_8.0.0'); - }); - + registry.when( + 'CSM long task metrics with data', + { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, + () => { it('returns web core vitals values', async () => { const response = await supertest.get( '/api/apm/rum-client/long-task-metrics?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D' @@ -52,6 +45,6 @@ export default function rumServicesApiTests({ getService }: FtrProviderContext) } `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/tests/csm/page_load_dist.ts b/x-pack/test/apm_api_integration/tests/csm/page_load_dist.ts new file mode 100644 index 0000000000000..28fa9cb87f516 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/csm/page_load_dist.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function rumServicesApiTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + registry.when('UX page load dist without data', { config: 'trial', archives: [] }, () => { + it('returns empty list', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-load-distribution?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' + ); + + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatch(); + }); + + it('returns empty list with breakdowns', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-load-distribution/breakdown?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&breakdown=Browser' + ); + + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatch(); + }); + }); + + registry.when( + 'UX page load dist with data', + { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, + () => { + it('returns page load distribution', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-load-distribution?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D' + ); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatch(); + }); + it('returns page load distribution with breakdown', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-load-distribution/breakdown?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&breakdown=Browser' + ); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatch(); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/csm/page_views.ts b/x-pack/test/apm_api_integration/tests/csm/page_views.ts new file mode 100644 index 0000000000000..43f9d278f694a --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/csm/page_views.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function rumServicesApiTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + registry.when('CSM page views without data', { config: 'trial', archives: [] }, () => { + it('returns empty list', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' + ); + + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatch(); + }); + + it('returns empty list with breakdowns', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&breakdowns=%7B%22name%22%3A%22Browser%22%2C%22fieldName%22%3A%22user_agent.name%22%2C%22type%22%3A%22category%22%7D' + ); + + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatch(); + }); + }); + + registry.when( + 'CSM page views with data', + { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, + () => { + it('returns page views', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D' + ); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatch(); + }); + it('returns page views with breakdown', async () => { + const response = await supertest.get( + '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&breakdowns=%7B%22name%22%3A%22Browser%22%2C%22fieldName%22%3A%22user_agent.name%22%2C%22type%22%3A%22category%22%7D' + ); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatch(); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/csm/url_search.ts b/x-pack/test/apm_api_integration/tests/csm/url_search.ts new file mode 100644 index 0000000000000..906f1783cb90e --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/csm/url_search.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function rumServicesApiTests({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + registry.when('CSM url search api without data', { config: 'trial', archives: [] }, () => { + it('returns empty list', async () => { + const response = await supertest.get( + '/api/apm/rum-client/url-search?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&percentile=50' + ); + + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatchInline(` + Object { + "items": Array [], + "total": 0, + } + `); + }); + }); + + registry.when( + 'CSM url search api with data', + { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, + () => { + it('returns top urls when no query', async () => { + const response = await supertest.get( + '/api/apm/rum-client/url-search?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&percentile=50' + ); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatchInline(` + Object { + "items": Array [ + Object { + "count": 5, + "pld": 4924000, + "url": "http://localhost:5601/nfw/app/csm?rangeFrom=now-15m&rangeTo=now&serviceName=kibana-frontend-8_0_0", + }, + Object { + "count": 1, + "pld": 2760000, + "url": "http://localhost:5601/nfw/app/home", + }, + ], + "total": 2, + } + `); + }); + + it('returns specific results against query', async () => { + const response = await supertest.get( + '/api/apm/rum-client/url-search?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&urlQuery=csm&percentile=50' + ); + + expect(response.status).to.be(200); + + expectSnapshot(response.body).toMatchInline(` + Object { + "items": Array [ + Object { + "count": 5, + "pld": 4924000, + "url": "http://localhost:5601/nfw/app/csm?rangeFrom=now-15m&rangeTo=now&serviceName=kibana-frontend-8_0_0", + }, + ], + "total": 1, + } + `); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/web_core_vitals.ts b/x-pack/test/apm_api_integration/tests/csm/web_core_vitals.ts similarity index 55% rename from x-pack/test/apm_api_integration/trial/tests/csm/web_core_vitals.ts rename to x-pack/test/apm_api_integration/tests/csm/web_core_vitals.ts index 50c261d2d37ad..3574ef065eef7 100644 --- a/x-pack/test/apm_api_integration/trial/tests/csm/web_core_vitals.ts +++ b/x-pack/test/apm_api_integration/tests/csm/web_core_vitals.ts @@ -5,41 +5,34 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function rumServicesApiTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - describe('CSM web core vitals', () => { - describe('when there is no data', () => { - it('returns empty list', async () => { - const response = await supertest.get( - '/api/apm/rum-client/web-core-vitals?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&percentile=50' - ); + registry.when('CSM web core vitals without data', { config: 'trial', archives: [] }, () => { + it('returns empty list', async () => { + const response = await supertest.get( + '/api/apm/rum-client/web-core-vitals?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&percentile=50' + ); - expect(response.status).to.be(200); - expect(response.body).to.eql({ - coreVitalPages: 0, - cls: null, - tbt: 0, - lcpRanks: [100, 0, 0], - fidRanks: [100, 0, 0], - clsRanks: [100, 0, 0], - }); + expect(response.status).to.be(200); + expect(response.body).to.eql({ + coreVitalPages: 0, + cls: null, + tbt: 0, + lcpRanks: [100, 0, 0], + fidRanks: [100, 0, 0], + clsRanks: [100, 0, 0], }); }); + }); - describe('when there is data', () => { - before(async () => { - await esArchiver.load('8.0.0'); - await esArchiver.load('rum_8.0.0'); - }); - after(async () => { - await esArchiver.unload('8.0.0'); - await esArchiver.unload('rum_8.0.0'); - }); - + registry.when( + 'CSM web core vitals with data', + { config: 'trial', archives: ['8.0.0', 'rum_8.0.0'] }, + () => { it('returns web core vitals values', async () => { const response = await supertest.get( '/api/apm/rum-client/web-core-vitals?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&percentile=50' @@ -73,6 +66,6 @@ export default function rumServicesApiTests({ getService }: FtrProviderContext) } `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/feature_controls.ts b/x-pack/test/apm_api_integration/tests/feature_controls.ts similarity index 98% rename from x-pack/test/apm_api_integration/basic/tests/feature_controls.ts rename to x-pack/test/apm_api_integration/tests/feature_controls.ts index 35025fcbfd107..7a65d8114c73f 100644 --- a/x-pack/test/apm_api_integration/basic/tests/feature_controls.ts +++ b/x-pack/test/apm_api_integration/tests/feature_controls.ts @@ -5,7 +5,8 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { FtrProviderContext } from '../common/ftr_provider_context'; +import { registry } from '../common/registry'; export default function featureControlsTests({ getService }: FtrProviderContext) { const supertest = getService('supertestAsApmWriteUser'); @@ -270,7 +271,7 @@ export default function featureControlsTests({ getService }: FtrProviderContext) } } - describe('apm feature controls', () => { + registry.when('apm feature controls', { config: 'basic', archives: [] }, () => { const config = { service: { name: 'test-service' }, settings: { transaction_sample_rate: '0.5' }, diff --git a/x-pack/test/apm_api_integration/tests/index.ts b/x-pack/test/apm_api_integration/tests/index.ts new file mode 100644 index 0000000000000..eef82c714b2d0 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/index.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../common/ftr_provider_context'; +import { registry } from '../common/registry'; + +export default function apmApiIntegrationTests(providerContext: FtrProviderContext) { + const { loadTestFile } = providerContext; + + describe('APM API tests', function () { + this.tags('ciGroup1'); + loadTestFile(require.resolve('./alerts/chart_preview')); + + loadTestFile(require.resolve('./correlations/slow_transactions')); + + loadTestFile(require.resolve('./csm/csm_services')); + loadTestFile(require.resolve('./csm/has_rum_data')); + loadTestFile(require.resolve('./csm/js_errors')); + loadTestFile(require.resolve('./csm/long_task_metrics')); + loadTestFile(require.resolve('./csm/page_load_dist')); + loadTestFile(require.resolve('./csm/page_views')); + loadTestFile(require.resolve('./csm/url_search')); + loadTestFile(require.resolve('./csm/web_core_vitals')); + + loadTestFile(require.resolve('./metrics_charts/metrics_charts')); + + loadTestFile(require.resolve('./observability_overview/has_data')); + loadTestFile(require.resolve('./observability_overview/observability_overview')); + + loadTestFile(require.resolve('./service_maps/service_maps')); + + loadTestFile(require.resolve('./service_overview/dependencies')); + loadTestFile(require.resolve('./service_overview/error_groups')); + loadTestFile(require.resolve('./service_overview/instances')); + + loadTestFile(require.resolve('./services/agent_name')); + loadTestFile(require.resolve('./services/annotations')); + loadTestFile(require.resolve('./services/service_details')); + loadTestFile(require.resolve('./services/service_icons')); + loadTestFile(require.resolve('./services/throughput')); + loadTestFile(require.resolve('./services/top_services')); + loadTestFile(require.resolve('./services/transaction_types')); + + loadTestFile(require.resolve('./settings/anomaly_detection/basic')); + loadTestFile(require.resolve('./settings/anomaly_detection/no_access_user')); + loadTestFile(require.resolve('./settings/anomaly_detection/read_user')); + loadTestFile(require.resolve('./settings/anomaly_detection/write_user')); + + loadTestFile(require.resolve('./settings/agent_configuration')); + + loadTestFile(require.resolve('./settings/custom_link')); + + loadTestFile(require.resolve('./traces/top_traces')); + + loadTestFile(require.resolve('./transactions/breakdown')); + loadTestFile(require.resolve('./transactions/distribution')); + loadTestFile(require.resolve('./transactions/error_rate')); + loadTestFile(require.resolve('./transactions/latency')); + loadTestFile(require.resolve('./transactions/throughput')); + loadTestFile(require.resolve('./transactions/top_transaction_groups')); + loadTestFile(require.resolve('./transactions/transactions_groups_overview')); + + loadTestFile(require.resolve('./feature_controls')); + + registry.run(providerContext); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/metrics_charts/metrics_charts.ts b/x-pack/test/apm_api_integration/tests/metrics_charts/metrics_charts.ts new file mode 100644 index 0000000000000..dda46f00d7c72 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/metrics_charts/metrics_charts.ts @@ -0,0 +1,446 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import expect from '@kbn/expect'; +import { first } from 'lodash'; +import { MetricsChartsByAgentAPIResponse } from '../../../../plugins/apm/server/lib/metrics/get_metrics_chart_data_by_agent'; +import { GenericMetricsChart } from '../../../../plugins/apm/server/lib/metrics/transform_metrics_chart'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +interface ChartResponse { + body: MetricsChartsByAgentAPIResponse; + status: number; +} + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + registry.when( + 'Metrics charts when data is loaded', + { config: 'basic', archives: ['metrics_8.0.0'] }, + () => { + describe('for opbeans-node', () => { + const start = encodeURIComponent('2020-09-08T14:50:00.000Z'); + const end = encodeURIComponent('2020-09-08T14:55:00.000Z'); + const uiFilters = encodeURIComponent(JSON.stringify({})); + const agentName = 'nodejs'; + + describe('returns metrics data', () => { + let chartsResponse: ChartResponse; + before(async () => { + chartsResponse = await supertest.get( + `/api/apm/services/opbeans-node/metrics/charts?start=${start}&end=${end}&uiFilters=${uiFilters}&agentName=${agentName}` + ); + }); + it('contains CPU usage and System memory usage chart data', async () => { + expect(chartsResponse.status).to.be(200); + expectSnapshot(chartsResponse.body.charts.map((chart) => chart.title)).toMatchInline(` + Array [ + "CPU usage", + "System memory usage", + ] + `); + }); + + describe('CPU usage', () => { + let cpuUsageChart: GenericMetricsChart | undefined; + before(() => { + cpuUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'cpu_usage_chart' + ); + }); + + it('has correct series', () => { + expect(cpuUsageChart).to.not.empty(); + expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "System max", + "System average", + "Process max", + "Process average", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 0.714, + 0.3877, + 0.75, + 0.2543, + ] + `); + }); + }); + + describe("System memory usage (using 'system.memory' fields to calculate the memory usage)", () => { + let systemMemoryUsageChart: GenericMetricsChart | undefined; + before(() => { + systemMemoryUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'memory_usage_chart' + ); + }); + + it('has correct series', () => { + expect(systemMemoryUsageChart).to.not.empty(); + expectSnapshot(systemMemoryUsageChart?.series.map(({ title }) => title)) + .toMatchInline(` + Array [ + "Max", + "Average", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(systemMemoryUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 0.722093920925555, + 0.718173546796348, + ] + `); + }); + }); + }); + }); + + describe('for opbeans-java', () => { + const uiFilters = encodeURIComponent(JSON.stringify({})); + const agentName = 'java'; + + describe('returns metrics data', () => { + const start = encodeURIComponent('2020-09-08T14:55:30.000Z'); + const end = encodeURIComponent('2020-09-08T15:00:00.000Z'); + + let chartsResponse: ChartResponse; + before(async () => { + chartsResponse = await supertest.get( + `/api/apm/services/opbeans-java/metrics/charts?start=${start}&end=${end}&uiFilters=${uiFilters}&agentName=${agentName}` + ); + }); + + it('has correct chart data', async () => { + expect(chartsResponse.status).to.be(200); + expectSnapshot(chartsResponse.body.charts.map((chart) => chart.title)).toMatchInline(` + Array [ + "CPU usage", + "System memory usage", + "Heap Memory", + "Non-Heap Memory", + "Thread Count", + "Garbage collection per minute", + "Garbage collection time spent per minute", + ] + `); + }); + + describe('CPU usage', () => { + let cpuUsageChart: GenericMetricsChart | undefined; + before(() => { + cpuUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'cpu_usage_chart' + ); + }); + + it('has correct series', () => { + expect(cpuUsageChart).to.not.empty(); + expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "System max", + "System average", + "Process max", + "Process average", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 0.203, + 0.178777777777778, + 0.01, + 0.009, + ] + `); + }); + + it('has the correct rate', async () => { + const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); + expectSnapshot(yValues).toMatchInline(` + Array [ + 0.193, + 0.193, + 0.009, + 0.009, + ] + `); + }); + }); + + describe("System memory usage (using 'system.process.cgroup' fields to calculate the memory usage)", () => { + let systemMemoryUsageChart: GenericMetricsChart | undefined; + before(() => { + systemMemoryUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'memory_usage_chart' + ); + }); + + it('has correct series', () => { + expect(systemMemoryUsageChart).to.not.empty(); + expectSnapshot(systemMemoryUsageChart?.series.map(({ title }) => title)) + .toMatchInline(` + Array [ + "Max", + "Average", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(systemMemoryUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 0.707924703557837, + 0.705395980841182, + ] + `); + }); + + it('has the correct rate', async () => { + const yValues = systemMemoryUsageChart?.series.map((serie) => first(serie.data)?.y); + expectSnapshot(yValues).toMatchInline(` + Array [ + 0.707924703557837, + 0.707924703557837, + ] + `); + }); + }); + + describe('Heap Memory', () => { + let cpuUsageChart: GenericMetricsChart | undefined; + before(() => { + cpuUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'heap_memory_area_chart' + ); + }); + + it('has correct series', () => { + expect(cpuUsageChart).to.not.empty(); + expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "Avg. used", + "Avg. committed", + "Avg. limit", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 222501617.777778, + 374341632, + 1560281088, + ] + `); + }); + + it('has the correct rate', async () => { + const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); + expectSnapshot(yValues).toMatchInline(` + Array [ + 211472896, + 374341632, + 1560281088, + ] + `); + }); + }); + + describe('Non-Heap Memory', () => { + let cpuUsageChart: GenericMetricsChart | undefined; + before(() => { + cpuUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'non_heap_memory_area_chart' + ); + }); + + it('has correct series', () => { + expect(cpuUsageChart).to.not.empty(); + expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "Avg. used", + "Avg. committed", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 138573397.333333, + 147677639.111111, + ] + `); + }); + + it('has the correct rate', async () => { + const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); + expectSnapshot(yValues).toMatchInline(` + Array [ + 138162752, + 147386368, + ] + `); + }); + }); + + describe('Thread Count', () => { + let cpuUsageChart: GenericMetricsChart | undefined; + before(() => { + cpuUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'thread_count_line_chart' + ); + }); + + it('has correct series', () => { + expect(cpuUsageChart).to.not.empty(); + expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "Avg. count", + "Max count", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 44.4444444444444, + 45, + ] + `); + }); + + it('has the correct rate', async () => { + const yValues = cpuUsageChart?.series.map((serie) => first(serie.data)?.y); + expectSnapshot(yValues).toMatchInline(` + Array [ + 44, + 44, + ] + `); + }); + }); + + describe('Garbage collection per minute', () => { + let cpuUsageChart: GenericMetricsChart | undefined; + before(() => { + cpuUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'gc_rate_line_chart' + ); + }); + + it('has correct series', () => { + expect(cpuUsageChart).to.not.empty(); + expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "G1 Old Generation", + "G1 Young Generation", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 0, + 15, + ] + `); + }); + }); + + describe('Garbage collection time spent per minute', () => { + let cpuUsageChart: GenericMetricsChart | undefined; + before(() => { + cpuUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'gc_time_line_chart' + ); + }); + + it('has correct series', () => { + expect(cpuUsageChart).to.not.empty(); + expectSnapshot(cpuUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "G1 Old Generation", + "G1 Young Generation", + ] + `); + }); + + it('has correct series overall values', () => { + expectSnapshot(cpuUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 0, + 187.5, + ] + `); + }); + }); + }); + + // 9223372036854771712 = memory limit for a c-group when no memory limit is specified + it('calculates system memory usage using system total field when cgroup limit is equal to 9223372036854771712', async () => { + const start = encodeURIComponent('2020-09-08T15:00:30.000Z'); + const end = encodeURIComponent('2020-09-08T15:05:00.000Z'); + + const chartsResponse: ChartResponse = await supertest.get( + `/api/apm/services/opbeans-java/metrics/charts?start=${start}&end=${end}&uiFilters=${uiFilters}&agentName=${agentName}` + ); + + const systemMemoryUsageChart = chartsResponse.body.charts.find( + ({ key }) => key === 'memory_usage_chart' + ); + + expect(systemMemoryUsageChart).to.not.empty(); + expectSnapshot(systemMemoryUsageChart?.series.map(({ title }) => title)).toMatchInline(` + Array [ + "Max", + "Average", + ] + `); + expectSnapshot(systemMemoryUsageChart?.series.map(({ overallValue }) => overallValue)) + .toMatchInline(` + Array [ + 0.114523896426499, + 0.114002376090415, + ] + `); + + const yValues = systemMemoryUsageChart?.series.map((serie) => first(serie.data)?.y); + expectSnapshot(yValues).toMatchInline(` + Array [ + 0.11383724014064, + 0.11383724014064, + ] + `); + }); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/basic/tests/observability_overview/has_data.ts b/x-pack/test/apm_api_integration/tests/observability_overview/has_data.ts similarity index 67% rename from x-pack/test/apm_api_integration/basic/tests/observability_overview/has_data.ts rename to x-pack/test/apm_api_integration/tests/observability_overview/has_data.ts index 6d0d2d3042625..9c88f75c6adbd 100644 --- a/x-pack/test/apm_api_integration/basic/tests/observability_overview/has_data.ts +++ b/x-pack/test/apm_api_integration/tests/observability_overview/has_data.ts @@ -4,40 +4,45 @@ * you may not use this file except in compliance with the Elastic License. */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - const archiveName = 'apm_8.0.0'; - - describe('Has data', () => { - describe('when data is not loaded', () => { + registry.when( + 'Observability overview when data is not loaded', + { config: 'basic', archives: [] }, + () => { it('returns false when there is no data', async () => { const response = await supertest.get('/api/apm/observability_overview/has_data'); expect(response.status).to.be(200); expect(response.body).to.eql(false); }); - }); - describe('when only onboarding data is loaded', () => { - before(() => esArchiver.load('observability_overview')); - after(() => esArchiver.unload('observability_overview')); + } + ); + + registry.when( + 'Observability overview when only onboarding data is loaded', + { config: 'basic', archives: ['observability_overview'] }, + () => { it('returns false when there is only onboarding data', async () => { const response = await supertest.get('/api/apm/observability_overview/has_data'); expect(response.status).to.be(200); expect(response.body).to.eql(false); }); - }); - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); + } + ); + registry.when( + 'Observability overview when APM data is loaded', + { config: 'basic', archives: ['apm_8.0.0'] }, + () => { it('returns true when there is at least one document on transaction, error or metrics indices', async () => { const response = await supertest.get('/api/apm/observability_overview/has_data'); expect(response.status).to.be(200); expect(response.body).to.eql(true); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/observability_overview/observability_overview.ts b/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.ts similarity index 69% rename from x-pack/test/apm_api_integration/basic/tests/observability_overview/observability_overview.ts rename to x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.ts index f7c459029c7f5..7c9d8fa8b0f91 100644 --- a/x-pack/test/apm_api_integration/basic/tests/observability_overview/observability_overview.ts +++ b/x-pack/test/apm_api_integration/tests/observability_overview/observability_overview.ts @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ import expect from '@kbn/expect'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; @@ -19,22 +19,28 @@ export default function ApiTest({ getService }: FtrProviderContext) { const end = encodeURIComponent(metadata.end); const bucketSize = '60s'; - describe('Observability overview', () => { - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get( - `/api/apm/observability_overview?start=${start}&end=${end}&bucketSize=${bucketSize}` - ); - expect(response.status).to.be(200); + registry.when( + 'Observability overview when data is not loaded', + { config: 'basic', archives: [] }, + () => { + describe('when data is not loaded', () => { + it('handles the empty state', async () => { + const response = await supertest.get( + `/api/apm/observability_overview?start=${start}&end=${end}&bucketSize=${bucketSize}` + ); + expect(response.status).to.be(200); - expect(response.body.serviceCount).to.be(0); - expect(response.body.transactionCoordinates.length).to.be(0); + expect(response.body.serviceCount).to.be(0); + expect(response.body.transactionCoordinates.length).to.be(0); + }); }); - }); - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); + } + ); + registry.when( + 'Observability overview when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { it('returns the service count and transaction coordinates', async () => { const response = await supertest.get( `/api/apm/observability_overview?start=${start}&end=${end}&bucketSize=${bucketSize}` @@ -80,6 +86,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { ] `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/trial/tests/service_maps/__snapshots__/service_maps.snap b/x-pack/test/apm_api_integration/tests/service_maps/__snapshots__/service_maps.snap similarity index 99% rename from x-pack/test/apm_api_integration/trial/tests/service_maps/__snapshots__/service_maps.snap rename to x-pack/test/apm_api_integration/tests/service_maps/__snapshots__/service_maps.snap index 7639822eaa6f9..69bc039b67ed2 100644 --- a/x-pack/test/apm_api_integration/trial/tests/service_maps/__snapshots__/service_maps.snap +++ b/x-pack/test/apm_api_integration/tests/service_maps/__snapshots__/service_maps.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM specs (trial) Service Maps Service Maps with a trial license /api/apm/service-map when there is data returns service map elements filtering by environment not defined 1`] = ` +exports[`APM API tests trial apm_8.0.0 Service Map with data /api/apm/service-map returns service map elements filtering by environment not defined 1`] = ` Object { "elements": Array [ Object { @@ -514,7 +514,7 @@ Object { } `; -exports[`APM specs (trial) Service Maps Service Maps with a trial license /api/apm/service-map when there is data returns the correct data 3`] = ` +exports[`APM API tests trial apm_8.0.0 Service Map with data /api/apm/service-map returns the correct data 3`] = ` Array [ Object { "data": Object { @@ -1741,7 +1741,7 @@ Array [ ] `; -exports[`APM specs (trial) Service Maps Service Maps with a trial license when there is data with anomalies with the default apm user returns the correct anomaly stats 3`] = ` +exports[`APM API tests trial apm_8.0.0 Service Map with data /api/apm/service-map with ML data with the default apm user returns the correct anomaly stats 3`] = ` Object { "elements": Array [ Object { diff --git a/x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts b/x-pack/test/apm_api_integration/tests/service_maps/service_maps.ts similarity index 58% rename from x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts rename to x-pack/test/apm_api_integration/tests/service_maps/service_maps.ts index 02acd34ad5666..a15f0442c7cde 100644 --- a/x-pack/test/apm_api_integration/trial/tests/service_maps/service_maps.ts +++ b/x-pack/test/apm_api_integration/tests/service_maps/service_maps.ts @@ -7,57 +7,85 @@ import querystring from 'querystring'; import expect from '@kbn/expect'; import { isEmpty, uniq } from 'lodash'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { PromiseReturnType } from '../../../../plugins/observability/typings/common'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function serviceMapsApiTests({ getService }: FtrProviderContext) { const supertest = getService('supertest'); const supertestAsApmReadUserWithoutMlAccess = getService('supertestAsApmReadUserWithoutMlAccess'); - const esArchiver = getService('esArchiver'); - const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; const start = encodeURIComponent(metadata.start); const end = encodeURIComponent(metadata.end); - describe('Service Maps with a trial license', () => { + registry.when('Service map with a basic license', { config: 'basic', archives: [] }, () => { + it('is only be available to users with Platinum license (or higher)', async () => { + const response = await supertest.get(`/api/apm/service-map?start=${start}&end=${end}`); + + expect(response.status).to.be(403); + + expectSnapshot(response.body.message).toMatchInline( + `"In order to access Service Maps, you must be subscribed to an Elastic Platinum license. With it, you'll have the ability to visualize your entire application stack along with your APM data."` + ); + }); + }); + + registry.when('Service map without data', { config: 'trial', archives: [] }, () => { describe('/api/apm/service-map', () => { - describe('when there is no data', () => { - it('returns empty list', async () => { - const response = await supertest.get(`/api/apm/service-map?start=${start}&end=${end}`); + it('returns an empty list', async () => { + const response = await supertest.get(`/api/apm/service-map?start=${start}&end=${end}`); - expect(response.status).to.be(200); - expect(response.body.elements.length).to.be(0); - }); + expect(response.status).to.be(200); + expect(response.body.elements.length).to.be(0); }); + }); - describe('when there is data', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); + describe('/api/apm/service-map/service/{serviceName}', () => { + it('returns an object with nulls', async () => { + const q = querystring.stringify({ + start: metadata.start, + end: metadata.end, + uiFilters: encodeURIComponent('{}'), + }); + const response = await supertest.get(`/api/apm/service-map/service/opbeans-node?${q}`); - let response: PromiseReturnType; + expect(response.status).to.be(200); - before(async () => { - response = await supertest.get(`/api/apm/service-map?start=${start}&end=${end}`); - }); + expect(response.body.avgCpuUsage).to.be(null); + expect(response.body.avgErrorRate).to.be(null); + expect(response.body.avgMemoryUsage).to.be(null); + expect(response.body.transactionStats.avgRequestsPerMinute).to.be(null); + expect(response.body.transactionStats.avgTransactionDuration).to.be(null); + }); + }); + }); - it('returns service map elements', () => { - expect(response.status).to.be(200); - expect(response.body.elements.length).to.be.greaterThan(0); - }); + registry.when('Service Map with data', { config: 'trial', archives: ['apm_8.0.0'] }, () => { + describe('/api/apm/service-map', () => { + let response: PromiseReturnType; + + before(async () => { + response = await supertest.get(`/api/apm/service-map?start=${start}&end=${end}`); + }); + + it('returns service map elements', () => { + expect(response.status).to.be(200); + expect(response.body.elements.length).to.be.greaterThan(0); + }); - it('returns the correct data', () => { - const elements: Array<{ data: Record }> = response.body.elements; + it('returns the correct data', () => { + const elements: Array<{ data: Record }> = response.body.elements; - const serviceNames = uniq( - elements - .filter((element) => element.data['service.name'] !== undefined) - .map((element) => element.data['service.name']) - ).sort(); + const serviceNames = uniq( + elements + .filter((element) => element.data['service.name'] !== undefined) + .map((element) => element.data['service.name']) + ).sort(); - expectSnapshot(serviceNames).toMatchInline(` + expectSnapshot(serviceNames).toMatchInline(` Array [ "kibana", "kibana-frontend", @@ -71,13 +99,13 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext) ] `); - const externalDestinations = uniq( - elements - .filter((element) => element.data.target?.startsWith('>')) - .map((element) => element.data.target) - ).sort(); + const externalDestinations = uniq( + elements + .filter((element) => element.data.target?.startsWith('>')) + .map((element) => element.data.target) + ).sort(); - expectSnapshot(externalDestinations).toMatchInline(` + expectSnapshot(externalDestinations).toMatchInline(` Array [ ">elasticsearch", ">feeds.elastic.co:443", @@ -86,51 +114,26 @@ export default function serviceMapsApiTests({ getService }: FtrProviderContext) ] `); - expectSnapshot(elements).toMatch(); - }); - - it('returns service map elements filtering by environment not defined', async () => { - const ENVIRONMENT_NOT_DEFINED = 'ENVIRONMENT_NOT_DEFINED'; - const { body, status } = await supertest.get( - `/api/apm/service-map?start=${start}&end=${end}&environment=${ENVIRONMENT_NOT_DEFINED}` - ); - expect(status).to.be(200); - const environments = new Set(); - body.elements.forEach((element: { data: Record }) => { - environments.add(element.data['service.environment']); - }); - - expect(environments.has(ENVIRONMENT_NOT_DEFINED)).to.eql(true); - expectSnapshot(body).toMatch(); - }); + expectSnapshot(elements).toMatch(); }); - }); - - describe('/api/apm/service-map/service/{serviceName}', () => { - describe('when there is no data', () => { - it('returns an object with nulls', async () => { - const q = querystring.stringify({ - start: metadata.start, - end: metadata.end, - uiFilters: encodeURIComponent('{}'), - }); - const response = await supertest.get(`/api/apm/service-map/service/opbeans-node?${q}`); - expect(response.status).to.be(200); - - expect(response.body.avgCpuUsage).to.be(null); - expect(response.body.avgErrorRate).to.be(null); - expect(response.body.avgMemoryUsage).to.be(null); - expect(response.body.transactionStats.avgRequestsPerMinute).to.be(null); - expect(response.body.transactionStats.avgTransactionDuration).to.be(null); + it('returns service map elements filtering by environment not defined', async () => { + const ENVIRONMENT_NOT_DEFINED = 'ENVIRONMENT_NOT_DEFINED'; + const { body, status } = await supertest.get( + `/api/apm/service-map?start=${start}&end=${end}&environment=${ENVIRONMENT_NOT_DEFINED}` + ); + expect(status).to.be(200); + const environments = new Set(); + body.elements.forEach((element: { data: Record }) => { + environments.add(element.data['service.environment']); }); + + expect(environments.has(ENVIRONMENT_NOT_DEFINED)).to.eql(true); + expectSnapshot(body).toMatch(); }); }); - describe('when there is data with anomalies', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - + describe('/api/apm/service-map with ML data', () => { describe('with the default apm user', () => { let response: PromiseReturnType; diff --git a/x-pack/test/apm_api_integration/basic/tests/service_overview/dependencies/es_utils.ts b/x-pack/test/apm_api_integration/tests/service_overview/dependencies/es_utils.ts similarity index 100% rename from x-pack/test/apm_api_integration/basic/tests/service_overview/dependencies/es_utils.ts rename to x-pack/test/apm_api_integration/tests/service_overview/dependencies/es_utils.ts diff --git a/x-pack/test/apm_api_integration/basic/tests/service_overview/dependencies/index.ts b/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts similarity index 91% rename from x-pack/test/apm_api_integration/basic/tests/service_overview/dependencies/index.ts rename to x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts index aeb5d1256796a..b3e7e0672fc7f 100644 --- a/x-pack/test/apm_api_integration/basic/tests/service_overview/dependencies/index.ts +++ b/x-pack/test/apm_api_integration/tests/service_overview/dependencies/index.ts @@ -8,26 +8,28 @@ import expect from '@kbn/expect'; import url from 'url'; import { sortBy, pick, last } from 'lodash'; import { ValuesType } from 'utility-types'; -import { Maybe } from '../../../../../../plugins/apm/typings/common'; -import { isFiniteNumber } from '../../../../../../plugins/apm/common/utils/is_finite_number'; -import { APIReturnType } from '../../../../../../plugins/apm/public/services/rest/createCallApmApi'; -import { ENVIRONMENT_ALL } from '../../../../../../plugins/apm/common/environment_filter_values'; -import { FtrProviderContext } from '../../../../../common/ftr_provider_context'; -import archives from '../../../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../../common/registry'; +import { Maybe } from '../../../../../plugins/apm/typings/common'; +import { isFiniteNumber } from '../../../../../plugins/apm/common/utils/is_finite_number'; +import { APIReturnType } from '../../../../../plugins/apm/public/services/rest/createCallApmApi'; +import { ENVIRONMENT_ALL } from '../../../../../plugins/apm/common/environment_filter_values'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import archives from '../../../common/fixtures/es_archiver/archives_metadata'; import { apmDependenciesMapping, createServiceDependencyDocs } from './es_utils'; const round = (num: Maybe): string => (isFiniteNumber(num) ? num.toPrecision(4) : ''); export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const es = getService('es'); const archiveName = 'apm_8.0.0'; const { start, end } = archives[archiveName]; - describe('Service overview dependencies', () => { - describe('when data is not loaded', () => { + registry.when( + 'Service overview dependencies when data is not loaded', + { config: 'basic', archives: [] }, + () => { it('handles the empty state', async () => { const response = await supertest.get( url.format({ @@ -44,9 +46,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.status).to.be(200); expect(response.body).to.eql([]); }); - }); + } + ); - describe('when specific data is loaded', () => { + registry.when( + 'Service overview dependencies when specific data is loaded', + { config: 'basic', archives: [] }, + () => { let response: { status: number; body: APIReturnType<'GET /api/apm/services/{serviceName}/dependencies'>; @@ -285,17 +291,19 @@ export default function ApiTest({ getService }: FtrProviderContext) { impact: 0, }); }); - }); + } + ); - describe('when data is loaded', () => { + registry.when( + 'Service overview dependencies when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { let response: { status: number; body: APIReturnType<'GET /api/apm/services/{serviceName}/dependencies'>; }; before(async () => { - await esArchiver.load(archiveName); - response = await supertest.get( url.format({ pathname: `/api/apm/services/opbeans-java/dependencies`, @@ -309,8 +317,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { ); }); - after(() => esArchiver.unload(archiveName)); - it('returns a successful response', () => { expect(response.status).to.be(200); }); @@ -380,6 +386,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { ] `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/service_overview/error_groups.ts b/x-pack/test/apm_api_integration/tests/service_overview/error_groups.ts similarity index 94% rename from x-pack/test/apm_api_integration/basic/tests/service_overview/error_groups.ts rename to x-pack/test/apm_api_integration/tests/service_overview/error_groups.ts index 7d1c05960f3e6..fc649c60103c8 100644 --- a/x-pack/test/apm_api_integration/basic/tests/service_overview/error_groups.ts +++ b/x-pack/test/apm_api_integration/tests/service_overview/error_groups.ts @@ -7,18 +7,20 @@ import expect from '@kbn/expect'; import qs from 'querystring'; import { pick, uniqBy } from 'lodash'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import archives from '../../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import archives from '../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const { start, end } = archives[archiveName]; - describe('Service overview error groups', () => { - describe('when data is not loaded', () => { + registry.when( + 'Service overview error groups when data is not loaded', + { config: 'basic', archives: [] }, + () => { it('handles the empty state', async () => { const response = await supertest.get( `/api/apm/services/opbeans-java/error_groups?${qs.stringify({ @@ -41,12 +43,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { is_aggregation_accurate: true, }); }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); + } + ); + registry.when( + 'Service overview error groups when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { it('returns the correct data', async () => { const response = await supertest.get( `/api/apm/services/opbeans-java/error_groups?${qs.stringify({ @@ -220,6 +223,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(uniqBy(items, 'group_id').length).to.eql(totalItems); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/service_overview/instances.ts b/x-pack/test/apm_api_integration/tests/service_overview/instances.ts similarity index 83% rename from x-pack/test/apm_api_integration/basic/tests/service_overview/instances.ts rename to x-pack/test/apm_api_integration/tests/service_overview/instances.ts index 2227a8c09a6cf..08d60f90900b8 100644 --- a/x-pack/test/apm_api_integration/basic/tests/service_overview/instances.ts +++ b/x-pack/test/apm_api_integration/tests/service_overview/instances.ts @@ -7,14 +7,14 @@ import expect from '@kbn/expect'; import url from 'url'; import { pick, sortBy } from 'lodash'; -import { isFiniteNumber } from '../../../../../plugins/apm/common/utils/is_finite_number'; -import { APIReturnType } from '../../../../../plugins/apm/public/services/rest/createCallApmApi'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import archives from '../../../common/fixtures/es_archiver/archives_metadata'; +import { isFiniteNumber } from '../../../../plugins/apm/common/utils/is_finite_number'; +import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import archives from '../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const { start, end } = archives[archiveName]; @@ -24,31 +24,36 @@ export default function ApiTest({ getService }: FtrProviderContext) { body: APIReturnType<'GET /api/apm/services/{serviceName}/service_overview_instances'>; } - describe('Service overview instances', () => { - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response: Response = await supertest.get( - url.format({ - pathname: `/api/apm/services/opbeans-java/service_overview_instances`, - query: { - start, - end, - numBuckets: 20, - transactionType: 'request', - uiFilters: '{}', - }, - }) - ); - - expect(response.status).to.be(200); - expect(response.body).to.eql([]); - }); - }); + registry.when( + 'Service overview instances when data is not loaded', + { config: 'basic', archives: [] }, + () => { + describe('when data is not loaded', () => { + it('handles the empty state', async () => { + const response: Response = await supertest.get( + url.format({ + pathname: `/api/apm/services/opbeans-java/service_overview_instances`, + query: { + start, + end, + numBuckets: 20, + transactionType: 'request', + uiFilters: '{}', + }, + }) + ); - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); + expect(response.status).to.be(200); + expect(response.body).to.eql([]); + }); + }); + } + ); + registry.when( + 'Service overview instances when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { describe('fetching java data', () => { let response: Response; @@ -209,6 +214,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expectSnapshot(values); }); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/services/__snapshots__/throughput.snap b/x-pack/test/apm_api_integration/tests/services/__snapshots__/throughput.snap similarity index 96% rename from x-pack/test/apm_api_integration/basic/tests/services/__snapshots__/throughput.snap rename to x-pack/test/apm_api_integration/tests/services/__snapshots__/throughput.snap index fe7f434aad2e1..f23601fccb174 100644 --- a/x-pack/test/apm_api_integration/basic/tests/services/__snapshots__/throughput.snap +++ b/x-pack/test/apm_api_integration/tests/services/__snapshots__/throughput.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM specs (basic) Services Throughput when data is loaded returns the service throughput has the correct throughput 1`] = ` +exports[`APM API tests basic apm_8.0.0 Throughput when data is loaded has the correct throughput 1`] = ` Array [ Object { "x": 1607435850000, diff --git a/x-pack/test/apm_api_integration/basic/tests/services/agent_name.ts b/x-pack/test/apm_api_integration/tests/services/agent_name.ts similarity index 55% rename from x-pack/test/apm_api_integration/basic/tests/services/agent_name.ts rename to x-pack/test/apm_api_integration/tests/services/agent_name.ts index cea8fb5da2428..538d7a448ea30 100644 --- a/x-pack/test/apm_api_integration/basic/tests/services/agent_name.ts +++ b/x-pack/test/apm_api_integration/tests/services/agent_name.ts @@ -5,34 +5,33 @@ */ import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import archives from '../../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import archives from '../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const range = archives[archiveName]; const start = encodeURIComponent(range.start); const end = encodeURIComponent(range.end); - describe('Agent name', () => { - describe('when data is not loaded ', () => { - it('handles the empty state', async () => { - const response = await supertest.get( - `/api/apm/services/opbeans-node/agent_name?start=${start}&end=${end}` - ); + registry.when('Agent name when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles the empty state', async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-node/agent_name?start=${start}&end=${end}` + ); - expect(response.status).to.be(200); - expect(response.body).to.eql({}); - }); + expect(response.status).to.be(200); + expect(response.body).to.eql({}); }); + }); - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - + registry.when( + 'Agent name when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { it('returns the agent name', async () => { const response = await supertest.get( `/api/apm/services/opbeans-node/agent_name?start=${start}&end=${end}` @@ -42,6 +41,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.body).to.eql({ agentName: 'nodejs' }); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/trial/tests/services/annotations.ts b/x-pack/test/apm_api_integration/tests/services/annotations.ts similarity index 92% rename from x-pack/test/apm_api_integration/trial/tests/services/annotations.ts rename to x-pack/test/apm_api_integration/tests/services/annotations.ts index c2ddc10c5f1d2..4ff690fa01aa1 100644 --- a/x-pack/test/apm_api_integration/trial/tests/services/annotations.ts +++ b/x-pack/test/apm_api_integration/tests/services/annotations.ts @@ -7,7 +7,8 @@ import expect from '@kbn/expect'; import { merge, cloneDeep, isPlainObject } from 'lodash'; import { JsonObject } from 'src/plugins/kibana_utils/common'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; const DEFAULT_INDEX_NAME = 'observability-annotations'; @@ -40,7 +41,32 @@ export default function annotationApiTests({ getService }: FtrProviderContext) { } } - describe('APM annotations with a trial license', () => { + registry.when('Annotations with a basic license', { config: 'basic', archives: [] }, () => { + describe('when creating an annotation', () => { + it('fails with a 403 forbidden', async () => { + const response = await request({ + url: '/api/apm/services/opbeans-java/annotation', + method: 'POST', + data: { + '@timestamp': new Date().toISOString(), + message: 'New deployment', + tags: ['foo'], + service: { + version: '1.1', + environment: 'production', + }, + }, + }); + + expect(response.status).to.be(403); + expect(response.body.message).to.be( + 'Annotations require at least a gold license or a trial license.' + ); + }); + }); + }); + + registry.when('Annotations with a trial license', { config: 'trial', archives: [] }, () => { describe('when creating an annotation', () => { afterEach(async () => { const indexExists = (await es.indices.exists({ index: DEFAULT_INDEX_NAME })).body; diff --git a/x-pack/test/apm_api_integration/basic/tests/services/service_details.ts b/x-pack/test/apm_api_integration/tests/services/service_details.ts similarity index 87% rename from x-pack/test/apm_api_integration/basic/tests/services/service_details.ts rename to x-pack/test/apm_api_integration/tests/services/service_details.ts index 54bd16e6f78c4..77155c907f3b1 100644 --- a/x-pack/test/apm_api_integration/basic/tests/services/service_details.ts +++ b/x-pack/test/apm_api_integration/tests/services/service_details.ts @@ -6,18 +6,20 @@ import expect from '@kbn/expect'; import url from 'url'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import archives from '../../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import archives from '../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const { start, end } = archives[archiveName]; - describe('Service details', () => { - describe('when data is not loaded ', () => { + registry.when( + 'Service details when data is not loaded', + { config: 'basic', archives: [] }, + () => { it('handles the empty state', async () => { const response = await supertest.get( url.format({ @@ -29,12 +31,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.status).to.be(200); expect(response.body).to.eql({}); }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); + } + ); + registry.when( + 'Service details when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { it('returns java service details', async () => { const response = await supertest.get( url.format({ @@ -116,6 +119,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { } `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/services/service_icons.ts b/x-pack/test/apm_api_integration/tests/services/service_icons.ts similarity index 53% rename from x-pack/test/apm_api_integration/basic/tests/services/service_icons.ts rename to x-pack/test/apm_api_integration/tests/services/service_icons.ts index 4b79de14551d6..7926a2744e45c 100644 --- a/x-pack/test/apm_api_integration/basic/tests/services/service_icons.ts +++ b/x-pack/test/apm_api_integration/tests/services/service_icons.ts @@ -6,35 +6,34 @@ import expect from '@kbn/expect'; import url from 'url'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import archives from '../../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import archives from '../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const { start, end } = archives[archiveName]; - describe('Service icons', () => { - describe('when data is not loaded ', () => { - it('handles the empty state', async () => { - const response = await supertest.get( - url.format({ - pathname: `/api/apm/services/opbeans-java/metadata/icons`, - query: { start, end }, - }) - ); + registry.when('Service icons when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles the empty state', async () => { + const response = await supertest.get( + url.format({ + pathname: `/api/apm/services/opbeans-java/metadata/icons`, + query: { start, end }, + }) + ); - expect(response.status).to.be(200); - expect(response.body).to.eql({}); - }); + expect(response.status).to.be(200); + expect(response.body).to.eql({}); }); + }); - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - + registry.when( + 'Service icons when data is not loaded', + { config: 'basic', archives: [archiveName] }, + () => { it('returns java service icons', async () => { const response = await supertest.get( url.format({ @@ -46,11 +45,11 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.status).to.be(200); expectSnapshot(response.body).toMatchInline(` - Object { - "agentName": "java", - "containerType": "Kubernetes", - } - `); + Object { + "agentName": "java", + "containerType": "Kubernetes", + } + `); }); it('returns python service icons', async () => { @@ -64,13 +63,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.status).to.be(200); expectSnapshot(response.body).toMatchInline(` - Object { - "agentName": "python", - "cloudProvider": "gcp", - "containerType": "Kubernetes", - } - `); + Object { + "agentName": "python", + "cloudProvider": "gcp", + "containerType": "Kubernetes", + } + `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/tests/services/throughput.ts b/x-pack/test/apm_api_integration/tests/services/throughput.ts new file mode 100644 index 0000000000000..fa94cbf600709 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/services/throughput.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import expect from '@kbn/expect'; +import qs from 'querystring'; +import { first, last } from 'lodash'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + const archiveName = 'apm_8.0.0'; + const metadata = archives_metadata[archiveName]; + + registry.when('Throughput when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles the empty state', async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-java/throughput?${qs.stringify({ + start: metadata.start, + end: metadata.end, + uiFilters: encodeURIComponent('{}'), + transactionType: 'request', + })}` + ); + expect(response.status).to.be(200); + expect(response.body.throughput.length).to.be(0); + }); + }); + + registry.when( + 'Throughput when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { + let throughputResponse: { + throughput: Array<{ x: number; y: number | null }>; + }; + before(async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-java/throughput?${qs.stringify({ + start: metadata.start, + end: metadata.end, + uiFilters: encodeURIComponent('{}'), + transactionType: 'request', + })}` + ); + throughputResponse = response.body; + }); + + it('returns some data', () => { + expect(throughputResponse.throughput.length).to.be.greaterThan(0); + + const nonNullDataPoints = throughputResponse.throughput.filter(({ y }) => y !== null); + + expect(nonNullDataPoints.length).to.be.greaterThan(0); + }); + + it('has the correct start date', () => { + expectSnapshot( + new Date(first(throughputResponse.throughput)?.x ?? NaN).toISOString() + ).toMatchInline(`"2020-12-08T13:57:30.000Z"`); + }); + + it('has the correct end date', () => { + expectSnapshot( + new Date(last(throughputResponse.throughput)?.x ?? NaN).toISOString() + ).toMatchInline(`"2020-12-08T14:27:30.000Z"`); + }); + + it('has the correct number of buckets', () => { + expectSnapshot(throughputResponse.throughput.length).toMatchInline(`61`); + }); + + it('has the correct throughput', () => { + expectSnapshot(throughputResponse.throughput).toMatch(); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/tests/services/top_services.ts b/x-pack/test/apm_api_integration/tests/services/top_services.ts new file mode 100644 index 0000000000000..42797e3e7c87a --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/services/top_services.ts @@ -0,0 +1,364 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { sortBy, pick, isEmpty } from 'lodash'; +import { APIReturnType } from '../../../../plugins/apm/public/services/rest/createCallApmApi'; +import { PromiseReturnType } from '../../../../plugins/observability/typings/common'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../common/registry'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const supertestAsApmReadUserWithoutMlAccess = getService('supertestAsApmReadUserWithoutMlAccess'); + + const archiveName = 'apm_8.0.0'; + + const range = archives_metadata[archiveName]; + + // url parameters + const start = encodeURIComponent(range.start); + const end = encodeURIComponent(range.end); + + const uiFilters = encodeURIComponent(JSON.stringify({})); + + registry.when( + 'APM Services Overview with a basic license when data is not loaded', + { config: 'basic', archives: [] }, + () => { + it('handles the empty state', async () => { + const response = await supertest.get( + `/api/apm/services?start=${start}&end=${end}&uiFilters=${uiFilters}` + ); + + expect(response.status).to.be(200); + expect(response.body.hasHistoricalData).to.be(false); + expect(response.body.hasLegacyData).to.be(false); + expect(response.body.items.length).to.be(0); + }); + } + ); + + registry.when( + 'APM Services Overview with a basic license when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { + let response: { + status: number; + body: APIReturnType<'GET /api/apm/services'>; + }; + + let sortedItems: typeof response.body.items; + + before(async () => { + response = await supertest.get( + `/api/apm/services?start=${start}&end=${end}&uiFilters=${uiFilters}` + ); + sortedItems = sortBy(response.body.items, 'serviceName'); + }); + + it('the response is successful', () => { + expect(response.status).to.eql(200); + }); + + it('returns hasHistoricalData: true', () => { + expect(response.body.hasHistoricalData).to.be(true); + }); + + it('returns hasLegacyData: false', () => { + expect(response.body.hasLegacyData).to.be(false); + }); + + it('returns the correct service names', () => { + expectSnapshot(sortedItems.map((item) => item.serviceName)).toMatchInline(` + Array [ + "kibana", + "kibana-frontend", + "opbeans-dotnet", + "opbeans-go", + "opbeans-java", + "opbeans-node", + "opbeans-python", + "opbeans-ruby", + "opbeans-rum", + ] + `); + }); + + it('returns the correct metrics averages', () => { + expectSnapshot( + sortedItems.map((item) => + pick( + item, + 'transactionErrorRate.value', + 'avgResponseTime.value', + 'transactionsPerMinute.value' + ) + ) + ).toMatchInline(` + Array [ + Object { + "avgResponseTime": Object { + "value": 420419.34550767, + }, + "transactionErrorRate": Object { + "value": 0, + }, + "transactionsPerMinute": Object { + "value": 45.6333333333333, + }, + }, + Object { + "avgResponseTime": Object { + "value": 2382833.33333333, + }, + "transactionErrorRate": Object { + "value": null, + }, + "transactionsPerMinute": Object { + "value": 0.2, + }, + }, + Object { + "avgResponseTime": Object { + "value": 631521.83908046, + }, + "transactionErrorRate": Object { + "value": 0.0229885057471264, + }, + "transactionsPerMinute": Object { + "value": 2.9, + }, + }, + Object { + "avgResponseTime": Object { + "value": 27946.1484375, + }, + "transactionErrorRate": Object { + "value": 0.015625, + }, + "transactionsPerMinute": Object { + "value": 4.26666666666667, + }, + }, + Object { + "avgResponseTime": Object { + "value": 237339.813333333, + }, + "transactionErrorRate": Object { + "value": 0.16, + }, + "transactionsPerMinute": Object { + "value": 2.5, + }, + }, + Object { + "avgResponseTime": Object { + "value": 24920.1052631579, + }, + "transactionErrorRate": Object { + "value": 0.0210526315789474, + }, + "transactionsPerMinute": Object { + "value": 3.16666666666667, + }, + }, + Object { + "avgResponseTime": Object { + "value": 29542.6607142857, + }, + "transactionErrorRate": Object { + "value": 0.0357142857142857, + }, + "transactionsPerMinute": Object { + "value": 1.86666666666667, + }, + }, + Object { + "avgResponseTime": Object { + "value": 70518.9328358209, + }, + "transactionErrorRate": Object { + "value": 0.0373134328358209, + }, + "transactionsPerMinute": Object { + "value": 4.46666666666667, + }, + }, + Object { + "avgResponseTime": Object { + "value": 2319812.5, + }, + "transactionErrorRate": Object { + "value": null, + }, + "transactionsPerMinute": Object { + "value": 0.533333333333333, + }, + }, + ] + `); + }); + + it('returns environments', () => { + expectSnapshot(sortedItems.map((item) => item.environments ?? [])).toMatchInline(` + Array [ + Array [ + "production", + ], + Array [ + "production", + ], + Array [ + "production", + ], + Array [ + "testing", + ], + Array [ + "production", + ], + Array [ + "testing", + ], + Array [], + Array [], + Array [ + "testing", + ], + ] + `); + }); + + it(`RUM services don't report any transaction error rates`, () => { + // RUM transactions don't have event.outcome set, + // so they should not have an error rate + + const rumServices = sortedItems.filter((item) => item.agentName === 'rum-js'); + + expect(rumServices.length).to.be.greaterThan(0); + + expect(rumServices.every((item) => isEmpty(item.transactionErrorRate?.value))); + }); + + it('non-RUM services all report transaction error rates', () => { + const nonRumServices = sortedItems.filter((item) => item.agentName !== 'rum-js'); + + expect( + nonRumServices.every((item) => { + return ( + typeof item.transactionErrorRate?.value === 'number' && + item.transactionErrorRate.timeseries.length > 0 + ); + }) + ).to.be(true); + }); + } + ); + + registry.when( + 'APM Services overview with a trial license when data is loaded', + { config: 'trial', archives: [archiveName] }, + () => { + describe('with the default APM read user', () => { + describe('and fetching a list of services', () => { + let response: { + status: number; + body: APIReturnType<'GET /api/apm/services'>; + }; + + before(async () => { + response = await supertest.get( + `/api/apm/services?start=${start}&end=${end}&uiFilters=${uiFilters}` + ); + }); + + it('the response is successful', () => { + expect(response.status).to.eql(200); + }); + + it('there is at least one service', () => { + expect(response.body.items.length).to.be.greaterThan(0); + }); + + it('some items have a health status set', () => { + // Under the assumption that the loaded archive has + // at least one APM ML job, and the time range is longer + // than 15m, at least one items should have a health status + // set. Note that we currently have a bug where healthy + // services report as unknown (so without any health status): + // https://github.com/elastic/kibana/issues/77083 + + const healthStatuses = sortBy(response.body.items, 'serviceName').map( + (item: any) => item.healthStatus + ); + + expect(healthStatuses.filter(Boolean).length).to.be.greaterThan(0); + + expectSnapshot(healthStatuses).toMatchInline(` + Array [ + "healthy", + "healthy", + "healthy", + "healthy", + "healthy", + "healthy", + "healthy", + "healthy", + "healthy", + ] + `); + }); + }); + }); + + describe('with a user that does not have access to ML', () => { + let response: PromiseReturnType; + before(async () => { + response = await supertestAsApmReadUserWithoutMlAccess.get( + `/api/apm/services?start=${start}&end=${end}&uiFilters=${uiFilters}` + ); + }); + + it('the response is successful', () => { + expect(response.status).to.eql(200); + }); + + it('there is at least one service', () => { + expect(response.body.items.length).to.be.greaterThan(0); + }); + + it('contains no health statuses', () => { + const definedHealthStatuses = response.body.items + .map((item: any) => item.healthStatus) + .filter(Boolean); + + expect(definedHealthStatuses.length).to.be(0); + }); + }); + + describe('and fetching a list of services with a filter', () => { + let response: PromiseReturnType; + before(async () => { + response = await supertest.get( + `/api/apm/services?start=${start}&end=${end}&uiFilters=${encodeURIComponent( + `{"kuery":"service.name:opbeans-java","environment":"ENVIRONMENT_ALL"}` + )}` + ); + }); + + it('does not return health statuses for services that are not found in APM data', () => { + expect(response.status).to.be(200); + + expect(response.body.items.length).to.be(1); + + expect(response.body.items[0].serviceName).to.be('opbeans-java'); + }); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/basic/tests/services/transaction_types.ts b/x-pack/test/apm_api_integration/tests/services/transaction_types.ts similarity index 75% rename from x-pack/test/apm_api_integration/basic/tests/services/transaction_types.ts rename to x-pack/test/apm_api_integration/tests/services/transaction_types.ts index fcfe1660d58e2..c45f4083ef8da 100644 --- a/x-pack/test/apm_api_integration/basic/tests/services/transaction_types.ts +++ b/x-pack/test/apm_api_integration/tests/services/transaction_types.ts @@ -5,12 +5,12 @@ */ import expect from '@kbn/expect'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; @@ -19,8 +19,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { const start = encodeURIComponent(metadata.start); const end = encodeURIComponent(metadata.end); - describe('Transaction types', () => { - describe('when data is not loaded ', () => { + registry.when( + 'Transaction types when data is not loaded', + { config: 'basic', archives: [] }, + () => { it('handles empty state', async () => { const response = await supertest.get( `/api/apm/services/opbeans-node/transaction_types?start=${start}&end=${end}` @@ -30,12 +32,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.body.transactionTypes.length).to.be(0); }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); + } + ); + registry.when( + 'Transaction types when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { it('handles empty state', async () => { const response = await supertest.get( `/api/apm/services/opbeans-node/transaction_types?start=${start}&end=${end}` @@ -53,6 +56,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { } `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/tests/settings/agent_configuration.ts b/x-pack/test/apm_api_integration/tests/settings/agent_configuration.ts new file mode 100644 index 0000000000000..0b58dd5908c60 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/settings/agent_configuration.ts @@ -0,0 +1,495 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { omit, orderBy } from 'lodash'; +import { AgentConfigurationIntake } from '../../../../plugins/apm/common/agent_configuration/configuration_types'; +import { AgentConfigSearchParams } from '../../../../plugins/apm/server/routes/settings/agent_configuration'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function agentConfigurationTests({ getService }: FtrProviderContext) { + const supertestRead = getService('supertestAsApmReadUser'); + const supertestWrite = getService('supertestAsApmWriteUser'); + const log = getService('log'); + + const archiveName = 'apm_8.0.0'; + + function getServices() { + return supertestRead + .get(`/api/apm/settings/agent-configuration/services`) + .set('kbn-xsrf', 'foo'); + } + + function getEnvironments(serviceName: string) { + return supertestRead + .get(`/api/apm/settings/agent-configuration/environments?serviceName=${serviceName}`) + .set('kbn-xsrf', 'foo'); + } + + function getAgentName(serviceName: string) { + return supertestRead + .get(`/api/apm/settings/agent-configuration/agent_name?serviceName=${serviceName}`) + .set('kbn-xsrf', 'foo'); + } + + function searchConfigurations(configuration: AgentConfigSearchParams) { + return supertestRead + .post(`/api/apm/settings/agent-configuration/search`) + .send(configuration) + .set('kbn-xsrf', 'foo'); + } + + function getAllConfigurations() { + return supertestRead.get(`/api/apm/settings/agent-configuration`).set('kbn-xsrf', 'foo'); + } + + async function createConfiguration(config: AgentConfigurationIntake, { user = 'write' } = {}) { + log.debug('creating configuration', config.service); + const supertestClient = user === 'read' ? supertestRead : supertestWrite; + + const res = await supertestClient + .put(`/api/apm/settings/agent-configuration`) + .send(config) + .set('kbn-xsrf', 'foo'); + + throwOnError(res); + + return res; + } + + async function updateConfiguration(config: AgentConfigurationIntake, { user = 'write' } = {}) { + log.debug('updating configuration', config.service); + const supertestClient = user === 'read' ? supertestRead : supertestWrite; + + const res = await supertestClient + .put(`/api/apm/settings/agent-configuration?overwrite=true`) + .send(config) + .set('kbn-xsrf', 'foo'); + + throwOnError(res); + + return res; + } + + async function deleteConfiguration( + { service }: AgentConfigurationIntake, + { user = 'write' } = {} + ) { + log.debug('deleting configuration', service); + const supertestClient = user === 'read' ? supertestRead : supertestWrite; + + const res = await supertestClient + .delete(`/api/apm/settings/agent-configuration`) + .send({ service }) + .set('kbn-xsrf', 'foo'); + + throwOnError(res); + + return res; + } + + function throwOnError(res: any) { + const { statusCode, req, body } = res; + if (statusCode !== 200) { + const e = new Error(` + Endpoint: ${req.method} ${req.path} + Service: ${JSON.stringify(res.request._data.service)} + Status code: ${statusCode} + Response: ${body.message}`); + + // @ts-ignore + e.res = res; + + throw e; + } + } + + registry.when( + 'agent configuration when no data is loaded', + { config: 'basic', archives: [] }, + () => { + it('handles the empty state for services', async () => { + const { body } = await getServices(); + expect(body).to.eql(['ALL_OPTION_VALUE']); + }); + + it('handles the empty state for environments', async () => { + const { body } = await getEnvironments('myservice'); + expect(body).to.eql([{ name: 'ALL_OPTION_VALUE', alreadyConfigured: false }]); + }); + + it('handles the empty state for agent names', async () => { + const { body } = await getAgentName('myservice'); + expect(body).to.eql({}); + }); + + describe('as a read-only user', () => { + const newConfig = { service: {}, settings: { transaction_sample_rate: '0.55' } }; + it('throws when attempting to create config', async () => { + try { + await createConfiguration(newConfig, { user: 'read' }); + + // ensure that `createConfiguration` throws + expect(true).to.be(false); + } catch (e) { + expect(e.res.statusCode).to.be(403); + } + }); + + describe('when a configuration already exists', () => { + before(async () => createConfiguration(newConfig)); + after(async () => deleteConfiguration(newConfig)); + + it('throws when attempting to update config', async () => { + try { + await updateConfiguration(newConfig, { user: 'read' }); + + // ensure that `updateConfiguration` throws + expect(true).to.be(false); + } catch (e) { + expect(e.res.statusCode).to.be(403); + } + }); + + it('throws when attempting to delete config', async () => { + try { + await deleteConfiguration(newConfig, { user: 'read' }); + + // ensure that `deleteConfiguration` throws + expect(true).to.be(false); + } catch (e) { + expect(e.res.statusCode).to.be(403); + } + }); + }); + }); + + describe('when creating one configuration', () => { + const newConfig = { + service: {}, + settings: { transaction_sample_rate: '0.55' }, + }; + + const searchParams = { + service: { name: 'myservice', environment: 'development' }, + etag: '7312bdcc34999629a3d39df24ed9b2a7553c0c39', + }; + + it('can create and delete config', async () => { + // assert that config does not exist + const res1 = await searchConfigurations(searchParams); + expect(res1.status).to.equal(404); + + // assert that config was created + await createConfiguration(newConfig); + const res2 = await searchConfigurations(searchParams); + expect(res2.status).to.equal(200); + + // assert that config was deleted + await deleteConfiguration(newConfig); + const res3 = await searchConfigurations(searchParams); + expect(res3.status).to.equal(404); + }); + + describe('when a configuration exists', () => { + before(async () => createConfiguration(newConfig)); + after(async () => deleteConfiguration(newConfig)); + + it('can find the config', async () => { + const { status, body } = await searchConfigurations(searchParams); + expect(status).to.equal(200); + expect(body._source.service).to.eql({}); + expect(body._source.settings).to.eql({ transaction_sample_rate: '0.55' }); + }); + + it('can list the config', async () => { + const { status, body } = await getAllConfigurations(); + expect(status).to.equal(200); + expect(omitTimestamp(body)).to.eql([ + { + service: {}, + settings: { transaction_sample_rate: '0.55' }, + applied_by_agent: false, + etag: 'eb88a8997666cc4b33745ef355a1bbd7c4782f2d', + }, + ]); + }); + + it('can update the config', async () => { + await updateConfiguration({ + service: {}, + settings: { transaction_sample_rate: '0.85' }, + }); + const { status, body } = await searchConfigurations(searchParams); + expect(status).to.equal(200); + expect(body._source.service).to.eql({}); + expect(body._source.settings).to.eql({ transaction_sample_rate: '0.85' }); + }); + }); + }); + + describe('when creating multiple configurations', () => { + const configs = [ + { + service: {}, + settings: { transaction_sample_rate: '0.1' }, + }, + { + service: { name: 'my_service' }, + settings: { transaction_sample_rate: '0.2' }, + }, + { + service: { name: 'my_service', environment: 'development' }, + settings: { transaction_sample_rate: '0.3' }, + }, + { + service: { environment: 'production' }, + settings: { transaction_sample_rate: '0.4' }, + }, + { + service: { environment: 'development' }, + settings: { transaction_sample_rate: '0.5' }, + }, + ]; + + before(async () => { + await Promise.all(configs.map((config) => createConfiguration(config))); + }); + + after(async () => { + await Promise.all(configs.map((config) => deleteConfiguration(config))); + }); + + const agentsRequests = [ + { + service: { name: 'non_existing_service', environment: 'non_existing_env' }, + expectedSettings: { transaction_sample_rate: '0.1' }, + }, + { + service: { name: 'my_service', environment: 'non_existing_env' }, + expectedSettings: { transaction_sample_rate: '0.2' }, + }, + { + service: { name: 'my_service', environment: 'production' }, + expectedSettings: { transaction_sample_rate: '0.2' }, + }, + { + service: { name: 'my_service', environment: 'development' }, + expectedSettings: { transaction_sample_rate: '0.3' }, + }, + { + service: { name: 'non_existing_service', environment: 'production' }, + expectedSettings: { transaction_sample_rate: '0.4' }, + }, + { + service: { name: 'non_existing_service', environment: 'development' }, + expectedSettings: { transaction_sample_rate: '0.5' }, + }, + ]; + + it('can list all configs', async () => { + const { status, body } = await getAllConfigurations(); + expect(status).to.equal(200); + expect(orderBy(omitTimestamp(body), ['settings.transaction_sample_rate'])).to.eql([ + { + service: {}, + settings: { transaction_sample_rate: '0.1' }, + applied_by_agent: false, + etag: '0758cb18817de60cca29e07480d472694239c4c3', + }, + { + service: { name: 'my_service' }, + settings: { transaction_sample_rate: '0.2' }, + applied_by_agent: false, + etag: 'e04737637056fdf1763bf0ef0d3fcb86e89ae5fc', + }, + { + service: { name: 'my_service', environment: 'development' }, + settings: { transaction_sample_rate: '0.3' }, + applied_by_agent: false, + etag: 'af4dac62621b6762e6281481d1f7523af1124120', + }, + { + service: { environment: 'production' }, + settings: { transaction_sample_rate: '0.4' }, + applied_by_agent: false, + etag: '8d1bf8e6b778b60af351117e2cf53fb1ee570068', + }, + { + service: { environment: 'development' }, + settings: { transaction_sample_rate: '0.5' }, + applied_by_agent: false, + etag: '4ce40da57e3c71daca704121c784b911ec05ae81', + }, + ]); + }); + + for (const agentRequest of agentsRequests) { + it(`${agentRequest.service.name} / ${agentRequest.service.environment}`, async () => { + const { status, body } = await searchConfigurations({ + service: agentRequest.service, + etag: 'abc', + }); + + expect(status).to.equal(200); + expect(body._source.settings).to.eql(agentRequest.expectedSettings); + }); + } + }); + + describe('when an agent retrieves a configuration', () => { + const config = { + service: { name: 'myservice', environment: 'development' }, + settings: { transaction_sample_rate: '0.9' }, + }; + const configProduction = { + service: { name: 'myservice', environment: 'production' }, + settings: { transaction_sample_rate: '0.9' }, + }; + let etag: string; + + before(async () => { + log.debug('creating agent configuration'); + await createConfiguration(config); + await createConfiguration(configProduction); + }); + + after(async () => { + await deleteConfiguration(config); + await deleteConfiguration(configProduction); + }); + + it(`should have 'applied_by_agent=false' before supplying etag`, async () => { + const res1 = await searchConfigurations({ + service: { name: 'myservice', environment: 'development' }, + }); + + etag = res1.body._source.etag; + + const res2 = await searchConfigurations({ + service: { name: 'myservice', environment: 'development' }, + etag, + }); + + expect(res1.body._source.applied_by_agent).to.be(false); + expect(res2.body._source.applied_by_agent).to.be(false); + }); + + it(`should have 'applied_by_agent=true' after supplying etag`, async () => { + await searchConfigurations({ + service: { name: 'myservice', environment: 'development' }, + etag, + }); + + async function hasBeenAppliedByAgent() { + const { body } = await searchConfigurations({ + service: { name: 'myservice', environment: 'development' }, + }); + + return body._source.applied_by_agent; + } + + // wait until `applied_by_agent` has been updated in elasticsearch + expect(await waitFor(hasBeenAppliedByAgent)).to.be(true); + }); + it(`should have 'applied_by_agent=false' before marking as applied`, async () => { + const res1 = await searchConfigurations({ + service: { name: 'myservice', environment: 'production' }, + }); + + expect(res1.body._source.applied_by_agent).to.be(false); + }); + it(`should have 'applied_by_agent=true' when 'mark_as_applied_by_agent' attribute is true`, async () => { + await searchConfigurations({ + service: { name: 'myservice', environment: 'production' }, + mark_as_applied_by_agent: true, + }); + + async function hasBeenAppliedByAgent() { + const { body } = await searchConfigurations({ + service: { name: 'myservice', environment: 'production' }, + }); + + return body._source.applied_by_agent; + } + + // wait until `applied_by_agent` has been updated in elasticsearch + expect(await waitFor(hasBeenAppliedByAgent)).to.be(true); + }); + }); + } + ); + + registry.when( + 'agent configuration when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { + it('returns all services', async () => { + const { body } = await getServices(); + expectSnapshot(body).toMatchInline(` + Array [ + "ALL_OPTION_VALUE", + "kibana", + "kibana-frontend", + "opbeans-dotnet", + "opbeans-go", + "opbeans-java", + "opbeans-node", + "opbeans-python", + "opbeans-ruby", + "opbeans-rum", + ] + `); + }); + + it('returns the environments, all unconfigured', async () => { + const { body } = await getEnvironments('opbeans-node'); + + expect(body.map((item: { name: string }) => item.name)).to.contain('ALL_OPTION_VALUE'); + + expect( + body.every((item: { alreadyConfigured: boolean }) => item.alreadyConfigured === false) + ).to.be(true); + + expectSnapshot(body).toMatchInline(` + Array [ + Object { + "alreadyConfigured": false, + "name": "ALL_OPTION_VALUE", + }, + Object { + "alreadyConfigured": false, + "name": "testing", + }, + ] + `); + }); + + it('returns the agent names', async () => { + const { body } = await getAgentName('opbeans-node'); + expect(body).to.eql({ agentName: 'nodejs' }); + }); + } + ); +} + +async function waitFor(cb: () => Promise, retries = 50): Promise { + if (retries === 0) { + throw new Error(`Maximum number of retries reached`); + } + + const res = await cb(); + if (!res) { + await new Promise((resolve) => setTimeout(resolve, 100)); + return waitFor(cb, retries - 1); + } + return res; +} + +function omitTimestamp(configs: AgentConfigurationIntake[]) { + return configs.map((config: AgentConfigurationIntake) => omit(config, '@timestamp')); +} diff --git a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/basic.ts b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/basic.ts new file mode 100644 index 0000000000000..269375ef080de --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/basic.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { registry } from '../../../common/registry'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; + +export default function apiTest({ getService }: FtrProviderContext) { + const noAccessUser = getService('supertestAsNoAccessUser'); + const readUser = getService('supertestAsApmReadUser'); + const writeUser = getService('supertestAsApmWriteUser'); + + type SupertestAsUser = typeof noAccessUser | typeof readUser | typeof writeUser; + + function getJobs(user: SupertestAsUser) { + return user.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); + } + + function createJobs(user: SupertestAsUser, environments: string[]) { + return user + .post(`/api/apm/settings/anomaly-detection/jobs`) + .send({ environments }) + .set('kbn-xsrf', 'foo'); + } + + async function expectForbidden(user: SupertestAsUser) { + const { body: getJobsBody } = await getJobs(user); + expect(getJobsBody.statusCode).to.be(403); + expect(getJobsBody.error).to.be('Forbidden'); + + const { body: createJobsBody } = await createJobs(user, ['production', 'staging']); + + expect(createJobsBody.statusCode).to.be(403); + expect(getJobsBody.error).to.be('Forbidden'); + } + + registry.when('ML jobs return a 403 for', { config: 'basic', archives: [] }, () => { + it('user without access', async () => { + await expectForbidden(noAccessUser); + }); + + it('read user', async () => { + await expectForbidden(readUser); + }); + + it('write user', async () => { + await expectForbidden(writeUser); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/no_access_user.ts b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/no_access_user.ts new file mode 100644 index 0000000000000..4878d5031f040 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/no_access_user.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { registry } from '../../../common/registry'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; + +export default function apiTest({ getService }: FtrProviderContext) { + const noAccessUser = getService('supertestAsNoAccessUser'); + + function getJobs() { + return noAccessUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); + } + + function createJobs(environments: string[]) { + return noAccessUser + .post(`/api/apm/settings/anomaly-detection/jobs`) + .send({ environments }) + .set('kbn-xsrf', 'foo'); + } + + registry.when('ML jobs', { config: 'trial', archives: [] }, () => { + describe('when user does not have read access to ML', () => { + describe('when calling the endpoint for listing jobs', () => { + it('returns an error because the user does not have access', async () => { + const { body } = await getJobs(); + expect(body.statusCode).to.be(403); + expect(body.error).to.be('Forbidden'); + }); + }); + + describe('when calling create endpoint', () => { + it('returns an error because the user does not have access', async () => { + const { body } = await createJobs(['production', 'staging']); + expect(body.statusCode).to.be(403); + expect(body.error).to.be('Forbidden'); + }); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/read_user.ts b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/read_user.ts new file mode 100644 index 0000000000000..a5fabe66af6f5 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/read_user.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { registry } from '../../../common/registry'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; + +export default function apiTest({ getService }: FtrProviderContext) { + const apmReadUser = getService('supertestAsApmReadUser'); + + function getJobs() { + return apmReadUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); + } + + function createJobs(environments: string[]) { + return apmReadUser + .post(`/api/apm/settings/anomaly-detection/jobs`) + .send({ environments }) + .set('kbn-xsrf', 'foo'); + } + + registry.when('ML jobs', { config: 'trial', archives: [] }, () => { + describe('when user has read access to ML', () => { + describe('when calling the endpoint for listing jobs', () => { + it('returns a list of jobs', async () => { + const { body } = await getJobs(); + + expect(body.jobs.length).to.be(0); + expect(body.hasLegacyJobs).to.be(false); + }); + }); + + describe('when calling create endpoint', () => { + it('returns an error because the user does not have access', async () => { + const { body } = await createJobs(['production', 'staging']); + + expect(body.statusCode).to.be(403); + expect(body.error).to.be('Forbidden'); + }); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.ts b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.ts new file mode 100644 index 0000000000000..5260d234eb9c7 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/settings/anomaly_detection/write_user.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { registry } from '../../../common/registry'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; + +export default function apiTest({ getService }: FtrProviderContext) { + const apmWriteUser = getService('supertestAsApmWriteUser'); + + function getJobs() { + return apmWriteUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); + } + + function createJobs(environments: string[]) { + return apmWriteUser + .post(`/api/apm/settings/anomaly-detection/jobs`) + .send({ environments }) + .set('kbn-xsrf', 'foo'); + } + + function deleteJobs(jobIds: string[]) { + return apmWriteUser.post(`/api/ml/jobs/delete_jobs`).send({ jobIds }).set('kbn-xsrf', 'foo'); + } + + registry.when('ML jobs', { config: 'trial', archives: [] }, () => { + describe('when user has write access to ML', () => { + after(async () => { + const res = await getJobs(); + const jobIds = res.body.jobs.map((job: any) => job.job_id); + await deleteJobs(jobIds); + }); + + describe('when calling the endpoint for listing jobs', () => { + it('returns a list of jobs', async () => { + const { body } = await getJobs(); + expect(body.jobs.length).to.be(0); + expect(body.hasLegacyJobs).to.be(false); + }); + }); + + describe('when calling create endpoint', () => { + it('creates two jobs', async () => { + await createJobs(['production', 'staging']); + + const { body } = await getJobs(); + expect(body.hasLegacyJobs).to.be(false); + expect(body.jobs.map((job: any) => job.environment)).to.eql(['production', 'staging']); + }); + }); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/tests/settings/custom_link.ts b/x-pack/test/apm_api_integration/tests/settings/custom_link.ts new file mode 100644 index 0000000000000..3df905eabeace --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/settings/custom_link.ts @@ -0,0 +1,183 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import URL from 'url'; +import expect from '@kbn/expect'; +import { CustomLink } from '../../../../plugins/apm/common/custom_link/custom_link_types'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function customLinksTests({ getService }: FtrProviderContext) { + const supertestRead = getService('supertest'); + const supertestWrite = getService('supertestAsApmWriteUser'); + const log = getService('log'); + + const archiveName = 'apm_8.0.0'; + + registry.when('Custom links with a basic license', { config: 'basic', archives: [] }, () => { + it('returns a 403 forbidden', async () => { + const customLink = { + url: 'https://elastic.co', + label: 'with filters', + filters: [ + { key: 'service.name', value: 'baz' }, + { key: 'transaction.type', value: 'qux' }, + ], + } as CustomLink; + const response = await supertestWrite + .post(`/api/apm/settings/custom_links`) + .send(customLink) + .set('kbn-xsrf', 'foo'); + + expect(response.status).to.be(403); + + expectSnapshot(response.body.message).toMatchInline( + `"To create custom links, you must be subscribed to an Elastic Gold license or above. With it, you'll have the ability to create custom links to improve your workflow when analyzing your services."` + ); + }); + }); + + registry.when( + 'Custom links with a trial license and data', + { config: 'trial', archives: [archiveName] }, + () => { + before(async () => { + const customLink = { + url: 'https://elastic.co', + label: 'with filters', + filters: [ + { key: 'service.name', value: 'baz' }, + { key: 'transaction.type', value: 'qux' }, + ], + } as CustomLink; + await createCustomLink(customLink); + }); + it('fetches a custom link', async () => { + const { status, body } = await searchCustomLinks({ + 'service.name': 'baz', + 'transaction.type': 'qux', + }); + const { label, url, filters } = body[0]; + + expect(status).to.equal(200); + expect({ label, url, filters }).to.eql({ + label: 'with filters', + url: 'https://elastic.co', + filters: [ + { key: 'service.name', value: 'baz' }, + { key: 'transaction.type', value: 'qux' }, + ], + }); + }); + it('updates a custom link', async () => { + let { status, body } = await searchCustomLinks({ + 'service.name': 'baz', + 'transaction.type': 'qux', + }); + expect(status).to.equal(200); + await updateCustomLink(body[0].id, { + label: 'foo', + url: 'https://elastic.co?service.name={{service.name}}', + filters: [ + { key: 'service.name', value: 'quz' }, + { key: 'transaction.name', value: 'bar' }, + ], + }); + ({ status, body } = await searchCustomLinks({ + 'service.name': 'quz', + 'transaction.name': 'bar', + })); + const { label, url, filters } = body[0]; + expect(status).to.equal(200); + expect({ label, url, filters }).to.eql({ + label: 'foo', + url: 'https://elastic.co?service.name={{service.name}}', + filters: [ + { key: 'service.name', value: 'quz' }, + { key: 'transaction.name', value: 'bar' }, + ], + }); + }); + it('deletes a custom link', async () => { + let { status, body } = await searchCustomLinks({ + 'service.name': 'quz', + 'transaction.name': 'bar', + }); + expect(status).to.equal(200); + await deleteCustomLink(body[0].id); + ({ status, body } = await searchCustomLinks({ + 'service.name': 'quz', + 'transaction.name': 'bar', + })); + expect(status).to.equal(200); + expect(body).to.eql([]); + }); + + describe('transaction', () => { + it('fetches a transaction sample', async () => { + const response = await supertestRead.get( + '/api/apm/settings/custom_links/transaction?service.name=opbeans-java' + ); + expect(response.status).to.be(200); + expect(response.body.service.name).to.eql('opbeans-java'); + }); + }); + } + ); + + function searchCustomLinks(filters?: any) { + const path = URL.format({ + pathname: `/api/apm/settings/custom_links`, + query: filters, + }); + return supertestRead.get(path).set('kbn-xsrf', 'foo'); + } + + async function createCustomLink(customLink: CustomLink) { + log.debug('creating configuration', customLink); + const res = await supertestWrite + .post(`/api/apm/settings/custom_links`) + .send(customLink) + .set('kbn-xsrf', 'foo'); + + throwOnError(res); + + return res; + } + + async function updateCustomLink(id: string, customLink: CustomLink) { + log.debug('updating configuration', id, customLink); + const res = await supertestWrite + .put(`/api/apm/settings/custom_links/${id}`) + .send(customLink) + .set('kbn-xsrf', 'foo'); + + throwOnError(res); + + return res; + } + + async function deleteCustomLink(id: string) { + log.debug('deleting configuration', id); + const res = await supertestWrite + .delete(`/api/apm/settings/custom_links/${id}`) + .set('kbn-xsrf', 'foo'); + + throwOnError(res); + + return res; + } + + function throwOnError(res: any) { + const { statusCode, req, body } = res; + if (statusCode !== 200) { + throw new Error(` + Endpoint: ${req.method} ${req.path} + Service: ${JSON.stringify(res.request._data.service)} + Status code: ${statusCode} + Response: ${body.message}`); + } + } +} diff --git a/x-pack/test/apm_api_integration/basic/tests/traces/__snapshots__/top_traces.snap b/x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.snap similarity index 99% rename from x-pack/test/apm_api_integration/basic/tests/traces/__snapshots__/top_traces.snap rename to x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.snap index 56e82d752dccd..c8104b9858027 100644 --- a/x-pack/test/apm_api_integration/basic/tests/traces/__snapshots__/top_traces.snap +++ b/x-pack/test/apm_api_integration/tests/traces/__snapshots__/top_traces.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM specs (basic) Traces Top traces when data is loaded returns the correct buckets 1`] = ` +exports[`APM API tests basic apm_8.0.0 Top traces when data is loaded returns the correct buckets 1`] = ` Array [ Object { "averageResponseTime": 1733, diff --git a/x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts b/x-pack/test/apm_api_integration/tests/traces/top_traces.ts similarity index 81% rename from x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts rename to x-pack/test/apm_api_integration/tests/traces/top_traces.ts index 2ce3ba3838292..4754b3faa20ad 100644 --- a/x-pack/test/apm_api_integration/basic/tests/traces/top_traces.ts +++ b/x-pack/test/apm_api_integration/tests/traces/top_traces.ts @@ -5,12 +5,12 @@ */ import expect from '@kbn/expect'; import { sortBy } from 'lodash'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; @@ -20,28 +20,28 @@ export default function ApiTest({ getService }: FtrProviderContext) { const end = encodeURIComponent(metadata.end); const uiFilters = encodeURIComponent(JSON.stringify({})); - describe('Top traces', () => { - describe('when data is not loaded ', () => { - it('handles empty state', async () => { - const response = await supertest.get( - `/api/apm/traces?start=${start}&end=${end}&uiFilters=${uiFilters}` - ); + registry.when('Top traces when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles empty state', async () => { + const response = await supertest.get( + `/api/apm/traces?start=${start}&end=${end}&uiFilters=${uiFilters}` + ); - expect(response.status).to.be(200); - expect(response.body.items.length).to.be(0); - expect(response.body.isAggregationAccurate).to.be(true); - }); + expect(response.status).to.be(200); + expect(response.body.items.length).to.be(0); + expect(response.body.isAggregationAccurate).to.be(true); }); + }); - describe('when data is loaded', () => { + registry.when( + 'Top traces when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { let response: any; before(async () => { - await esArchiver.load(archiveName); response = await supertest.get( `/api/apm/traces?start=${start}&end=${end}&uiFilters=${uiFilters}` ); }); - after(() => esArchiver.unload(archiveName)); it('returns the correct status code', async () => { expect(response.status).to.be(200); @@ -116,6 +116,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { ] `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/breakdown.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/breakdown.snap similarity index 98% rename from x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/breakdown.snap rename to x-pack/test/apm_api_integration/tests/transactions/__snapshots__/breakdown.snap index 25aa68d2a86b1..cfdd27b594956 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/breakdown.snap +++ b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/breakdown.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM specs (basic) Transactions Breakdown when data is loaded returns the transaction breakdown for a service 1`] = ` +exports[`APM API tests basic apm_8.0.0 when data is loaded returns the transaction breakdown for a service 1`] = ` Object { "timeseries": Array [ Object { @@ -1019,7 +1019,7 @@ Object { } `; -exports[`APM specs (basic) Transactions Breakdown when data is loaded returns the transaction breakdown for a transaction group 9`] = ` +exports[`APM API tests basic apm_8.0.0 when data is loaded returns the transaction breakdown for a transaction group 9`] = ` Array [ Object { "x": 1607435850000, diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/error_rate.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/error_rate.snap similarity index 95% rename from x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/error_rate.snap rename to x-pack/test/apm_api_integration/tests/transactions/__snapshots__/error_rate.snap index 3b67a86ba84e8..d97d39cda1b8d 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/error_rate.snap +++ b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/error_rate.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM specs (basic) Transactions Error rate when data is loaded returns the transaction error rate has the correct error rate 1`] = ` +exports[`APM API tests basic apm_8.0.0 Error rate when data is loaded returns the transaction error rate has the correct error rate 1`] = ` Array [ Object { "x": 1607435850000, diff --git a/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap new file mode 100644 index 0000000000000..a384ca2c9364e --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/latency.snap @@ -0,0 +1,46 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`APM API tests trial apm_8.0.0 Transaction latency with a trial license when data is loaded when not defined environments seleted should return the correct anomaly boundaries 1`] = ` +Array [ + Object { + "x": 1607436000000, + "y": 0, + "y0": 0, + }, + Object { + "x": 1607436900000, + "y": 0, + "y0": 0, + }, +] +`; + +exports[`APM API tests trial apm_8.0.0 Transaction latency with a trial license when data is loaded with environment selected and empty kuery filter should return a non-empty anomaly series 1`] = ` +Array [ + Object { + "x": 1607436000000, + "y": 1625128.56211579, + "y0": 7533.02707532227, + }, + Object { + "x": 1607436900000, + "y": 1660982.24115757, + "y0": 5732.00699123528, + }, +] +`; + +exports[`APM API tests trial apm_8.0.0 Transaction latency with a trial license when data is loaded with environment selected in uiFilters should return a non-empty anomaly series 1`] = ` +Array [ + Object { + "x": 1607436000000, + "y": 1625128.56211579, + "y0": 7533.02707532227, + }, + Object { + "x": 1607436900000, + "y": 1660982.24115757, + "y0": 5732.00699123528, + }, +] +`; diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/top_transaction_groups.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/top_transaction_groups.snap similarity index 96% rename from x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/top_transaction_groups.snap rename to x-pack/test/apm_api_integration/tests/transactions/__snapshots__/top_transaction_groups.snap index 473305f3e39af..67a02e416de51 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/top_transaction_groups.snap +++ b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/top_transaction_groups.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`APM specs (basic) Transactions Top transaction groups when data is loaded returns the correct buckets (when ignoring samples) 1`] = ` +exports[`APM API tests basic apm_8.0.0 Top transaction groups when data is loaded returns the correct buckets (when ignoring samples) 1`] = ` Array [ Object { "averageResponseTime": 2722.75, diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/transaction_charts.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/transaction_charts.snap similarity index 100% rename from x-pack/test/apm_api_integration/basic/tests/transactions/__snapshots__/transaction_charts.snap rename to x-pack/test/apm_api_integration/tests/transactions/__snapshots__/transaction_charts.snap diff --git a/x-pack/test/apm_api_integration/trial/tests/transactions/__snapshots__/transactions_charts.snap b/x-pack/test/apm_api_integration/tests/transactions/__snapshots__/transactions_charts.snap similarity index 100% rename from x-pack/test/apm_api_integration/trial/tests/transactions/__snapshots__/transactions_charts.snap rename to x-pack/test/apm_api_integration/tests/transactions/__snapshots__/transactions_charts.snap diff --git a/x-pack/test/apm_api_integration/tests/transactions/breakdown.ts b/x-pack/test/apm_api_integration/tests/transactions/breakdown.ts new file mode 100644 index 0000000000000..4a9dcf9273003 --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/transactions/breakdown.ts @@ -0,0 +1,118 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import expect from '@kbn/expect'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + const archiveName = 'apm_8.0.0'; + const metadata = archives_metadata[archiveName]; + + const start = encodeURIComponent(metadata.start); + const end = encodeURIComponent(metadata.end); + const transactionType = 'request'; + const transactionName = 'GET /api'; + const uiFilters = encodeURIComponent(JSON.stringify({})); + + registry.when('Breakdown when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles the empty state', async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}` + ); + expect(response.status).to.be(200); + expect(response.body).to.eql({ timeseries: [] }); + }); + }); + + registry.when('when data is loaded', { config: 'basic', archives: [archiveName] }, () => { + it('returns the transaction breakdown for a service', async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}` + ); + + expect(response.status).to.be(200); + expectSnapshot(response.body).toMatch(); + }); + it('returns the transaction breakdown for a transaction group', async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}&transactionName=${transactionName}` + ); + + expect(response.status).to.be(200); + + const { timeseries } = response.body; + + const numberOfSeries = timeseries.length; + + expectSnapshot(numberOfSeries).toMatchInline(`1`); + + const { title, color, type, data, hideLegend, legendValue } = timeseries[0]; + + const nonNullDataPoints = data.filter((y: number | null) => y !== null); + + expectSnapshot(nonNullDataPoints.length).toMatchInline(`61`); + + expectSnapshot( + data.slice(0, 5).map(({ x, y }: { x: number; y: number | null }) => { + return { + x: new Date(x ?? NaN).toISOString(), + y, + }; + }) + ).toMatchInline(` + Array [ + Object { + "x": "2020-12-08T13:57:30.000Z", + "y": null, + }, + Object { + "x": "2020-12-08T13:58:00.000Z", + "y": null, + }, + Object { + "x": "2020-12-08T13:58:30.000Z", + "y": 1, + }, + Object { + "x": "2020-12-08T13:59:00.000Z", + "y": 1, + }, + Object { + "x": "2020-12-08T13:59:30.000Z", + "y": null, + }, + ] + `); + + expectSnapshot(title).toMatchInline(`"app"`); + expectSnapshot(color).toMatchInline(`"#54b399"`); + expectSnapshot(type).toMatchInline(`"areaStacked"`); + expectSnapshot(hideLegend).toMatchInline(`false`); + expectSnapshot(legendValue).toMatchInline(`"100%"`); + + expectSnapshot(data).toMatch(); + }); + it('returns the transaction breakdown sorted by name', async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-node/transaction/charts/breakdown?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}` + ); + + expect(response.status).to.be(200); + expectSnapshot(response.body.timeseries.map((serie: { title: string }) => serie.title)) + .toMatchInline(` + Array [ + "app", + "http", + "postgresql", + "redis", + ] + `); + }); + }); +} diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/distribution.ts b/x-pack/test/apm_api_integration/tests/transactions/distribution.ts similarity index 85% rename from x-pack/test/apm_api_integration/basic/tests/transactions/distribution.ts rename to x-pack/test/apm_api_integration/tests/transactions/distribution.ts index 890b6af728d5a..5d918b479111f 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/distribution.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/distribution.ts @@ -6,12 +6,12 @@ import expect from '@kbn/expect'; import qs from 'querystring'; import { isEmpty } from 'lodash'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; @@ -24,8 +24,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { transactionType: 'request', })}`; - describe('Transaction groups distribution', () => { - describe('when data is not loaded ', () => { + registry.when( + 'Transaction groups distribution when data is not loaded', + { config: 'basic', archives: [] }, + () => { it('handles empty state', async () => { const response = await supertest.get(url); @@ -34,15 +36,17 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.body.noHits).to.be(true); expect(response.body.buckets.length).to.be(0); }); - }); + } + ); - describe('when data is loaded', () => { + registry.when( + 'Transaction groups distribution when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { let response: any; before(async () => { - await esArchiver.load(archiveName); response = await supertest.get(url); }); - after(() => esArchiver.unload(archiveName)); it('returns the correct metadata', () => { expect(response.status).to.be(200); @@ -91,6 +95,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { ] `); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/error_rate.ts b/x-pack/test/apm_api_integration/tests/transactions/error_rate.ts similarity index 71% rename from x-pack/test/apm_api_integration/basic/tests/transactions/error_rate.ts rename to x-pack/test/apm_api_integration/tests/transactions/error_rate.ts index 22d9a7eba7fbf..487f2efbae400 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/error_rate.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/error_rate.ts @@ -6,12 +6,12 @@ import expect from '@kbn/expect'; import { first, last } from 'lodash'; import { format } from 'url'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; @@ -20,28 +20,27 @@ export default function ApiTest({ getService }: FtrProviderContext) { const uiFilters = '{}'; const transactionType = 'request'; - describe('Error rate', () => { - const url = format({ - pathname: '/api/apm/services/opbeans-java/transactions/charts/error_rate', - query: { start, end, uiFilters, transactionType }, - }); + const url = format({ + pathname: '/api/apm/services/opbeans-java/transactions/charts/error_rate', + query: { start, end, uiFilters, transactionType }, + }); - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get(url); - expect(response.status).to.be(200); + registry.when('Error rate when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles the empty state', async () => { + const response = await supertest.get(url); + expect(response.status).to.be(200); - expect(response.body.noHits).to.be(true); + expect(response.body.noHits).to.be(true); - expect(response.body.transactionErrorRate.length).to.be(0); - expect(response.body.average).to.be(null); - }); + expect(response.body.transactionErrorRate.length).to.be(0); + expect(response.body.average).to.be(null); }); + }); - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - + registry.when( + 'Error rate when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { describe('returns the transaction error rate', () => { let errorRateResponse: { transactionErrorRate: Array<{ x: number; y: number | null }>; @@ -89,6 +88,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expectSnapshot(errorRateResponse.transactionErrorRate).toMatch(); }); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/tests/transactions/latency.ts b/x-pack/test/apm_api_integration/tests/transactions/latency.ts new file mode 100644 index 0000000000000..c860b0e75495c --- /dev/null +++ b/x-pack/test/apm_api_integration/tests/transactions/latency.ts @@ -0,0 +1,243 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { PromiseReturnType } from '../../../../plugins/observability/typings/common'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../common/registry'; + +export default function ApiTest({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + const archiveName = 'apm_8.0.0'; + + const range = archives_metadata[archiveName]; + + // url parameters + const start = encodeURIComponent(range.start); + const end = encodeURIComponent(range.end); + + registry.when( + 'Latency with a basic license when data is not loaded ', + { config: 'basic', archives: [] }, + () => { + const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'testing' })); + it('returns 400 when latencyAggregationType is not informed', async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request` + ); + + expect(response.status).to.be(400); + }); + + it('returns 400 when transactionType is not informed', async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&latencyAggregationType=avg` + ); + + expect(response.status).to.be(400); + }); + + it('handles the empty state', async () => { + const response = await supertest.get( + `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&latencyAggregationType=avg&transactionType=request` + ); + + expect(response.status).to.be(200); + + expect(response.body.overallAvgDuration).to.be(null); + expect(response.body.latencyTimeseries.length).to.be(0); + }); + } + ); + + registry.when( + 'Latency with a basic license when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { + let response: PromiseReturnType; + + const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'testing' })); + + describe('average latency type', () => { + before(async () => { + response = await supertest.get( + `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=avg` + ); + }); + + it('returns average duration and timeseries', async () => { + expect(response.status).to.be(200); + expect(response.body.overallAvgDuration).not.to.be(null); + expect(response.body.latencyTimeseries.length).to.be.eql(61); + }); + }); + + describe('95th percentile latency type', () => { + before(async () => { + response = await supertest.get( + `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=p95` + ); + }); + + it('returns average duration and timeseries', async () => { + expect(response.status).to.be(200); + expect(response.body.overallAvgDuration).not.to.be(null); + expect(response.body.latencyTimeseries.length).to.be.eql(61); + }); + }); + + describe('99th percentile latency type', () => { + before(async () => { + response = await supertest.get( + `/api/apm/services/opbeans-node/transactions/charts/latency?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=request&latencyAggregationType=p99` + ); + }); + + it('returns average duration and timeseries', async () => { + expect(response.status).to.be(200); + expect(response.body.overallAvgDuration).not.to.be(null); + expect(response.body.latencyTimeseries.length).to.be.eql(61); + }); + }); + } + ); + + registry.when( + 'Transaction latency with a trial license when data is loaded', + { config: 'trial', archives: [archiveName] }, + () => { + let response: PromiseReturnType; + + const transactionType = 'request'; + + describe('without environment', () => { + const uiFilters = encodeURIComponent(JSON.stringify({})); + before(async () => { + response = await supertest.get( + `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` + ); + }); + it('should return an error response', () => { + expect(response.status).to.eql(400); + }); + }); + + describe('without uiFilters', () => { + before(async () => { + response = await supertest.get( + `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&latencyAggregationType=avg` + ); + }); + it('should return an error response', () => { + expect(response.status).to.eql(400); + }); + }); + + describe('with environment selected in uiFilters', () => { + const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'production' })); + before(async () => { + response = await supertest.get( + `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` + ); + }); + + it('should have a successful response', () => { + expect(response.status).to.eql(200); + }); + + it('should return the ML job id for anomalies of the selected environment', () => { + expect(response.body).to.have.property('anomalyTimeseries'); + expect(response.body.anomalyTimeseries).to.have.property('jobId'); + expectSnapshot(response.body.anomalyTimeseries.jobId).toMatchInline( + `"apm-production-1369-high_mean_transaction_duration"` + ); + }); + + it('should return a non-empty anomaly series', () => { + expect(response.body).to.have.property('anomalyTimeseries'); + expect(response.body.anomalyTimeseries.anomalyBoundaries?.length).to.be.greaterThan(0); + expectSnapshot(response.body.anomalyTimeseries.anomalyBoundaries).toMatch(); + }); + }); + + describe('when not defined environments seleted', () => { + const uiFilters = encodeURIComponent( + JSON.stringify({ environment: 'ENVIRONMENT_NOT_DEFINED' }) + ); + before(async () => { + response = await supertest.get( + `/api/apm/services/opbeans-python/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` + ); + }); + + it('should have a successful response', () => { + expect(response.status).to.eql(200); + }); + + it('should return the ML job id for anomalies with no defined environment', () => { + expect(response.body).to.have.property('anomalyTimeseries'); + expect(response.body.anomalyTimeseries).to.have.property('jobId'); + expectSnapshot(response.body.anomalyTimeseries.jobId).toMatchInline( + `"apm-environment_not_defined-5626-high_mean_transaction_duration"` + ); + }); + + it('should return the correct anomaly boundaries', () => { + expect(response.body).to.have.property('anomalyTimeseries'); + expectSnapshot(response.body.anomalyTimeseries.anomalyBoundaries).toMatch(); + }); + }); + + describe('with all environments selected', () => { + const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'ENVIRONMENT_ALL' })); + before(async () => { + response = await supertest.get( + `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` + ); + }); + + it('should have a successful response', () => { + expect(response.status).to.eql(200); + }); + + it('should not return anomaly timeseries data', () => { + expect(response.body).to.not.have.property('anomalyTimeseries'); + }); + }); + + describe('with environment selected and empty kuery filter', () => { + const uiFilters = encodeURIComponent( + JSON.stringify({ kuery: '', environment: 'production' }) + ); + before(async () => { + response = await supertest.get( + `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` + ); + }); + + it('should have a successful response', () => { + expect(response.status).to.eql(200); + }); + + it('should return the ML job id for anomalies of the selected environment', () => { + expect(response.body).to.have.property('anomalyTimeseries'); + expect(response.body.anomalyTimeseries).to.have.property('jobId'); + expectSnapshot(response.body.anomalyTimeseries.jobId).toMatchInline( + `"apm-production-1369-high_mean_transaction_duration"` + ); + }); + + it('should return a non-empty anomaly series', () => { + expect(response.body).to.have.property('anomalyTimeseries'); + expect(response.body.anomalyTimeseries.anomalyBoundaries?.length).to.be.greaterThan(0); + expectSnapshot(response.body.anomalyTimeseries.anomalyBoundaries).toMatch(); + }); + }); + } + ); +} diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/throughput.ts b/x-pack/test/apm_api_integration/tests/transactions/throughput.ts similarity index 54% rename from x-pack/test/apm_api_integration/basic/tests/transactions/throughput.ts rename to x-pack/test/apm_api_integration/tests/transactions/throughput.ts index 1013f3a19d71f..475bef4e9b549 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/throughput.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/throughput.ts @@ -5,13 +5,13 @@ */ import expect from '@kbn/expect'; import url from 'url'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { PromiseReturnType } from '../../../../plugins/observability/typings/common'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; @@ -20,31 +20,30 @@ export default function ApiTest({ getService }: FtrProviderContext) { const { start, end } = metadata; const uiFilters = JSON.stringify({ environment: 'testing' }); - describe('Throughput', () => { - describe('when data is not loaded ', () => { - it('handles the empty state', async () => { - const response = await supertest.get( - url.format({ - pathname: `/api/apm/services/opbeans-node/transactions/charts/throughput`, - query: { - start, - end, - uiFilters, - transactionType: 'request', - }, - }) - ); + registry.when('Throughput when data is not loaded', { config: 'basic', archives: [] }, () => { + it('handles the empty state', async () => { + const response = await supertest.get( + url.format({ + pathname: `/api/apm/services/opbeans-node/transactions/charts/throughput`, + query: { + start, + end, + uiFilters, + transactionType: 'request', + }, + }) + ); - expect(response.status).to.be(200); + expect(response.status).to.be(200); - expect(response.body.throughputTimeseries.length).to.be(0); - }); + expect(response.body.throughputTimeseries.length).to.be(0); }); + }); - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - + registry.when( + 'Throughput when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { let response: PromiseReturnType; before(async () => { @@ -66,6 +65,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.body.throughputTimeseries.length).to.be.greaterThan(0); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/top_transaction_groups.ts b/x-pack/test/apm_api_integration/tests/transactions/top_transaction_groups.ts similarity index 80% rename from x-pack/test/apm_api_integration/basic/tests/transactions/top_transaction_groups.ts rename to x-pack/test/apm_api_integration/tests/transactions/top_transaction_groups.ts index dac36ae8b3303..70afb2ee384c4 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/top_transaction_groups.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/top_transaction_groups.ts @@ -5,8 +5,9 @@ */ import expect from '@kbn/expect'; import { sortBy } from 'lodash'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; +import archives_metadata from '../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import { registry } from '../../common/registry'; function sortTransactionGroups(items: any[]) { return sortBy(items, 'impact'); @@ -14,7 +15,6 @@ function sortTransactionGroups(items: any[]) { export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const metadata = archives_metadata[archiveName]; @@ -25,8 +25,10 @@ export default function ApiTest({ getService }: FtrProviderContext) { const uiFilters = encodeURIComponent(JSON.stringify({})); const transactionType = 'request'; - describe('Top transaction groups', () => { - describe('when data is not loaded ', () => { + registry.when( + 'Top transaction groups when data is not loaded', + { config: 'basic', archives: [] }, + () => { it('handles empty state', async () => { const response = await supertest.get( `/api/apm/services/opbeans-node/transactions/groups?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}` @@ -37,17 +39,19 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(response.body.isAggregationAccurate).to.be(true); expect(response.body.items.length).to.be(0); }); - }); + } + ); - describe('when data is loaded', () => { + registry.when( + 'Top transaction groups when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { let response: any; before(async () => { - await esArchiver.load(archiveName); response = await supertest.get( `/api/apm/services/opbeans-node/transactions/groups?start=${start}&end=${end}&uiFilters=${uiFilters}&transactionType=${transactionType}` ); }); - after(() => esArchiver.unload(archiveName)); it('returns the correct metadata', () => { expect(response.status).to.be(200); @@ -62,6 +66,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { it('returns the correct buckets (when ignoring samples)', async () => { expectSnapshot(sortTransactionGroups(response.body.items)).toMatch(); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_overview.ts b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_overview.ts similarity index 94% rename from x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_overview.ts rename to x-pack/test/apm_api_integration/tests/transactions/transactions_groups_overview.ts index be978b2a82618..8818aaccdec01 100644 --- a/x-pack/test/apm_api_integration/basic/tests/transactions/transactions_groups_overview.ts +++ b/x-pack/test/apm_api_integration/tests/transactions/transactions_groups_overview.ts @@ -7,18 +7,20 @@ import expect from '@kbn/expect'; import { pick, uniqBy, sortBy } from 'lodash'; import url from 'url'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; -import archives from '../../../common/fixtures/es_archiver/archives_metadata'; +import { FtrProviderContext } from '../../common/ftr_provider_context'; +import archives from '../../common/fixtures/es_archiver/archives_metadata'; +import { registry } from '../../common/registry'; export default function ApiTest({ getService }: FtrProviderContext) { const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); const archiveName = 'apm_8.0.0'; const { start, end } = archives[archiveName]; - describe('Transactions groups overview', () => { - describe('when data is not loaded', () => { + registry.when( + 'Transaction groups overview when data is not loaded', + { config: 'basic', archives: [] }, + () => { it('handles the empty state', async () => { const response = await supertest.get( url.format({ @@ -45,12 +47,13 @@ export default function ApiTest({ getService }: FtrProviderContext) { isAggregationAccurate: true, }); }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); + } + ); + registry.when( + 'Top transaction groups when data is loaded', + { config: 'basic', archives: [archiveName] }, + () => { it('returns the correct data', async () => { const response = await supertest.get( url.format({ @@ -264,6 +267,6 @@ export default function ApiTest({ getService }: FtrProviderContext) { expect(uniqBy(items, 'name').length).to.eql(totalItems); }); - }); - }); + } + ); } diff --git a/x-pack/test/apm_api_integration/trial/config.ts b/x-pack/test/apm_api_integration/trial/config.ts index 94a6f808603c1..ba9ef4ca42597 100644 --- a/x-pack/test/apm_api_integration/trial/config.ts +++ b/x-pack/test/apm_api_integration/trial/config.ts @@ -4,10 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createTestConfig } from '../common/config'; +import { configs } from '../configs'; -export default createTestConfig({ - license: 'trial', - name: 'X-Pack APM API integration tests (trial)', - testFiles: [require.resolve('./tests')], -}); +export default configs.trial; diff --git a/x-pack/test/apm_api_integration/trial/tests/correlations/slow_transactions.ts b/x-pack/test/apm_api_integration/trial/tests/correlations/slow_transactions.ts deleted file mode 100644 index 9a868373292f6..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/correlations/slow_transactions.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 expect from '@kbn/expect'; -import { format } from 'url'; -import { APIReturnType } from '../../../../../plugins/apm/public/services/rest/createCallApmApi'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - const archiveName = 'apm_8.0.0'; - const range = archives_metadata[archiveName]; - - describe('Slow durations', () => { - const url = format({ - pathname: `/api/apm/correlations/slow_transactions`, - query: { - start: range.start, - end: range.end, - durationPercentile: 95, - fieldNames: - 'user.username,user.id,host.ip,user_agent.name,kubernetes.pod.uuid,url.domain,container.id,service.node.name', - }, - }); - - describe('when data is not loaded', () => { - it('handles the empty state', async () => { - const response = await supertest.get(url); - - expect(response.status).to.be(200); - expect(response.body.response).to.be(undefined); - }); - }); - - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - describe('making request with default args', () => { - type ResponseBody = APIReturnType<'GET /api/apm/correlations/slow_transactions'>; - let response: { - status: number; - body: NonNullable; - }; - - before(async () => { - response = await supertest.get(url); - }); - - it('returns successfully', () => { - expect(response.status).to.eql(200); - }); - - it('returns significant terms', () => { - const sorted = response.body?.significantTerms?.sort(); - expectSnapshot(sorted?.map((term) => term.fieldName)).toMatchInline(` - Array [ - "user_agent.name", - "url.domain", - "host.ip", - "service.node.name", - "container.id", - "url.domain", - "user_agent.name", - ] - `); - }); - - it('returns a distribution per term', () => { - expectSnapshot(response.body?.significantTerms?.map((term) => term.distribution.length)) - .toMatchInline(` - Array [ - 11, - 11, - 11, - 11, - 11, - 11, - 11, - ] - `); - }); - - it('returns overall distribution', () => { - expectSnapshot(response.body?.overall?.distribution.length).toMatchInline(`11`); - }); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/csm_services.ts b/x-pack/test/apm_api_integration/trial/tests/csm/csm_services.ts deleted file mode 100644 index 05c6439508ece..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/csm/csm_services.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function rumServicesApiTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - describe('CSM Services', () => { - describe('when there is no data', () => { - it('returns empty list', async () => { - const response = await supertest.get( - '/api/apm/rum-client/services?start=2020-06-28T10%3A24%3A46.055Z&end=2020-07-29T10%3A24%3A46.055Z&uiFilters=%7B%22agentName%22%3A%5B%22js-base%22%2C%22rum-js%22%5D%7D' - ); - - expect(response.status).to.be(200); - expect(response.body).to.eql([]); - }); - }); - - describe('when there is data', () => { - before(async () => { - await esArchiver.load('8.0.0'); - await esArchiver.load('rum_8.0.0'); - }); - after(async () => { - await esArchiver.unload('8.0.0'); - await esArchiver.unload('rum_8.0.0'); - }); - - it('returns rum services list', async () => { - const response = await supertest.get( - '/api/apm/rum-client/services?start=2020-06-28T10%3A24%3A46.055Z&end=2020-07-29T10%3A24%3A46.055Z&uiFilters=%7B%22agentName%22%3A%5B%22js-base%22%2C%22rum-js%22%5D%7D' - ); - - expect(response.status).to.be(200); - - expectSnapshot(response.body).toMatchInline(`Array []`); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/page_load_dist.ts b/x-pack/test/apm_api_integration/trial/tests/csm/page_load_dist.ts deleted file mode 100644 index fa5fcbcb6a7c3..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/csm/page_load_dist.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function rumServicesApiTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - describe('UX page load dist', () => { - describe('when there is no data', () => { - it('returns empty list', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-load-distribution?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' - ); - - expect(response.status).to.be(200); - expectSnapshot(response.body).toMatch(); - }); - it('returns empty list with breakdowns', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-load-distribution/breakdown?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&breakdown=Browser' - ); - - expect(response.status).to.be(200); - expectSnapshot(response.body).toMatch(); - }); - }); - - describe('when there is data', () => { - before(async () => { - await esArchiver.load('8.0.0'); - await esArchiver.load('rum_8.0.0'); - }); - after(async () => { - await esArchiver.unload('8.0.0'); - await esArchiver.unload('rum_8.0.0'); - }); - - it('returns page load distribution', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-load-distribution?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D' - ); - - expect(response.status).to.be(200); - - expectSnapshot(response.body).toMatch(); - }); - it('returns page load distribution with breakdown', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-load-distribution/breakdown?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&breakdown=Browser' - ); - - expect(response.status).to.be(200); - - expectSnapshot(response.body).toMatch(); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts b/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts deleted file mode 100644 index 5d910862843d5..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/csm/page_views.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function rumServicesApiTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - describe('CSM page views', () => { - describe('when there is no data', () => { - it('returns empty list', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D' - ); - - expect(response.status).to.be(200); - expectSnapshot(response.body).toMatch(); - }); - it('returns empty list with breakdowns', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&breakdowns=%7B%22name%22%3A%22Browser%22%2C%22fieldName%22%3A%22user_agent.name%22%2C%22type%22%3A%22category%22%7D' - ); - - expect(response.status).to.be(200); - expectSnapshot(response.body).toMatch(); - }); - }); - - describe('when there is data', () => { - before(async () => { - await esArchiver.load('8.0.0'); - await esArchiver.load('rum_8.0.0'); - }); - after(async () => { - await esArchiver.unload('8.0.0'); - await esArchiver.unload('rum_8.0.0'); - }); - - it('returns page views', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D' - ); - - expect(response.status).to.be(200); - - expectSnapshot(response.body).toMatch(); - }); - it('returns page views with breakdown', async () => { - const response = await supertest.get( - '/api/apm/rum-client/page-view-trends?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&breakdowns=%7B%22name%22%3A%22Browser%22%2C%22fieldName%22%3A%22user_agent.name%22%2C%22type%22%3A%22category%22%7D' - ); - - expect(response.status).to.be(200); - - expectSnapshot(response.body).toMatch(); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/csm/url_search.ts b/x-pack/test/apm_api_integration/trial/tests/csm/url_search.ts deleted file mode 100644 index 961c783434639..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/csm/url_search.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function rumServicesApiTests({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - describe('CSM url search api', () => { - describe('when there is no data', () => { - it('returns empty list', async () => { - const response = await supertest.get( - '/api/apm/rum-client/url-search?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-14T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22elastic-co-rum-test%22%5D%7D&percentile=50' - ); - - expect(response.status).to.be(200); - expectSnapshot(response.body).toMatchInline(` - Object { - "items": Array [], - "total": 0, - } - `); - }); - }); - - describe('when there is data', () => { - before(async () => { - await esArchiver.load('8.0.0'); - await esArchiver.load('rum_8.0.0'); - }); - after(async () => { - await esArchiver.unload('8.0.0'); - await esArchiver.unload('rum_8.0.0'); - }); - - it('returns top urls when no query', async () => { - const response = await supertest.get( - '/api/apm/rum-client/url-search?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&percentile=50' - ); - - expect(response.status).to.be(200); - - expectSnapshot(response.body).toMatchInline(` - Object { - "items": Array [ - Object { - "count": 5, - "pld": 4924000, - "url": "http://localhost:5601/nfw/app/csm?rangeFrom=now-15m&rangeTo=now&serviceName=kibana-frontend-8_0_0", - }, - Object { - "count": 1, - "pld": 2760000, - "url": "http://localhost:5601/nfw/app/home", - }, - ], - "total": 2, - } - `); - }); - - it('returns specific results against query', async () => { - const response = await supertest.get( - '/api/apm/rum-client/url-search?start=2020-09-07T20%3A35%3A54.654Z&end=2020-09-16T20%3A35%3A54.654Z&uiFilters=%7B%22serviceName%22%3A%5B%22kibana-frontend-8_0_0%22%5D%7D&urlQuery=csm&percentile=50' - ); - - expect(response.status).to.be(200); - - expectSnapshot(response.body).toMatchInline(` - Object { - "items": Array [ - Object { - "count": 5, - "pld": 4924000, - "url": "http://localhost:5601/nfw/app/csm?rangeFrom=now-15m&rangeTo=now&serviceName=kibana-frontend-8_0_0", - }, - ], - "total": 1, - } - `); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/index.ts b/x-pack/test/apm_api_integration/trial/tests/index.ts deleted file mode 100644 index c8ee858d9ceb0..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/index.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { FtrProviderContext } from '../../../api_integration/ftr_provider_context'; - -export default function observabilityApiIntegrationTests({ loadTestFile }: FtrProviderContext) { - describe('APM specs (trial)', function () { - this.tags('ciGroup1'); - - describe('Services', function () { - loadTestFile(require.resolve('./services/annotations')); - loadTestFile(require.resolve('./services/top_services.ts')); - }); - - describe('Transactions', function () { - loadTestFile(require.resolve('./transactions/latency')); - }); - - describe('Settings', function () { - loadTestFile(require.resolve('./settings/custom_link.ts')); - describe('Anomaly detection', function () { - loadTestFile(require.resolve('./settings/anomaly_detection/no_access_user')); - loadTestFile(require.resolve('./settings/anomaly_detection/read_user')); - loadTestFile(require.resolve('./settings/anomaly_detection/write_user')); - }); - }); - - describe('Service Maps', function () { - loadTestFile(require.resolve('./service_maps/service_maps')); - }); - - describe('CSM', function () { - loadTestFile(require.resolve('./csm/csm_services.ts')); - loadTestFile(require.resolve('./csm/web_core_vitals.ts')); - loadTestFile(require.resolve('./csm/long_task_metrics.ts')); - loadTestFile(require.resolve('./csm/url_search.ts')); - loadTestFile(require.resolve('./csm/page_views.ts')); - loadTestFile(require.resolve('./csm/js_errors.ts')); - loadTestFile(require.resolve('./csm/has_rum_data.ts')); - loadTestFile(require.resolve('./csm/page_load_dist.ts')); - }); - - describe('Correlations', function () { - loadTestFile(require.resolve('./correlations/slow_transactions')); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/services/top_services.ts b/x-pack/test/apm_api_integration/trial/tests/services/top_services.ts deleted file mode 100644 index e37d98b41b7af..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/services/top_services.ts +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { sortBy } from 'lodash'; -import { APIReturnType } from '../../../../../plugins/apm/public/services/rest/createCallApmApi'; -import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const supertestAsApmReadUserWithoutMlAccess = getService('supertestAsApmReadUserWithoutMlAccess'); - const esArchiver = getService('esArchiver'); - - const archiveName = 'apm_8.0.0'; - - const range = archives_metadata[archiveName]; - - // url parameters - const start = encodeURIComponent(range.start); - const end = encodeURIComponent(range.end); - - const uiFilters = encodeURIComponent(JSON.stringify({})); - - describe('APM Services Overview', () => { - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - describe('with the default APM read user', () => { - describe('and fetching a list of services', () => { - let response: { - status: number; - body: APIReturnType<'GET /api/apm/services'>; - }; - - before(async () => { - response = await supertest.get( - `/api/apm/services?start=${start}&end=${end}&uiFilters=${uiFilters}` - ); - }); - - it('the response is successful', () => { - expect(response.status).to.eql(200); - }); - - it('there is at least one service', () => { - expect(response.body.items.length).to.be.greaterThan(0); - }); - - it('some items have a health status set', () => { - // Under the assumption that the loaded archive has - // at least one APM ML job, and the time range is longer - // than 15m, at least one items should have a health status - // set. Note that we currently have a bug where healthy - // services report as unknown (so without any health status): - // https://github.com/elastic/kibana/issues/77083 - - const healthStatuses = sortBy(response.body.items, 'serviceName').map( - (item: any) => item.healthStatus - ); - - expect(healthStatuses.filter(Boolean).length).to.be.greaterThan(0); - - expectSnapshot(healthStatuses).toMatchInline(` - Array [ - "healthy", - "healthy", - "healthy", - "healthy", - "healthy", - "healthy", - "healthy", - "healthy", - "healthy", - ] - `); - }); - }); - }); - - describe('with a user that does not have access to ML', () => { - let response: PromiseReturnType; - before(async () => { - response = await supertestAsApmReadUserWithoutMlAccess.get( - `/api/apm/services?start=${start}&end=${end}&uiFilters=${uiFilters}` - ); - }); - - it('the response is successful', () => { - expect(response.status).to.eql(200); - }); - - it('there is at least one service', () => { - expect(response.body.items.length).to.be.greaterThan(0); - }); - - it('contains no health statuses', () => { - const definedHealthStatuses = response.body.items - .map((item: any) => item.healthStatus) - .filter(Boolean); - - expect(definedHealthStatuses.length).to.be(0); - }); - }); - - describe('and fetching a list of services with a filter', () => { - let response: PromiseReturnType; - before(async () => { - response = await supertest.get( - `/api/apm/services?start=${start}&end=${end}&uiFilters=${encodeURIComponent( - `{"kuery":"service.name:opbeans-java","environment":"ENVIRONMENT_ALL"}` - )}` - ); - }); - - it('does not return health statuses for services that are not found in APM data', () => { - expect(response.status).to.be(200); - - expect(response.body.items.length).to.be(1); - - expect(response.body.items[0].serviceName).to.be('opbeans-java'); - }); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/no_access_user.ts b/x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/no_access_user.ts deleted file mode 100644 index a917bdb3cea23..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/no_access_user.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; - -export default function apiTest({ getService }: FtrProviderContext) { - const noAccessUser = getService('supertestAsNoAccessUser'); - - function getJobs() { - return noAccessUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); - } - - function createJobs(environments: string[]) { - return noAccessUser - .post(`/api/apm/settings/anomaly-detection/jobs`) - .send({ environments }) - .set('kbn-xsrf', 'foo'); - } - - describe('when user does not have read access to ML', () => { - describe('when calling the endpoint for listing jobs', () => { - it('returns an error because the user does not have access', async () => { - const { body } = await getJobs(); - expect(body.statusCode).to.be(403); - expect(body.error).to.be('Forbidden'); - }); - }); - - describe('when calling create endpoint', () => { - it('returns an error because the user does not have access', async () => { - const { body } = await createJobs(['production', 'staging']); - expect(body.statusCode).to.be(403); - expect(body.error).to.be('Forbidden'); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/read_user.ts b/x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/read_user.ts deleted file mode 100644 index 2265c4dc0a41d..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/read_user.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; - -export default function apiTest({ getService }: FtrProviderContext) { - const apmReadUser = getService('supertestAsApmReadUser'); - - function getJobs() { - return apmReadUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); - } - - function createJobs(environments: string[]) { - return apmReadUser - .post(`/api/apm/settings/anomaly-detection/jobs`) - .send({ environments }) - .set('kbn-xsrf', 'foo'); - } - - describe('when user has read access to ML', () => { - describe('when calling the endpoint for listing jobs', () => { - it('returns a list of jobs', async () => { - const { body } = await getJobs(); - - expect(body.jobs.length).to.be(0); - expect(body.hasLegacyJobs).to.be(false); - }); - }); - - describe('when calling create endpoint', () => { - it('returns an error because the user does not have access', async () => { - const { body } = await createJobs(['production', 'staging']); - - expect(body.statusCode).to.be(403); - expect(body.error).to.be('Forbidden'); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/write_user.ts b/x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/write_user.ts deleted file mode 100644 index 720d66e1efcc8..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/settings/anomaly_detection/write_user.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { FtrProviderContext } from '../../../../common/ftr_provider_context'; - -export default function apiTest({ getService }: FtrProviderContext) { - const apmWriteUser = getService('supertestAsApmWriteUser'); - - function getJobs() { - return apmWriteUser.get(`/api/apm/settings/anomaly-detection/jobs`).set('kbn-xsrf', 'foo'); - } - - function createJobs(environments: string[]) { - return apmWriteUser - .post(`/api/apm/settings/anomaly-detection/jobs`) - .send({ environments }) - .set('kbn-xsrf', 'foo'); - } - - function deleteJobs(jobIds: string[]) { - return apmWriteUser.post(`/api/ml/jobs/delete_jobs`).send({ jobIds }).set('kbn-xsrf', 'foo'); - } - - describe('when user has write access to ML', () => { - after(async () => { - const res = await getJobs(); - const jobIds = res.body.jobs.map((job: any) => job.job_id); - await deleteJobs(jobIds); - }); - - describe('when calling the endpoint for listing jobs', () => { - it('returns a list of jobs', async () => { - const { body } = await getJobs(); - expect(body.jobs.length).to.be(0); - expect(body.hasLegacyJobs).to.be(false); - }); - }); - - describe('when calling create endpoint', () => { - it('creates two jobs', async () => { - await createJobs(['production', 'staging']); - - const { body } = await getJobs(); - expect(body.hasLegacyJobs).to.be(false); - expect(body.jobs.map((job: any) => job.environment)).to.eql(['production', 'staging']); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/settings/custom_link.ts b/x-pack/test/apm_api_integration/trial/tests/settings/custom_link.ts deleted file mode 100644 index bcfe8fce4b948..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/settings/custom_link.ts +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import URL from 'url'; -import expect from '@kbn/expect'; -import { CustomLink } from '../../../../../plugins/apm/common/custom_link/custom_link_types'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; - -export default function customLinksTests({ getService }: FtrProviderContext) { - const supertestRead = getService('supertest'); - const supertestWrite = getService('supertestAsApmWriteUser'); - const log = getService('log'); - const esArchiver = getService('esArchiver'); - - const archiveName = 'apm_8.0.0'; - - function searchCustomLinks(filters?: any) { - const path = URL.format({ - pathname: `/api/apm/settings/custom_links`, - query: filters, - }); - return supertestRead.get(path).set('kbn-xsrf', 'foo'); - } - - async function createCustomLink(customLink: CustomLink) { - log.debug('creating configuration', customLink); - const res = await supertestWrite - .post(`/api/apm/settings/custom_links`) - .send(customLink) - .set('kbn-xsrf', 'foo'); - - throwOnError(res); - - return res; - } - - async function updateCustomLink(id: string, customLink: CustomLink) { - log.debug('updating configuration', id, customLink); - const res = await supertestWrite - .put(`/api/apm/settings/custom_links/${id}`) - .send(customLink) - .set('kbn-xsrf', 'foo'); - - throwOnError(res); - - return res; - } - - async function deleteCustomLink(id: string) { - log.debug('deleting configuration', id); - const res = await supertestWrite - .delete(`/api/apm/settings/custom_links/${id}`) - .set('kbn-xsrf', 'foo'); - - throwOnError(res); - - return res; - } - - function throwOnError(res: any) { - const { statusCode, req, body } = res; - if (statusCode !== 200) { - throw new Error(` - Endpoint: ${req.method} ${req.path} - Service: ${JSON.stringify(res.request._data.service)} - Status code: ${statusCode} - Response: ${body.message}`); - } - } - - describe('custom links', () => { - before(async () => { - const customLink = { - url: 'https://elastic.co', - label: 'with filters', - filters: [ - { key: 'service.name', value: 'baz' }, - { key: 'transaction.type', value: 'qux' }, - ], - } as CustomLink; - await createCustomLink(customLink); - }); - it('fetches a custom link', async () => { - const { status, body } = await searchCustomLinks({ - 'service.name': 'baz', - 'transaction.type': 'qux', - }); - const { label, url, filters } = body[0]; - - expect(status).to.equal(200); - expect({ label, url, filters }).to.eql({ - label: 'with filters', - url: 'https://elastic.co', - filters: [ - { key: 'service.name', value: 'baz' }, - { key: 'transaction.type', value: 'qux' }, - ], - }); - }); - it('updates a custom link', async () => { - let { status, body } = await searchCustomLinks({ - 'service.name': 'baz', - 'transaction.type': 'qux', - }); - expect(status).to.equal(200); - await updateCustomLink(body[0].id, { - label: 'foo', - url: 'https://elastic.co?service.name={{service.name}}', - filters: [ - { key: 'service.name', value: 'quz' }, - { key: 'transaction.name', value: 'bar' }, - ], - }); - ({ status, body } = await searchCustomLinks({ - 'service.name': 'quz', - 'transaction.name': 'bar', - })); - const { label, url, filters } = body[0]; - expect(status).to.equal(200); - expect({ label, url, filters }).to.eql({ - label: 'foo', - url: 'https://elastic.co?service.name={{service.name}}', - filters: [ - { key: 'service.name', value: 'quz' }, - { key: 'transaction.name', value: 'bar' }, - ], - }); - }); - it('deletes a custom link', async () => { - let { status, body } = await searchCustomLinks({ - 'service.name': 'quz', - 'transaction.name': 'bar', - }); - expect(status).to.equal(200); - await deleteCustomLink(body[0].id); - ({ status, body } = await searchCustomLinks({ - 'service.name': 'quz', - 'transaction.name': 'bar', - })); - expect(status).to.equal(200); - expect(body).to.eql([]); - }); - - describe('transaction', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - it('fetches a transaction sample', async () => { - const response = await supertestRead.get( - '/api/apm/settings/custom_links/transaction?service.name=opbeans-java' - ); - expect(response.status).to.be(200); - expect(response.body.service.name).to.eql('opbeans-java'); - }); - }); - }); -} diff --git a/x-pack/test/apm_api_integration/trial/tests/transactions/__snapshots__/latency.snap b/x-pack/test/apm_api_integration/trial/tests/transactions/__snapshots__/latency.snap deleted file mode 100644 index 9475670387a08..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/transactions/__snapshots__/latency.snap +++ /dev/null @@ -1,46 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`APM specs (trial) Transactions Latency when data is loaded and fetching transaction charts with uiFilters when not defined environments seleted should return the correct anomaly boundaries 1`] = ` -Array [ - Object { - "x": 1607436000000, - "y": 0, - "y0": 0, - }, - Object { - "x": 1607436900000, - "y": 0, - "y0": 0, - }, -] -`; - -exports[`APM specs (trial) Transactions Latency when data is loaded and fetching transaction charts with uiFilters with environment selected and empty kuery filter should return a non-empty anomaly series 1`] = ` -Array [ - Object { - "x": 1607436000000, - "y": 1625128.56211579, - "y0": 7533.02707532227, - }, - Object { - "x": 1607436900000, - "y": 1660982.24115757, - "y0": 5732.00699123528, - }, -] -`; - -exports[`APM specs (trial) Transactions Latency when data is loaded and fetching transaction charts with uiFilters with environment selected in uiFilters should return a non-empty anomaly series 1`] = ` -Array [ - Object { - "x": 1607436000000, - "y": 1625128.56211579, - "y0": 7533.02707532227, - }, - Object { - "x": 1607436900000, - "y": 1660982.24115757, - "y0": 5732.00699123528, - }, -] -`; diff --git a/x-pack/test/apm_api_integration/trial/tests/transactions/latency.ts b/x-pack/test/apm_api_integration/trial/tests/transactions/latency.ts deleted file mode 100644 index e0b9559be7208..0000000000000 --- a/x-pack/test/apm_api_integration/trial/tests/transactions/latency.ts +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { PromiseReturnType } from '../../../../../plugins/observability/typings/common'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import archives_metadata from '../../../common/fixtures/es_archiver/archives_metadata'; - -export default function ApiTest({ getService }: FtrProviderContext) { - const supertest = getService('supertest'); - const esArchiver = getService('esArchiver'); - - const archiveName = 'apm_8.0.0'; - - const range = archives_metadata[archiveName]; - - // url parameters - const start = encodeURIComponent(range.start); - const end = encodeURIComponent(range.end); - const transactionType = 'request'; - - describe('Latency', () => { - describe('when data is loaded', () => { - before(() => esArchiver.load(archiveName)); - after(() => esArchiver.unload(archiveName)); - - describe('and fetching transaction charts with uiFilters', () => { - let response: PromiseReturnType; - - describe('without environment', () => { - const uiFilters = encodeURIComponent(JSON.stringify({})); - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` - ); - }); - it('should return an error response', () => { - expect(response.status).to.eql(400); - }); - }); - - describe('without uiFilters', () => { - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&latencyAggregationType=avg` - ); - }); - it('should return an error response', () => { - expect(response.status).to.eql(400); - }); - }); - - describe('with environment selected in uiFilters', () => { - const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'production' })); - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` - ); - }); - - it('should have a successful response', () => { - expect(response.status).to.eql(200); - }); - - it('should return the ML job id for anomalies of the selected environment', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expect(response.body.anomalyTimeseries).to.have.property('jobId'); - expectSnapshot(response.body.anomalyTimeseries.jobId).toMatchInline( - `"apm-production-1369-high_mean_transaction_duration"` - ); - }); - - it('should return a non-empty anomaly series', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expect(response.body.anomalyTimeseries.anomalyBoundaries?.length).to.be.greaterThan(0); - expectSnapshot(response.body.anomalyTimeseries.anomalyBoundaries).toMatch(); - }); - }); - - describe('when not defined environments seleted', () => { - const uiFilters = encodeURIComponent( - JSON.stringify({ environment: 'ENVIRONMENT_NOT_DEFINED' }) - ); - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-python/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` - ); - }); - - it('should have a successful response', () => { - expect(response.status).to.eql(200); - }); - - it('should return the ML job id for anomalies with no defined environment', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expect(response.body.anomalyTimeseries).to.have.property('jobId'); - expectSnapshot(response.body.anomalyTimeseries.jobId).toMatchInline( - `"apm-environment_not_defined-5626-high_mean_transaction_duration"` - ); - }); - - it('should return the correct anomaly boundaries', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expectSnapshot(response.body.anomalyTimeseries.anomalyBoundaries).toMatch(); - }); - }); - - describe('with all environments selected', () => { - const uiFilters = encodeURIComponent(JSON.stringify({ environment: 'ENVIRONMENT_ALL' })); - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` - ); - }); - - it('should have a successful response', () => { - expect(response.status).to.eql(200); - }); - - it('should not return anomaly timeseries data', () => { - expect(response.body).to.not.have.property('anomalyTimeseries'); - }); - }); - - describe('with environment selected and empty kuery filter', () => { - const uiFilters = encodeURIComponent( - JSON.stringify({ kuery: '', environment: 'production' }) - ); - before(async () => { - response = await supertest.get( - `/api/apm/services/opbeans-java/transactions/charts/latency?start=${start}&end=${end}&transactionType=${transactionType}&uiFilters=${uiFilters}&latencyAggregationType=avg` - ); - }); - - it('should have a successful response', () => { - expect(response.status).to.eql(200); - }); - - it('should return the ML job id for anomalies of the selected environment', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expect(response.body.anomalyTimeseries).to.have.property('jobId'); - expectSnapshot(response.body.anomalyTimeseries.jobId).toMatchInline( - `"apm-production-1369-high_mean_transaction_duration"` - ); - }); - - it('should return a non-empty anomaly series', () => { - expect(response.body).to.have.property('anomalyTimeseries'); - expect(response.body.anomalyTimeseries.anomalyBoundaries?.length).to.be.greaterThan(0); - expectSnapshot(response.body.anomalyTimeseries.anomalyBoundaries).toMatch(); - }); - }); - }); - }); - }); -} diff --git a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts index c3c7ecd0aba81..00a20abe367ae 100644 --- a/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts +++ b/x-pack/test/detection_engine_api_integration/security_and_spaces/tests/generating_signals.ts @@ -265,7 +265,7 @@ export default ({ getService }: FtrProviderContext) => { }; const { id } = await createRule(supertest, rule); await waitForRuleSuccessOrStatus(supertest, id); - await waitForSignalsToBePresent(supertest, 1, [id]); + await waitForSignalsToBePresent(supertest, 10, [id]); const signalsOpen = await getSignalsByRuleIds(supertest, ['eql-rule']); const sequenceSignal = signalsOpen.hits.hits.find( (signal) => signal._source.signal.depth === 2 diff --git a/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts b/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts index 112a855c61201..b5f55180419ef 100644 --- a/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts +++ b/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts @@ -29,7 +29,8 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const queryBar = getService('queryBar'); const savedQueryManagementComponent = getService('savedQueryManagementComponent'); - describe('dashboard feature controls security', () => { + // FLAKY: https://github.com/elastic/kibana/issues/86950 + describe.skip('dashboard feature controls security', () => { before(async () => { await esArchiver.load('dashboard/feature_controls/security'); await esArchiver.loadIfNeeded('logstash_functional'); diff --git a/x-pack/test/functional/apps/lens/smokescreen.ts b/x-pack/test/functional/apps/lens/smokescreen.ts index f2d91c2ae577f..88682d475146f 100644 --- a/x-pack/test/functional/apps/lens/smokescreen.ts +++ b/x-pack/test/functional/apps/lens/smokescreen.ts @@ -514,6 +514,28 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { ); }); + it('should transition from unique count to last value', async () => { + await PageObjects.visualize.navigateToNewVisualization(); + await PageObjects.visualize.clickVisType('lens'); + await PageObjects.lens.goToTimeRange(); + + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-empty-dimension', + operation: 'cardinality', + field: 'ip', + }); + await PageObjects.lens.configureDimension({ + dimension: 'lnsXY_yDimensionPanel > lns-dimensionTrigger', + operation: 'last_value', + field: 'bytes', + isPreviousIncompatible: true, + }); + + expect(await PageObjects.lens.getDimensionTriggerText('lnsXY_yDimensionPanel')).to.eql( + 'Last value of bytes' + ); + }); + it('should allow to change index pattern', async () => { await PageObjects.lens.switchFirstLayerIndexPattern('log*'); expect(await PageObjects.lens.getFirstLayerIndexPattern()).to.equal('log*'); diff --git a/x-pack/test/functional/apps/ml/data_visualizer/file_data_visualizer.ts b/x-pack/test/functional/apps/ml/data_visualizer/file_data_visualizer.ts index 531eba54f931d..10926a831d36b 100644 --- a/x-pack/test/functional/apps/ml/data_visualizer/file_data_visualizer.ts +++ b/x-pack/test/functional/apps/ml/data_visualizer/file_data_visualizer.ts @@ -112,6 +112,47 @@ export default function ({ getService }: FtrProviderContext) { fieldNameFiltersResultCount: 1, }, }, + { + suiteSuffix: 'with a file containing geo field', + filePath: path.join(__dirname, 'files_to_import', 'geo_file.csv'), + indexName: 'user-import_2', + createIndexPattern: false, + fieldTypeFilters: [ML_JOB_FIELD_TYPES.GEO_POINT], + fieldNameFilters: ['Coordinates'], + expected: { + results: { + title: 'geo_file.csv', + numberOfFields: 3, + }, + metricFields: [], + nonMetricFields: [ + { + fieldName: 'Context', + type: ML_JOB_FIELD_TYPES.UNKNOWN, + docCountFormatted: '0 (0%)', + exampleCount: 0, + }, + { + fieldName: 'Coordinates', + type: ML_JOB_FIELD_TYPES.GEO_POINT, + docCountFormatted: '13 (100%)', + exampleCount: 7, + }, + { + fieldName: 'Location', + type: ML_JOB_FIELD_TYPES.KEYWORD, + docCountFormatted: '13 (100%)', + exampleCount: 7, + }, + ], + visibleMetricFieldsCount: 0, + totalMetricFieldsCount: 0, + populatedFieldsCount: 3, + totalFieldsCount: 3, + fieldTypeFiltersResultCount: 1, + fieldNameFiltersResultCount: 1, + }, + }, ]; const testDataListNegative = [ diff --git a/x-pack/test/functional/apps/ml/data_visualizer/files_to_import/geo_file.csv b/x-pack/test/functional/apps/ml/data_visualizer/files_to_import/geo_file.csv new file mode 100644 index 0000000000000..df7417f474d83 --- /dev/null +++ b/x-pack/test/functional/apps/ml/data_visualizer/files_to_import/geo_file.csv @@ -0,0 +1,14 @@ +Coordinates,Location,Context +POINT (-2.516919 51.423683),On or near A4175, +POINT (-2.515072 51.419357),On or near Stockwood Hill, +POINT (-2.509126 51.416137),On or near St Francis Road, +POINT (-2.509384 51.40959),On or near Barnard Walk, +POINT (-2.509126 51.416137),On or near St Francis Road, +POINT (-2.516919 51.423683),On or near A4175, +POINT (-2.511571 51.414895),On or near Orchard Close, +POINT (-2.534338 51.417697),On or near Scotland Lane, +POINT (-2.509384 51.40959),On or near Barnard Walk, +POINT (-2.495055 51.422132),On or near Cross Street, +POINT (-2.509384 51.40959),On or near Barnard Walk, +POINT (-2.495055 51.422132),On or near Cross Street, +POINT (-2.509126 51.416137),On or near St Francis Road, \ No newline at end of file diff --git a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts index 0833f84960ea6..01d7ca6af4cc3 100644 --- a/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts +++ b/x-pack/test/functional/apps/ml/data_visualizer/index_data_visualizer.ts @@ -24,6 +24,11 @@ interface TestData { sourceIndexOrSavedSearch: string; fieldNameFilters: string[]; fieldTypeFilters: string[]; + rowsPerPage?: 10 | 25 | 50; + sampleSizeValidations: Array<{ + size: number; + expected: { field: string; docCountFormatted: string }; + }>; expected: { totalDocCountFormatted: string; metricFields?: MetricFieldVisConfig[]; @@ -47,6 +52,10 @@ export default function ({ getService }: FtrProviderContext) { sourceIndexOrSavedSearch: 'ft_farequote', fieldNameFilters: ['airline', '@timestamp'], fieldTypeFilters: [ML_JOB_FIELD_TYPES.KEYWORD], + sampleSizeValidations: [ + { size: 1000, expected: { field: 'airline', docCountFormatted: '1000 (100%)' } }, + { size: 5000, expected: { field: '@timestamp', docCountFormatted: '5000 (100%)' } }, + ], expected: { totalDocCountFormatted: '86,274', metricFields: [ @@ -132,6 +141,10 @@ export default function ({ getService }: FtrProviderContext) { sourceIndexOrSavedSearch: 'ft_farequote_kuery', fieldNameFilters: ['@version'], fieldTypeFilters: [ML_JOB_FIELD_TYPES.DATE, ML_JOB_FIELD_TYPES.TEXT], + sampleSizeValidations: [ + { size: 1000, expected: { field: 'airline', docCountFormatted: '1000 (100%)' } }, + { size: 5000, expected: { field: '@timestamp', docCountFormatted: '5000 (100%)' } }, + ], expected: { totalDocCountFormatted: '34,415', metricFields: [ @@ -217,6 +230,10 @@ export default function ({ getService }: FtrProviderContext) { sourceIndexOrSavedSearch: 'ft_farequote_lucene', fieldNameFilters: ['@version.keyword', 'type'], fieldTypeFilters: [ML_JOB_FIELD_TYPES.NUMBER], + sampleSizeValidations: [ + { size: 1000, expected: { field: 'airline', docCountFormatted: '1000 (100%)' } }, + { size: 5000, expected: { field: '@timestamp', docCountFormatted: '5000 (100%)' } }, + ], expected: { totalDocCountFormatted: '34,416', metricFields: [ @@ -297,6 +314,41 @@ export default function ({ getService }: FtrProviderContext) { }, }; + const sampleLogTestData: TestData = { + suiteTitle: 'geo point field', + sourceIndexOrSavedSearch: 'ft_module_sample_logs', + fieldNameFilters: ['geo.coordinates'], + fieldTypeFilters: [ML_JOB_FIELD_TYPES.GEO_POINT], + rowsPerPage: 50, + expected: { + totalDocCountFormatted: '408', + metricFields: [], + // only testing the geo_point fields + nonMetricFields: [ + { + fieldName: 'geo.coordinates', + type: ML_JOB_FIELD_TYPES.GEO_POINT, + existsInDocs: true, + aggregatable: true, + loading: false, + docCountFormatted: '408 (100%)', + exampleCount: 10, + }, + ], + emptyFields: [], + visibleMetricFieldsCount: 4, + totalMetricFieldsCount: 5, + populatedFieldsCount: 35, + totalFieldsCount: 36, + fieldNameFiltersResultCount: 1, + fieldTypeFiltersResultCount: 1, + }, + sampleSizeValidations: [ + { size: 1000, expected: { field: 'geo.coordinates', docCountFormatted: '408 (100%)' } }, + { size: 5000, expected: { field: '@timestamp', docCountFormatted: '408 (100%)' } }, + ], + }; + function runTests(testData: TestData) { it(`${testData.suiteTitle} loads the source data in the data visualizer`, async () => { await ml.testExecution.logTestStep( @@ -332,6 +384,10 @@ export default function ({ getService }: FtrProviderContext) { ); await ml.dataVisualizerIndexBased.assertDataVisualizerTableExist(); + if (testData.rowsPerPage) { + await ml.dataVisualizerTable.ensureNumRowsPerPage(testData.rowsPerPage); + } + await ml.dataVisualizerTable.assertSearchPanelExist(); await ml.dataVisualizerTable.assertSampleSizeInputExists(); await ml.dataVisualizerTable.assertFieldTypeInputExists(); @@ -376,8 +432,14 @@ export default function ({ getService }: FtrProviderContext) { await ml.testExecution.logTestStep( `${testData.suiteTitle} sample size control changes non-metric fields` ); - await ml.dataVisualizerTable.setSampleSizeInputValue(1000, 'airline', '1000 (100%)'); - await ml.dataVisualizerTable.setSampleSizeInputValue(5000, '@timestamp', '5000 (100%)'); + for (const sampleSizeCase of testData.sampleSizeValidations) { + const { size, expected } = sampleSizeCase; + await ml.dataVisualizerTable.setSampleSizeInputValue( + size, + expected.field, + expected.docCountFormatted + ); + } await ml.testExecution.logTestStep('sets and resets field type filter correctly'); await ml.dataVisualizerTable.setFieldTypeFilter( @@ -411,7 +473,10 @@ export default function ({ getService }: FtrProviderContext) { this.tags(['mlqa']); before(async () => { await esArchiver.loadIfNeeded('ml/farequote'); + await esArchiver.loadIfNeeded('ml/module_sample_logs'); + await ml.testResources.createIndexPatternIfNeeded('ft_farequote', '@timestamp'); + await ml.testResources.createIndexPatternIfNeeded('ft_module_sample_logs', '@timestamp'); await ml.testResources.createSavedSearchFarequoteLuceneIfNeeded(); await ml.testResources.createSavedSearchFarequoteKueryIfNeeded(); await ml.testResources.setKibanaTimeZoneToUTC(); @@ -447,5 +512,15 @@ export default function ({ getService }: FtrProviderContext) { runTests(farequoteLuceneSearchTestData); }); + + describe('with module_sample_logs ', function () { + // Run tests on full farequote index. + it(`${sampleLogTestData.suiteTitle} loads the data visualizer selector page`, async () => { + // Start navigation from the base of the ML app. + await ml.navigation.navigateToMl(); + await ml.navigation.navigateToDataVisualizer(); + }); + runTests(sampleLogTestData); + }); }); } diff --git a/x-pack/test/functional/services/ml/data_visualizer_table.ts b/x-pack/test/functional/services/ml/data_visualizer_table.ts index ad4625ed4dcb4..4772b3c894471 100644 --- a/x-pack/test/functional/services/ml/data_visualizer_table.ts +++ b/x-pack/test/functional/services/ml/data_visualizer_table.ts @@ -288,6 +288,16 @@ export function MachineLearningDataVisualizerTableProvider( await this.ensureDetailsClosed(fieldName); } + public async assertExamplesList(fieldName: string, expectedExamplesCount: number) { + const examplesList = await testSubjects.find( + this.detailsSelector(fieldName, 'mlFieldDataExamplesList') + ); + const examplesListItems = await examplesList.findAllByTagName('li'); + expect(examplesListItems).to.have.length( + expectedExamplesCount, + `Expected example list item count for field '${fieldName}' to be '${expectedExamplesCount}' (got '${examplesListItems.length}')` + ); + } public async assertTextFieldContents( fieldName: string, docCountFormatted: string, @@ -297,14 +307,33 @@ export function MachineLearningDataVisualizerTableProvider( await this.assertFieldDocCount(fieldName, docCountFormatted); await this.ensureDetailsOpen(fieldName); - const examplesList = await testSubjects.find( - this.detailsSelector(fieldName, 'mlFieldDataExamplesList') - ); - const examplesListItems = await examplesList.findAllByTagName('li'); - expect(examplesListItems).to.have.length( - expectedExamplesCount, - `Expected example list item count for field '${fieldName}' to be '${expectedExamplesCount}' (got '${examplesListItems.length}')` - ); + await this.assertExamplesList(fieldName, expectedExamplesCount); + await this.ensureDetailsClosed(fieldName); + } + + public async assertGeoPointFieldContents( + fieldName: string, + docCountFormatted: string, + expectedExamplesCount: number + ) { + await this.assertRowExists(fieldName); + await this.assertFieldDocCount(fieldName, docCountFormatted); + await this.ensureDetailsOpen(fieldName); + + await this.assertExamplesList(fieldName, expectedExamplesCount); + + await testSubjects.existOrFail(this.detailsSelector(fieldName, 'mlEmbeddedMapContent')); + + await this.ensureDetailsClosed(fieldName); + } + + public async assertUnknownFieldContents(fieldName: string, docCountFormatted: string) { + await this.assertRowExists(fieldName); + await this.assertFieldDocCount(fieldName, docCountFormatted); + await this.ensureDetailsOpen(fieldName); + + await testSubjects.existOrFail(this.detailsSelector(fieldName, 'mlDVDocumentStatsContent')); + await this.ensureDetailsClosed(fieldName); } @@ -321,10 +350,14 @@ export function MachineLearningDataVisualizerTableProvider( await this.assertKeywordFieldContents(fieldName, docCountFormatted, exampleCount); } else if (fieldType === ML_JOB_FIELD_TYPES.TEXT) { await this.assertTextFieldContents(fieldName, docCountFormatted, exampleCount); + } else if (fieldType === ML_JOB_FIELD_TYPES.GEO_POINT) { + await this.assertGeoPointFieldContents(fieldName, docCountFormatted, exampleCount); + } else if (fieldType === ML_JOB_FIELD_TYPES.UNKNOWN) { + await this.assertUnknownFieldContents(fieldName, docCountFormatted); } } - public async ensureNumRowsPerPage(n: 10 | 25 | 100) { + public async ensureNumRowsPerPage(n: 10 | 25 | 50) { const paginationButton = 'mlDataVisualizerTable > tablePaginationPopoverButton'; await retry.tryForTime(10000, async () => { await testSubjects.existOrFail(paginationButton); diff --git a/x-pack/test/reporting_api_integration/reporting_without_security.config.ts b/x-pack/test/reporting_api_integration/reporting_without_security.config.ts index 4a95a15169b59..11182bbcdb3b0 100644 --- a/x-pack/test/reporting_api_integration/reporting_without_security.config.ts +++ b/x-pack/test/reporting_api_integration/reporting_without_security.config.ts @@ -32,6 +32,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { kbnTestServer: { ...apiConfig.get('kbnTestServer'), serverArgs: [ + `--migrations.enableV2=false`, `--elasticsearch.hosts=${formatUrl(esTestConfig.getUrlParts())}`, `--logging.json=false`, `--server.maxPayloadBytes=1679958`, diff --git a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts index f53c1c589daab..ce1b58433362b 100644 --- a/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts +++ b/x-pack/test/security_solution_endpoint/apps/endpoint/policy_details.ts @@ -255,11 +255,16 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { events: { file: false, network: true, process: true }, logging: { file: 'info' }, malware: { mode: 'prevent' }, + ransomware: { mode: 'prevent' }, popup: { malware: { enabled: true, message: 'Elastic Security {action} {filename}', }, + ransomware: { + enabled: true, + message: 'Elastic Security {action} {filename}', + }, }, }, windows: { @@ -274,11 +279,16 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }, logging: { file: 'info' }, malware: { mode: 'prevent' }, + ransomware: { mode: 'prevent' }, popup: { malware: { enabled: true, message: 'Elastic Security {action} {filename}', }, + ransomware: { + enabled: true, + message: 'Elastic Security {action} {filename}', + }, }, antivirus_registration: { enabled: false, @@ -399,11 +409,16 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { events: { file: true, network: true, process: true }, logging: { file: 'info' }, malware: { mode: 'prevent' }, + ransomware: { mode: 'prevent' }, popup: { malware: { enabled: true, message: 'Elastic Security {action} {filename}', }, + ransomware: { + enabled: true, + message: 'Elastic Security {action} {filename}', + }, }, }, windows: { @@ -418,11 +433,16 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }, logging: { file: 'info' }, malware: { mode: 'prevent' }, + ransomware: { mode: 'prevent' }, popup: { malware: { enabled: true, message: 'Elastic Security {action} {filename}', }, + ransomware: { + enabled: true, + message: 'Elastic Security {action} {filename}', + }, }, antivirus_registration: { enabled: false, @@ -536,11 +556,16 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { events: { file: true, network: true, process: true }, logging: { file: 'info' }, malware: { mode: 'prevent' }, + ransomware: { mode: 'prevent' }, popup: { malware: { enabled: true, message: 'Elastic Security {action} {filename}', }, + ransomware: { + enabled: true, + message: 'Elastic Security {action} {filename}', + }, }, }, windows: { @@ -555,11 +580,16 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { }, logging: { file: 'info' }, malware: { mode: 'prevent' }, + ransomware: { mode: 'prevent' }, popup: { malware: { enabled: true, message: 'Elastic Security {action} {filename}', }, + ransomware: { + enabled: true, + message: 'Elastic Security {action} {filename}', + }, }, antivirus_registration: { enabled: false, diff --git a/x-pack/test/security_solution_endpoint/services/endpoint_policy.ts b/x-pack/test/security_solution_endpoint/services/endpoint_policy.ts index 5f54ab2539c5d..82e47896ce411 100644 --- a/x-pack/test/security_solution_endpoint/services/endpoint_policy.ts +++ b/x-pack/test/security_solution_endpoint/services/endpoint_policy.ts @@ -16,7 +16,7 @@ import { GetFullAgentPolicyResponse, GetPackagesResponse, } from '../../../plugins/fleet/common'; -import { factory as policyConfigFactory } from '../../../plugins/security_solution/common/endpoint/models/policy_config'; +import { policyFactory } from '../../../plugins/security_solution/common/endpoint/models/policy_config'; import { Immutable } from '../../../plugins/security_solution/common/endpoint/types'; // NOTE: import path below should be the deep path to the actual module - else we get CI errors @@ -178,7 +178,7 @@ export function EndpointPolicyTestResourcesProvider({ getService }: FtrProviderC streams: [], config: { policy: { - value: policyConfigFactory(), + value: policyFactory(), }, }, }, diff --git a/x-pack/test/send_search_to_background_integration/config.ts b/x-pack/test/send_search_to_background_integration/config.ts index bad818bb69664..c788cc38477e6 100644 --- a/x-pack/test/send_search_to_background_integration/config.ts +++ b/x-pack/test/send_search_to_background_integration/config.ts @@ -24,6 +24,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { resolve(__dirname, './tests/apps/dashboard/async_search'), resolve(__dirname, './tests/apps/discover'), resolve(__dirname, './tests/apps/management/search_sessions'), + resolve(__dirname, './tests/apps/lens'), ], kbnTestServer: { diff --git a/x-pack/test/send_search_to_background_integration/services/index.ts b/x-pack/test/send_search_to_background_integration/services/index.ts index 35eed5a218b42..b177ac3a01cb0 100644 --- a/x-pack/test/send_search_to_background_integration/services/index.ts +++ b/x-pack/test/send_search_to_background_integration/services/index.ts @@ -5,9 +5,9 @@ */ import { services as functionalServices } from '../../functional/services'; -import { SendToBackgroundProvider } from './send_to_background'; +import { SearchSessionsProvider } from './search_sessions'; export const services = { ...functionalServices, - searchSessions: SendToBackgroundProvider, + searchSessions: SearchSessionsProvider, }; diff --git a/x-pack/test/send_search_to_background_integration/services/send_to_background.ts b/x-pack/test/send_search_to_background_integration/services/search_sessions.ts similarity index 81% rename from x-pack/test/send_search_to_background_integration/services/send_to_background.ts rename to x-pack/test/send_search_to_background_integration/services/search_sessions.ts index 8c3261c2074ae..7041de6462243 100644 --- a/x-pack/test/send_search_to_background_integration/services/send_to_background.ts +++ b/x-pack/test/send_search_to_background_integration/services/search_sessions.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import expect from '@kbn/expect'; import { SavedObjectsFindResponse } from 'src/core/server'; import { WebElementWrapper } from '../../../../test/functional/services/lib/web_element_wrapper'; import { FtrProviderContext } from '../ftr_provider_context'; @@ -20,7 +21,7 @@ type SessionStateType = | 'restored' | 'canceled'; -export function SendToBackgroundProvider({ getService }: FtrProviderContext) { +export function SearchSessionsProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const log = getService('log'); const retry = getService('retry'); @@ -36,6 +37,17 @@ export function SendToBackgroundProvider({ getService }: FtrProviderContext) { return testSubjects.exists(SEARCH_SESSION_INDICATOR_TEST_SUBJ); } + public async missingOrFail(): Promise { + return testSubjects.missingOrFail(SEARCH_SESSION_INDICATOR_TEST_SUBJ); + } + + public async disabledOrFail() { + await this.exists(); + await expect(await (await (await this.find()).findByTagName('button')).isEnabled()).to.be( + false + ); + } + public async expectState(state: SessionStateType) { return retry.waitFor(`searchSessions indicator to get into state = ${state}`, async () => { const currentState = await ( @@ -93,12 +105,12 @@ export function SendToBackgroundProvider({ getService }: FtrProviderContext) { } /* - * This cleanup function should be used by tests that create new background sesions. - * Tests should not end with new background sessions remaining in storage since that interferes with functional tests that check the _find API. - * Alternatively, a test can navigate to `Managment > Search Sessions` and use the UI to delete any created tests. + * This cleanup function should be used by tests that create new search sessions. + * Tests should not end with new search sessions remaining in storage since that interferes with functional tests that check the _find API. + * Alternatively, a test can navigate to `Management > Search Sessions` and use the UI to delete any created tests. */ public async deleteAllSearchSessions() { - log.debug('Deleting created background sessions'); + log.debug('Deleting created searcg sessions'); // ignores 409 errs and keeps retrying await retry.tryForTime(10000, async () => { const { body } = await supertest @@ -109,10 +121,10 @@ export function SendToBackgroundProvider({ getService }: FtrProviderContext) { .expect(200); const { saved_objects: savedObjects } = body as SavedObjectsFindResponse; - log.debug(`Found created background sessions: ${savedObjects.map(({ id }) => id)}`); + log.debug(`Found created search sessions: ${savedObjects.map(({ id }) => id)}`); await Promise.all( savedObjects.map(async (so) => { - log.debug(`Deleting background session: ${so.id}`); + log.debug(`Deleting search session: ${so.id}`); await supertest .delete(`/internal/session/${so.id}`) .set(`kbn-xsrf`, `anything`) diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background.ts b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background.ts index 03635efb6113d..7e878e763bfc1 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/send_to_background.ts @@ -30,9 +30,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await searchSessions.deleteAllSearchSessions(); }); - it('Restore using non-existing sessionId errors out. Refresh starts a new session and completes.', async () => { + it('Restore using non-existing sessionId errors out. Refresh starts a new session and completes. Back button restores a session.', async () => { await PageObjects.dashboard.loadSavedDashboard('Not Delayed'); - const url = await browser.getCurrentUrl(); + let url = await browser.getCurrentUrl(); const fakeSessionId = '__fake__'; const savedSessionURL = `${url}&searchSessionId=${fakeSessionId}`; await browser.get(savedSessionURL); @@ -53,6 +53,20 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { 'Sum of Bytes by Extension' ); expect(session2).not.to.be(fakeSessionId); + + // back button should restore the session: + url = await browser.getCurrentUrl(); + expect(url).not.to.contain('searchSessionId'); + + await browser.goBack(); + + url = await browser.getCurrentUrl(); + expect(url).to.contain('searchSessionId'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await searchSessions.expectState('restored'); + expect( + await dashboardPanelActions.getSearchSessionIdByTitle('Sum of Bytes by Extension') + ).to.be(fakeSessionId); }); it('Saves and restores a session', async () => { diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/sessions_in_space.ts b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/sessions_in_space.ts index f590e44138642..6aea4368a2f3f 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/sessions_in_space.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/dashboard/async_search/sessions_in_space.ts @@ -23,7 +23,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const searchSessions = getService('searchSessions'); describe('dashboard in space', () => { - describe('Send to background in space', () => { + describe('Storing search sessions in space', () => { before(async () => { await esArchiver.load('dashboard/session_in_space'); @@ -92,5 +92,60 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { await testSubjects.missingOrFail('embeddableErrorLabel'); }); }); + + describe('Disabled storing search sessions', () => { + before(async () => { + await esArchiver.load('dashboard/session_in_space'); + + await security.role.create('data_analyst', { + elasticsearch: { + indices: [{ names: ['logstash-*'], privileges: ['all'] }], + }, + kibana: [ + { + feature: { + dashboard: ['minimal_read'], + }, + spaces: ['another-space'], + }, + ], + }); + + await security.user.create('analyst', { + password: 'analyst-password', + roles: ['data_analyst'], + full_name: 'test user', + }); + + await PageObjects.security.forceLogout(); + + await PageObjects.security.login('analyst', 'analyst-password', { + expectSpaceSelector: false, + }); + }); + + after(async () => { + await security.role.delete('data_analyst'); + await security.user.delete('analyst'); + + await esArchiver.unload('dashboard/session_in_space'); + await PageObjects.security.forceLogout(); + }); + + it("Doesn't allow to store a session", async () => { + await PageObjects.common.navigateToApp('dashboard', { basePath: 's/another-space' }); + await PageObjects.dashboard.loadSavedDashboard('A Dashboard in another space'); + + await PageObjects.timePicker.setAbsoluteRange( + 'Sep 1, 2015 @ 00:00:00.000', + 'Oct 1, 2015 @ 00:00:00.000' + ); + + await PageObjects.dashboard.waitForRenderComplete(); + + await searchSessions.expectState('completed'); + await searchSessions.disabledOrFail(); + }); + }); }); } diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/discover/async_search.ts b/x-pack/test/send_search_to_background_integration/tests/apps/discover/async_search.ts index d64df98c98601..b5e65158c573a 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/discover/async_search.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/discover/async_search.ts @@ -13,6 +13,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { const browser = getService('browser'); const inspector = getService('inspector'); const PageObjects = getPageObjects(['discover', 'common', 'timePicker', 'header']); + const searchSessions = getService('searchSessions'); describe('discover async search', () => { before(async () => { @@ -31,18 +32,33 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) { expect(searchSessionId2).not.to.be(searchSessionId1); }); - it('search session id should be picked up from the URL, non existing session id errors out', async () => { - const url = await browser.getCurrentUrl(); + it('search session id should be picked up from the URL, non existing session id errors out, back button restores a session', async () => { + let url = await browser.getCurrentUrl(); const fakeSearchSessionId = '__test__'; const savedSessionURL = url + `&searchSessionId=${fakeSearchSessionId}`; await browser.navigateTo(savedSessionURL); await PageObjects.header.waitUntilLoadingHasFinished(); + await searchSessions.expectState('restored'); await testSubjects.existOrFail('discoverNoResultsError'); // expect error because of fake searchSessionId const searchSessionId1 = await getSearchSessionId(); expect(searchSessionId1).to.be(fakeSearchSessionId); await queryBar.clickQuerySubmitButton(); + await PageObjects.header.waitUntilLoadingHasFinished(); + await searchSessions.expectState('completed'); const searchSessionId2 = await getSearchSessionId(); expect(searchSessionId2).not.to.be(searchSessionId1); + + // back button should restore the session: + url = await browser.getCurrentUrl(); + expect(url).not.to.contain('searchSessionId'); + + await browser.goBack(); + + url = await browser.getCurrentUrl(); + expect(url).to.contain('searchSessionId'); + await PageObjects.header.waitUntilLoadingHasFinished(); + await searchSessions.expectState('restored'); + expect(await getSearchSessionId()).to.be(fakeSearchSessionId); }); }); diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/discover/sessions_in_space.ts b/x-pack/test/send_search_to_background_integration/tests/apps/discover/sessions_in_space.ts index 6384afb179593..733b2edd4cd06 100644 --- a/x-pack/test/send_search_to_background_integration/tests/apps/discover/sessions_in_space.ts +++ b/x-pack/test/send_search_to_background_integration/tests/apps/discover/sessions_in_space.ts @@ -23,7 +23,7 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { const searchSessions = getService('searchSessions'); describe('discover in space', () => { - describe('Send to background in space', () => { + describe('Storing search sessions in space', () => { before(async () => { await esArchiver.load('dashboard/session_in_space'); @@ -93,7 +93,62 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) { // Check that session is restored await searchSessions.expectState('restored'); - await testSubjects.missingOrFail('embeddableErrorLabel'); + await testSubjects.missingOrFail('discoverNoResultsError'); // expect error because of fake searchSessionId + }); + }); + describe('Disabled storing search sessions in space', () => { + before(async () => { + await esArchiver.load('dashboard/session_in_space'); + + await security.role.create('data_analyst', { + elasticsearch: { + indices: [{ names: ['logstash-*'], privileges: ['all'] }], + }, + kibana: [ + { + feature: { + discover: ['read'], + }, + spaces: ['another-space'], + }, + ], + }); + + await security.user.create('analyst', { + password: 'analyst-password', + roles: ['data_analyst'], + full_name: 'test user', + }); + + await PageObjects.security.forceLogout(); + + await PageObjects.security.login('analyst', 'analyst-password', { + expectSpaceSelector: false, + }); + }); + + after(async () => { + await security.role.delete('data_analyst'); + await security.user.delete('analyst'); + + await esArchiver.unload('dashboard/session_in_space'); + await PageObjects.security.forceLogout(); + }); + + it("Doesn't allow to store a session", async () => { + await PageObjects.common.navigateToApp('discover', { basePath: 's/another-space' }); + + await PageObjects.discover.selectIndexPattern('logstash-*'); + + await PageObjects.timePicker.setAbsoluteRange( + 'Sep 1, 2015 @ 00:00:00.000', + 'Oct 1, 2015 @ 00:00:00.000' + ); + + await PageObjects.discover.waitForDocTableLoadingComplete(); + + await searchSessions.expectState('completed'); + await searchSessions.disabledOrFail(); }); }); }); diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/lens/index.ts b/x-pack/test/send_search_to_background_integration/tests/apps/lens/index.ts new file mode 100644 index 0000000000000..e84cede4bc87d --- /dev/null +++ b/x-pack/test/send_search_to_background_integration/tests/apps/lens/index.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ loadTestFile, getService }: FtrProviderContext) { + const kibanaServer = getService('kibanaServer'); + const esArchiver = getService('esArchiver'); + + describe('lens search sessions', function () { + this.tags('ciGroup3'); + + before(async () => { + await esArchiver.loadIfNeeded('logstash_functional'); + await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*' }); + }); + + loadTestFile(require.resolve('./search_sessions.ts')); + }); +} diff --git a/x-pack/test/send_search_to_background_integration/tests/apps/lens/search_sessions.ts b/x-pack/test/send_search_to_background_integration/tests/apps/lens/search_sessions.ts new file mode 100644 index 0000000000000..6bd79f0f98883 --- /dev/null +++ b/x-pack/test/send_search_to_background_integration/tests/apps/lens/search_sessions.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function ({ getPageObjects, getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const searchSession = getService('searchSessions'); + const PageObjects = getPageObjects(['visualize', 'lens', 'common', 'timePicker', 'header']); + const listingTable = getService('listingTable'); + + describe('lens search sessions', () => { + before(async () => { + await esArchiver.loadIfNeeded('logstash_functional'); + await esArchiver.loadIfNeeded('lens/basic'); + }); + after(async () => { + await esArchiver.unload('lens/basic'); + }); + + it("doesn't shows search sessions indicator UI", async () => { + await PageObjects.visualize.gotoVisualizationLandingPage(); + await listingTable.searchForItemWithName('lnsXYvis'); + await PageObjects.lens.clickVisualizeListItemTitle('lnsXYvis'); + await PageObjects.lens.goToTimeRange(); + await PageObjects.header.waitUntilLoadingHasFinished(); + expect(await PageObjects.lens.isShowingNoResults()).to.be(false); + + await searchSession.missingOrFail(); + }); + }); +} diff --git a/x-pack/test/tsconfig.json b/x-pack/test/tsconfig.json index 6368751fedf75..6a75f0c7e02d3 100644 --- a/x-pack/test/tsconfig.json +++ b/x-pack/test/tsconfig.json @@ -58,6 +58,7 @@ { "path": "../plugins/beats_management/tsconfig.json" }, { "path": "../plugins/cloud/tsconfig.json" }, { "path": "../plugins/saved_objects_tagging/tsconfig.json" }, - { "path": "../plugins/global_search_bar/tsconfig.json" } + { "path": "../plugins/global_search_bar/tsconfig.json" }, + { "path": "../plugins/license_management/tsconfig.json" } ] } diff --git a/x-pack/test/upgrade_assistant_integration/config.js b/x-pack/test/upgrade_assistant_integration/config.js index d11b39ff74e35..5409d8a470502 100644 --- a/x-pack/test/upgrade_assistant_integration/config.js +++ b/x-pack/test/upgrade_assistant_integration/config.js @@ -5,7 +5,6 @@ */ import path from 'path'; -import { LegacyEsProvider } from './services'; export default async function ({ readConfigFile }) { // Read the Kibana API integration tests config file so that we can utilize its services. @@ -25,7 +24,6 @@ export default async function ({ readConfigFile }) { services: { ...kibanaCommonConfig.get('services'), supertest: kibanaAPITestsConfig.get('services.supertest'), - legacyEs: LegacyEsProvider, }, esArchiver: xPackFunctionalTestsConfig.get('esArchiver'), junit: { diff --git a/x-pack/test/upgrade_assistant_integration/services/legacy_es.js b/x-pack/test/upgrade_assistant_integration/services/legacy_es.js deleted file mode 100644 index 78bfd63ded3c9..0000000000000 --- a/x-pack/test/upgrade_assistant_integration/services/legacy_es.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. - */ - -import { format as formatUrl } from 'url'; - -import * as legacyElasticsearch from 'elasticsearch'; - -export function LegacyEsProvider({ getService }) { - const config = getService('config'); - - return new legacyElasticsearch.Client({ - host: formatUrl(config.get('servers.elasticsearch')), - requestTimeout: config.get('timeouts.esRequestTimeout'), - }); -} diff --git a/x-pack/test/upgrade_assistant_integration/upgrade_assistant/reindexing.js b/x-pack/test/upgrade_assistant_integration/upgrade_assistant/reindexing.js index 10074f68bb59e..f57d2184d4b8d 100644 --- a/x-pack/test/upgrade_assistant_integration/upgrade_assistant/reindexing.js +++ b/x-pack/test/upgrade_assistant_integration/upgrade_assistant/reindexing.js @@ -13,7 +13,7 @@ import { getIndexState } from '../../../plugins/upgrade_assistant/common/get_ind export default function ({ getService }) { const supertest = getService('supertest'); const esArchiver = getService('esArchiver'); - const es = getService('legacyEs'); + const es = getService('es'); // Utility function that keeps polling API until reindex operation has completed or failed. const waitForReindexToComplete = async (indexName) => { @@ -65,14 +65,14 @@ export default function ({ getService }) { expect(lastState.status).to.equal(ReindexStatus.completed); const { newIndexName } = lastState; - const indexSummary = await es.indices.get({ index: 'dummydata' }); + const { body: indexSummary } = await es.indices.get({ index: 'dummydata' }); // The new index was created expect(indexSummary[newIndexName]).to.be.an('object'); // The original index name is aliased to the new one expect(indexSummary[newIndexName].aliases.dummydata).to.be.an('object'); // The number of documents in the new index matches what we expect - expect((await es.count({ index: lastState.newIndexName })).count).to.be(3); + expect((await es.count({ index: lastState.newIndexName })).body.count).to.be(3); // Cleanup newly created index await es.indices.delete({ @@ -95,9 +95,9 @@ export default function ({ getService }) { ], }, }); - expect((await es.count({ index: 'myAlias' })).count).to.be(3); - expect((await es.count({ index: 'wildcardAlias' })).count).to.be(3); - expect((await es.count({ index: 'myHttpsAlias' })).count).to.be(2); + expect((await es.count({ index: 'myAlias' })).body.count).to.be(3); + expect((await es.count({ index: 'wildcardAlias' })).body.count).to.be(3); + expect((await es.count({ index: 'myHttpsAlias' })).body.count).to.be(2); // Reindex await supertest @@ -107,10 +107,10 @@ export default function ({ getService }) { const lastState = await waitForReindexToComplete('dummydata'); // The regular aliases should still return 3 docs - expect((await es.count({ index: 'myAlias' })).count).to.be(3); - expect((await es.count({ index: 'wildcardAlias' })).count).to.be(3); + expect((await es.count({ index: 'myAlias' })).body.count).to.be(3); + expect((await es.count({ index: 'wildcardAlias' })).body.count).to.be(3); // The filtered alias should still return 2 docs - expect((await es.count({ index: 'myHttpsAlias' })).count).to.be(2); + expect((await es.count({ index: 'myHttpsAlias' })).body.count).to.be(2); // Cleanup newly created index await es.indices.delete({ @@ -204,8 +204,8 @@ export default function ({ getService }) { await assertQueueState(undefined, 0); // Check that the closed index is still closed after reindexing - const resolvedIndices = await es.transport.request({ - path: `_resolve/index/${nameOfIndexThatShouldBeClosed}`, + const { body: resolvedIndices } = await es.indices.resolveIndex({ + name: nameOfIndexThatShouldBeClosed, }); const test1ReindexedState = getIndexState(nameOfIndexThatShouldBeClosed, resolvedIndices); diff --git a/x-pack/test/usage_collection/config.ts b/x-pack/test/usage_collection/config.ts index 27b12a1ff298c..d31ecc444d00d 100644 --- a/x-pack/test/usage_collection/config.ts +++ b/x-pack/test/usage_collection/config.ts @@ -24,7 +24,10 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) { return { ...xpackFunctionalConfig.getAll(), // list paths to the files that contain your plugins tests - testFiles: [resolve(__dirname, './test_suites/application_usage')], + testFiles: [ + resolve(__dirname, './test_suites/application_usage'), + resolve(__dirname, './test_suites/stack_management_usage'), + ], services, pageObjects, diff --git a/x-pack/test/usage_collection/plugins/stack_management_usage_test/kibana.json b/x-pack/test/usage_collection/plugins/stack_management_usage_test/kibana.json new file mode 100644 index 0000000000000..b586de3fa4d79 --- /dev/null +++ b/x-pack/test/usage_collection/plugins/stack_management_usage_test/kibana.json @@ -0,0 +1,9 @@ +{ + "id": "StackManagementUsageTest", + "version": "1.0.0", + "kibanaVersion": "kibana", + "configPath": ["xpack", "StackManagementUsageTest"], + "requiredPlugins": [], + "server": false, + "ui": true +} diff --git a/x-pack/test/usage_collection/plugins/stack_management_usage_test/public/index.ts b/x-pack/test/usage_collection/plugins/stack_management_usage_test/public/index.ts new file mode 100644 index 0000000000000..82aae6988052a --- /dev/null +++ b/x-pack/test/usage_collection/plugins/stack_management_usage_test/public/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { StackManagementUsageTest } from './plugin'; + +export function plugin() { + return new StackManagementUsageTest(); +} diff --git a/x-pack/test/usage_collection/plugins/stack_management_usage_test/public/plugin.ts b/x-pack/test/usage_collection/plugins/stack_management_usage_test/public/plugin.ts new file mode 100644 index 0000000000000..3cd10a1d4c178 --- /dev/null +++ b/x-pack/test/usage_collection/plugins/stack_management_usage_test/public/plugin.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Plugin, CoreSetup, CoreStart } from 'kibana/public'; +import './types'; + +export class StackManagementUsageTest implements Plugin { + public setup(core: CoreSetup) {} + + public start(core: CoreStart) { + const allUiSettings = core.uiSettings.getAll(); + window.__registeredUiSettings__ = allUiSettings; + } +} diff --git a/x-pack/test/usage_collection/plugins/stack_management_usage_test/public/types.ts b/x-pack/test/usage_collection/plugins/stack_management_usage_test/public/types.ts new file mode 100644 index 0000000000000..c49ec89d94b9f --- /dev/null +++ b/x-pack/test/usage_collection/plugins/stack_management_usage_test/public/types.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IUiSettingsClient } from 'src/core/public'; +export {}; // Hack to declare this file as a module so TS allows us to extend the Global Window interface + +declare global { + interface Window { + __registeredUiSettings__: ReturnType; + } +} diff --git a/x-pack/test/usage_collection/plugins/stack_management_usage_test/tsconfig.json b/x-pack/test/usage_collection/plugins/stack_management_usage_test/tsconfig.json new file mode 100644 index 0000000000000..f1bf94a38de8f --- /dev/null +++ b/x-pack/test/usage_collection/plugins/stack_management_usage_test/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../../../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "public/**/*.ts", + "public/**/*.tsx", + ], + "exclude": [] +} diff --git a/x-pack/test/usage_collection/test_suites/stack_management_usage/index.ts b/x-pack/test/usage_collection/test_suites/stack_management_usage/index.ts new file mode 100644 index 0000000000000..b8f0cf522605e --- /dev/null +++ b/x-pack/test/usage_collection/test_suites/stack_management_usage/index.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import _ from 'lodash'; +import { FtrProviderContext } from '../../ftr_provider_context'; +import { stackManagementSchema } from '../../../../../src/plugins/kibana_usage_collection/server/collectors/management/schema'; + +export default function ({ getService, getPageObjects }: FtrProviderContext) { + describe('Stack Management', function () { + this.tags('ciGroup1'); + const { common } = getPageObjects(['common']); + const browser = getService('browser'); + + let registeredSettings: Record; + + before(async () => { + await common.navigateToApp('home'); // Navigate to Home to make sure all the appIds are loaded + registeredSettings = await browser.execute(() => window.__registeredUiSettings__); + }); + + it('registers all UI Settings in the UsageStats interface', () => { + const unreportedUISettings = Object.keys(registeredSettings) + .filter((key) => key !== 'buildNum') + .filter((key) => typeof _.get(stackManagementSchema, key) === 'undefined'); + + if (unreportedUISettings.length) { + throw new Error( + `Detected the following unregistered UI Settings in the stack management collector: + ${JSON.stringify(unreportedUISettings, null)} + Update the management collector schema and its UsageStats interface. + Refer to src/plugins/kibana_usage_collection/server/collectors/management/README.md for additional information. + ` + ); + } + }); + + it('registers all sensitive UI settings as keyword type', async () => { + const sensitiveSettings = Object.entries(registeredSettings) + .filter(([, config]) => config.sensitive) + .map(([key]) => key); + + const nonBooleanSensitiveProps = sensitiveSettings + .map((key) => ({ key, ..._.get(stackManagementSchema, key) })) + .filter((keyDescriptor) => keyDescriptor.type !== 'keyword'); + + if (nonBooleanSensitiveProps.length) { + throw new Error( + `Detected the following sensitive UI Settings in the stack management collector not having a 'keyword' type: + ${JSON.stringify(nonBooleanSensitiveProps, null)} + Update each setting in the management collector schema with ({ type: 'keyword' }). + Refer to src/plugins/kibana_usage_collection/server/collectors/management/README.md for additional information. + ` + ); + } + }); + }); +} diff --git a/x-pack/tsconfig.json b/x-pack/tsconfig.json index a6eb098b5d678..7ed53ca0abb6b 100644 --- a/x-pack/tsconfig.json +++ b/x-pack/tsconfig.json @@ -19,6 +19,9 @@ "plugins/event_log/**/*", "plugins/licensing/**/*", "plugins/lens/**/*", + "plugins/maps/**/*", + "plugins/maps_file_upload/**/*", + "plugins/maps_legacy_licensing/**/*", "plugins/searchprofiler/**/*", "plugins/security_solution/cypress/**/*", "plugins/task_manager/**/*", @@ -26,7 +29,6 @@ "plugins/translations/**/*", "plugins/triggers_actions_ui/**/*", "plugins/ui_actions_enhanced/**/*", - "plugins/vis_type_timeseries_enhanced/**/*", "plugins/spaces/**/*", "plugins/security/**/*", "plugins/stack_alerts/**/*", @@ -35,6 +37,7 @@ "plugins/cloud/**/*", "plugins/saved_objects_tagging/**/*", "plugins/global_search_bar/**/*", + "plugins/license_management/**/*", "test/**/*" ], "compilerOptions": { @@ -86,11 +89,13 @@ { "path": "./plugins/event_log/tsconfig.json" }, { "path": "./plugins/licensing/tsconfig.json" }, { "path": "./plugins/lens/tsconfig.json" }, + { "path": "./plugins/maps/tsconfig.json" }, + { "path": "./plugins/maps_file_upload/tsconfig.json" }, + { "path": "./plugins/maps_legacy_licensing/tsconfig.json" }, { "path": "./plugins/searchprofiler/tsconfig.json" }, { "path": "./plugins/task_manager/tsconfig.json" }, { "path": "./plugins/telemetry_collection_xpack/tsconfig.json" }, { "path": "./plugins/ui_actions_enhanced/tsconfig.json" }, - { "path": "./plugins/vis_type_timeseries_enhanced/tsconfig.json" }, { "path": "./plugins/translations/tsconfig.json" }, { "path": "./plugins/spaces/tsconfig.json" }, { "path": "./plugins/security/tsconfig.json" }, @@ -102,6 +107,7 @@ { "path": "./plugins/actions/tsconfig.json"}, { "path": "./plugins/alerts/tsconfig.json"}, { "path": "./plugins/triggers_actions_ui/tsconfig.json"}, - { "path": "./plugins/stack_alerts/tsconfig.json"} + { "path": "./plugins/stack_alerts/tsconfig.json"}, + { "path": "./plugins/license_management/tsconfig.json" }, ] } diff --git a/x-pack/tsconfig.refs.json b/x-pack/tsconfig.refs.json index 6a9e54e2e7adf..eeba8dd770da6 100644 --- a/x-pack/tsconfig.refs.json +++ b/x-pack/tsconfig.refs.json @@ -15,11 +15,13 @@ { "path": "./plugins/features/tsconfig.json" }, { "path": "./plugins/graph/tsconfig.json" }, { "path": "./plugins/embeddable_enhanced/tsconfig.json" }, + { "path": "./plugins/maps/tsconfig.json" }, + { "path": "./plugins/maps_file_upload/tsconfig.json" }, + { "path": "./plugins/maps_legacy_licensing/tsconfig.json" }, { "path": "./plugins/searchprofiler/tsconfig.json" }, { "path": "./plugins/task_manager/tsconfig.json" }, { "path": "./plugins/telemetry_collection_xpack/tsconfig.json" }, { "path": "./plugins/ui_actions_enhanced/tsconfig.json" }, - { "path": "./plugins/vis_type_timeseries_enhanced/tsconfig.json" }, { "path": "./plugins/translations/tsconfig.json" }, { "path": "./plugins/triggers_actions_ui/tsconfig.json"}, { "path": "./plugins/spaces/tsconfig.json" }, @@ -29,6 +31,7 @@ { "path": "./plugins/beats_management/tsconfig.json" }, { "path": "./plugins/cloud/tsconfig.json" }, { "path": "./plugins/saved_objects_tagging/tsconfig.json" }, - { "path": "./plugins/global_search_bar/tsconfig.json" } + { "path": "./plugins/global_search_bar/tsconfig.json" }, + { "path": "./plugins/license_management/tsconfig.json" } ] } diff --git a/x-pack/typings/elasticsearch/index.d.ts b/x-pack/typings/elasticsearch/index.d.ts index ff20ce39d6446..049e1e52c66d9 100644 --- a/x-pack/typings/elasticsearch/index.d.ts +++ b/x-pack/typings/elasticsearch/index.d.ts @@ -5,6 +5,7 @@ */ import { ValuesType } from 'utility-types'; import { Explanation, SearchParams, SearchResponse } from 'elasticsearch'; +import { RequestParams } from '@elastic/elasticsearch'; import { AggregationResponseMap, AggregationInputMap, SortOptions } from './aggregations'; export { AggregationInputMap, @@ -72,9 +73,7 @@ export interface ESSearchBody { _source?: ESSourceOptions; } -export type ESSearchRequest = Omit & { - body?: ESSearchBody; -}; +export type ESSearchRequest = RequestParams.Search; export interface ESSearchOptions { restTotalHitsAsInt: boolean; diff --git a/yarn.lock b/yarn.lock index 174f1284a3a6b..befb729569945 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1997,6 +1997,11 @@ resolved "https://registry.yarnpkg.com/@base2/pretty-print-object/-/pretty-print-object-1.0.0.tgz#860ce718b0b73f4009e153541faff2cb6b85d047" integrity sha512-4Th98KlMHr5+JkxfcoDT//6vY8vM+iSPrLNpHhRyLx2CFYi8e2RfqPLdpbnpo0Q5lQC5hNB79yes07zb02fvCw== +"@bazel/ibazel@^0.14.0": + version "0.14.0" + resolved "https://registry.yarnpkg.com/@bazel/ibazel/-/ibazel-0.14.0.tgz#86fa0002bed2ce1123b7ad98d4dd4623a0d93244" + integrity sha512-s0gyec6lArcRDwVfIP6xpY8iEaFpzrSpyErSppd3r2O49pOEg7n6HGS/qJ8ncvme56vrDk6crl/kQ6VAdEO+rg== + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -3512,6 +3517,10 @@ version "0.0.0" uid "" +"@kbn/tinymath@link:packages/kbn-tinymath": + version "0.0.0" + uid "" + "@kbn/ui-framework@link:packages/kbn-ui-framework": version "0.0.0" uid "" @@ -6861,28 +6870,29 @@ resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg== -"@typescript-eslint/eslint-plugin@^4.8.1": - version "4.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.8.1.tgz#b362abe0ee478a6c6d06c14552a6497f0b480769" - integrity sha512-d7LeQ7dbUrIv5YVFNzGgaW3IQKMmnmKFneRWagRlGYOSfLJVaRbj/FrBNOBC1a3tVO+TgNq1GbHvRtg1kwL0FQ== +"@typescript-eslint/eslint-plugin@^4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.14.1.tgz#22dd301ce228aaab3416b14ead10b1db3e7d3180" + integrity sha512-5JriGbYhtqMS1kRcZTQxndz1lKMwwEXKbwZbkUZNnp6MJX0+OVXnG0kOlBZP4LUAxEyzu3cs+EXd/97MJXsGfw== dependencies: - "@typescript-eslint/experimental-utils" "4.8.1" - "@typescript-eslint/scope-manager" "4.8.1" + "@typescript-eslint/experimental-utils" "4.14.1" + "@typescript-eslint/scope-manager" "4.14.1" debug "^4.1.1" functional-red-black-tree "^1.0.1" + lodash "^4.17.15" regexpp "^3.0.0" semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@4.8.1": - version "4.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.8.1.tgz#27275c20fa4336df99ebcf6195f7d7aa7aa9f22d" - integrity sha512-WigyLn144R3+lGATXW4nNcDJ9JlTkG8YdBWHkDlN0lC3gUGtDi7Pe3h5GPvFKMcRz8KbZpm9FJV9NTW8CpRHpg== +"@typescript-eslint/experimental-utils@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-4.14.1.tgz#a5c945cb24dabb96747180e1cfc8487f8066f471" + integrity sha512-2CuHWOJwvpw0LofbyG5gvYjEyoJeSvVH2PnfUQSn0KQr4v8Dql2pr43ohmx4fdPQ/eVoTSFjTi/bsGEXl/zUUQ== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/scope-manager" "4.8.1" - "@typescript-eslint/types" "4.8.1" - "@typescript-eslint/typescript-estree" "4.8.1" + "@typescript-eslint/scope-manager" "4.14.1" + "@typescript-eslint/types" "4.14.1" + "@typescript-eslint/typescript-estree" "4.14.1" eslint-scope "^5.0.0" eslint-utils "^2.0.0" @@ -6898,16 +6908,24 @@ eslint-scope "^5.0.0" eslint-utils "^2.0.0" -"@typescript-eslint/parser@^4.8.1": - version "4.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.8.1.tgz#4fe2fbdbb67485bafc4320b3ae91e34efe1219d1" - integrity sha512-QND8XSVetATHK9y2Ltc/XBl5Ro7Y62YuZKnPEwnNPB8E379fDsvzJ1dMJ46fg/VOmk0hXhatc+GXs5MaXuL5Uw== +"@typescript-eslint/parser@^4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-4.14.1.tgz#3bd6c24710cd557d8446625284bcc9c6d52817c6" + integrity sha512-mL3+gU18g9JPsHZuKMZ8Z0Ss9YP1S5xYZ7n68Z98GnPq02pYNQuRXL85b9GYhl6jpdvUc45Km7hAl71vybjUmw== dependencies: - "@typescript-eslint/scope-manager" "4.8.1" - "@typescript-eslint/types" "4.8.1" - "@typescript-eslint/typescript-estree" "4.8.1" + "@typescript-eslint/scope-manager" "4.14.1" + "@typescript-eslint/types" "4.14.1" + "@typescript-eslint/typescript-estree" "4.14.1" debug "^4.1.1" +"@typescript-eslint/scope-manager@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.14.1.tgz#8444534254c6f370e9aa974f035ced7fe713ce02" + integrity sha512-F4bjJcSqXqHnC9JGUlnqSa3fC2YH5zTtmACS1Hk+WX/nFB0guuynVK5ev35D4XZbdKjulXBAQMyRr216kmxghw== + dependencies: + "@typescript-eslint/types" "4.14.1" + "@typescript-eslint/visitor-keys" "4.14.1" + "@typescript-eslint/scope-manager@4.3.0": version "4.3.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.3.0.tgz#c743227e087545968080d2362cfb1273842cb6a7" @@ -6916,23 +6934,29 @@ "@typescript-eslint/types" "4.3.0" "@typescript-eslint/visitor-keys" "4.3.0" -"@typescript-eslint/scope-manager@4.8.1": - version "4.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-4.8.1.tgz#e343c475f8f1d15801b546cb17d7f309b768fdce" - integrity sha512-r0iUOc41KFFbZdPAdCS4K1mXivnSZqXS5D9oW+iykQsRlTbQRfuFRSW20xKDdYiaCoH+SkSLeIF484g3kWzwOQ== - dependencies: - "@typescript-eslint/types" "4.8.1" - "@typescript-eslint/visitor-keys" "4.8.1" +"@typescript-eslint/types@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.14.1.tgz#b3d2eb91dafd0fd8b3fce7c61512ac66bd0364aa" + integrity sha512-SkhzHdI/AllAgQSxXM89XwS1Tkic7csPdndUuTKabEwRcEfR8uQ/iPA3Dgio1rqsV3jtqZhY0QQni8rLswJM2w== "@typescript-eslint/types@4.3.0": version "4.3.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.3.0.tgz#1f0b2d5e140543e2614f06d48fb3ae95193c6ddf" integrity sha512-Cx9TpRvlRjOppGsU6Y6KcJnUDOelja2NNCX6AZwtVHRzaJkdytJWMuYiqi8mS35MRNA3cJSwDzXePfmhU6TANw== -"@typescript-eslint/types@4.8.1": - version "4.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-4.8.1.tgz#23829c73c5fc6f4fcd5346a7780b274f72fee222" - integrity sha512-ave2a18x2Y25q5K05K/U3JQIe2Av4+TNi/2YuzyaXLAsDx6UZkz1boZ7nR/N6Wwae2PpudTZmHFXqu7faXfHmA== +"@typescript-eslint/typescript-estree@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.14.1.tgz#20d3b8c8e3cdc8f764bdd5e5b0606dd83da6075b" + integrity sha512-M8+7MbzKC1PvJIA8kR2sSBnex8bsR5auatLCnVlNTJczmJgqRn8M+sAlQfkEq7M4IY3WmaNJ+LJjPVRrREVSHQ== + dependencies: + "@typescript-eslint/types" "4.14.1" + "@typescript-eslint/visitor-keys" "4.14.1" + debug "^4.1.1" + globby "^11.0.1" + is-glob "^4.0.1" + lodash "^4.17.15" + semver "^7.3.2" + tsutils "^3.17.1" "@typescript-eslint/typescript-estree@4.3.0": version "4.3.0" @@ -6948,19 +6972,13 @@ semver "^7.3.2" tsutils "^3.17.1" -"@typescript-eslint/typescript-estree@4.8.1": - version "4.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-4.8.1.tgz#7307e3f2c9e95df7daa8dc0a34b8c43b7ec0dd32" - integrity sha512-bJ6Fn/6tW2g7WIkCWh3QRlaSU7CdUUK52shx36/J7T5oTQzANvi6raoTsbwGM11+7eBbeem8hCCKbyvAc0X3sQ== +"@typescript-eslint/visitor-keys@4.14.1": + version "4.14.1" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.14.1.tgz#e93c2ff27f47ee477a929b970ca89d60a117da91" + integrity sha512-TAblbDXOI7bd0C/9PE1G+AFo7R5uc+ty1ArDoxmrC1ah61Hn6shURKy7gLdRb1qKJmjHkqu5Oq+e4Kt0jwf1IA== dependencies: - "@typescript-eslint/types" "4.8.1" - "@typescript-eslint/visitor-keys" "4.8.1" - debug "^4.1.1" - globby "^11.0.1" - is-glob "^4.0.1" - lodash "^4.17.15" - semver "^7.3.2" - tsutils "^3.17.1" + "@typescript-eslint/types" "4.14.1" + eslint-visitor-keys "^2.0.0" "@typescript-eslint/visitor-keys@4.3.0": version "4.3.0" @@ -6970,14 +6988,6 @@ "@typescript-eslint/types" "4.3.0" eslint-visitor-keys "^2.0.0" -"@typescript-eslint/visitor-keys@4.8.1": - version "4.8.1" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-4.8.1.tgz#794f68ee292d1b2e3aa9690ebedfcb3a8c90e3c3" - integrity sha512-3nrwXFdEYALQh/zW8rFwP4QltqsanCDz4CwWMPiIZmwlk9GlvBeueEIbq05SEq4ganqM0g9nh02xXgv5XI3PeQ== - dependencies: - "@typescript-eslint/types" "4.8.1" - eslint-visitor-keys "^2.0.0" - "@webassemblyjs/ast@1.9.0": version "1.9.0" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.9.0.tgz#bd850604b4042459a5a41cd7d338cbed695ed964" @@ -8438,7 +8448,7 @@ axe-core@^4.0.2: resolved "https://registry.yarnpkg.com/axe-core/-/axe-core-4.0.2.tgz#c7cf7378378a51fcd272d3c09668002a4990b1cb" integrity sha512-arU1h31OGFu+LPrOLGZ7nB45v940NMDMEJeNmbutu57P+UFDVnkZg3e+J1I2HJRZ9hT7gO8J91dn/PMrAiKakA== -axios@^0.21.0, axios@^0.21.1: +axios@^0.21.1: version "0.21.1" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== @@ -10227,13 +10237,13 @@ chrome-trace-event@^1.0.2: dependencies: tslib "^1.9.0" -chromedriver@^87.0.3: - version "87.0.5" - resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-87.0.5.tgz#5a56bae6e23fc5eaa0c5ac3b76f936e4dd0989a1" - integrity sha512-bWAKdZANrt3LXMUOKFP+DgW7DjVKfihCbjej6URkUcKsvbQBDYpf5YY5d/dXE3SOSzIFZ7fmLxogusxpsupCJg== +chromedriver@^88.0.0: + version "88.0.0" + resolved "https://registry.yarnpkg.com/chromedriver/-/chromedriver-88.0.0.tgz#6833fffd516db23c811eeafa1ee1069b5a12fd2f" + integrity sha512-EE8rXh7mikxk3VWKjUsz0KCUX8d3HkQ4HgMNJhWrWjzju12dKPPVHO9MY+YaAI5ryXrXGNf0Y4HcNKgW36P/CA== dependencies: "@testim/chrome-version" "^1.0.7" - axios "^0.21.0" + axios "^0.21.1" del "^6.0.0" extract-zip "^2.0.1" https-proxy-agent "^5.0.0" @@ -28050,11 +28060,6 @@ tinygradient@0.4.3: "@types/tinycolor2" "^1.4.0" tinycolor2 "^1.0.0" -tinymath@1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/tinymath/-/tinymath-1.2.1.tgz#f97ed66c588cdbf3c19dfba2ae266ee323db7e47" - integrity sha512-8CYutfuHR3ywAJus/3JUhaJogZap1mrUQGzNxdBiQDhP3H0uFdQenvaXvqI8lMehX4RsanRZzxVfjMBREFdQaA== - tinyqueue@^1.1.0: version "1.2.3" resolved "https://registry.yarnpkg.com/tinyqueue/-/tinyqueue-1.2.3.tgz#b6a61de23060584da29f82362e45df1ec7353f3d" @@ -28571,10 +28576,10 @@ typescript-compare@^0.0.2: dependencies: typescript-logic "^0.0.0" -typescript-fsa-reducers@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/typescript-fsa-reducers/-/typescript-fsa-reducers-1.2.1.tgz#2af1a85f7b88fb0dfb9fa59d5da51a5d7ac6543f" - integrity sha512-Qgn7zEnAU5n3YEWEL5ooEmIWZl9B4QyXD4Y/0DqpUzF0YuTrcsLa7Lht0gFXZ+xqLJXQwo3fEiTfQTDF1fBnMg== +typescript-fsa-reducers@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/typescript-fsa-reducers/-/typescript-fsa-reducers-1.2.2.tgz#1e2c8e8a50ca3e09e0d6fdf2a3b42bdd17de8094" + integrity sha512-IQ2VsIqUvmzVgWNDjxkeOxX97itl/rq+2u82jGsRdzCSFi9OtV4qf1Ec1urvj/eDlPHOaihIL7wMZzLYx9GvFg== typescript-fsa@^3.0.0: version "3.0.0" @@ -28593,10 +28598,10 @@ typescript-tuple@^2.2.1: dependencies: typescript-compare "^0.0.2" -typescript@4.1.2, typescript@^3.2.2, typescript@^3.3.3333, typescript@^3.5.3, typescript@~3.7.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.2.tgz#6369ef22516fe5e10304aae5a5c4862db55380e9" - integrity sha512-thGloWsGH3SOxv1SoY7QojKi0tc+8FnOmiarEGMbd/lar7QOEd3hvlx3Fp5y6FlDUGl9L+pd4n2e+oToGMmhRQ== +typescript@4.1.3, typescript@^3.2.2, typescript@^3.3.3333, typescript@^3.5.3, typescript@~3.7.2: + version "4.1.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7" + integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg== ua-parser-js@^0.7.18: version "0.7.22"